From 4df08e42357839754535c8f4bf53ef52304cd475 Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Wed, 5 Jun 2019 23:36:34 -0400 Subject: [PATCH 01/13] Store Depth Cache Save full depth cache upon calculation for each position --- src/main/ArbitrageExecution.js | 6 +++--- src/main/CalculationNode.js | 8 ++++---- src/main/HUD.js | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index c7b3c20..6c085a2 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -13,7 +13,7 @@ const ArbitrageExecution = { executeCalculatedPosition(calculated) { const startTime = new Date().getTime(); - const { times } = calculated; + const { depth } = calculated; const { symbol } = calculated.trade; if (!ArbitrageExecution.isSafeToExecute(calculated)) return false; @@ -25,7 +25,7 @@ const ArbitrageExecution = { ArbitrageExecution.inProgressSymbols.add(symbol.b); ArbitrageExecution.inProgressSymbols.add(symbol.c); - logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${(startTime - Math.min(times.ab, times.bc, times.ca)).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); + logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${(startTime - Math.min(depth.ab.eventTime, depth.bc.eventTime, depth.ca.eventTime)).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); return ArbitrageExecution.execute(calculated) .then((actual) => { @@ -84,7 +84,7 @@ const ArbitrageExecution = { if (calculated.percent < CONFIG.TRADING.PROFIT_THRESHOLD) return false; // Age Threshold is Not Satisfied - const ageInMilliseconds = now - Math.min(calculated.times.ab, calculated.times.bc, calculated.times.ca); + const ageInMilliseconds = now - Math.min(calculated.depth.ab.eventTime, calculated.depth.bc.eventTime, calculated.depth.ca.eventTime); if (ageInMilliseconds > CONFIG.TRADING.AGE_THRESHOLD) return false; if (CONFIG.TRADING.EXECUTION_CAP && ArbitrageExecution.getAttemptedPositionsCount() >= CONFIG.TRADING.EXECUTION_CAP) { diff --git a/src/main/CalculationNode.js b/src/main/CalculationNode.js index 1b20d3d..fdba1ea 100644 --- a/src/main/CalculationNode.js +++ b/src/main/CalculationNode.js @@ -26,10 +26,10 @@ const CalculationNode = { ab: 0, bc: 0, ca: 0, - times: { - ab: binance.depthCache(trade.ab.ticker).eventTime, - bc: binance.depthCache(trade.bc.ticker).eventTime, - ca: binance.depthCache(trade.ca.ticker).eventTime + depth: { + ab: binance.depthCache(trade.ab.ticker), + bc: binance.depthCache(trade.bc.ticker), + ca: binance.depthCache(trade.ca.ticker) }, a: 0, b: 0, diff --git a/src/main/HUD.js b/src/main/HUD.js index 5bbfe1b..63bf628 100644 --- a/src/main/HUD.js +++ b/src/main/HUD.js @@ -46,10 +46,10 @@ const HUD = { tableData.push([ `${arb.trade.symbol.a}-${arb.trade.symbol.b}-${arb.trade.symbol.c}`, `${arb.percent.toFixed(4)}%`, - `${((now - arb.times.ab)/1000).toFixed(2)}`, - `${((now - arb.times.bc)/1000).toFixed(2)}`, - `${((now - arb.times.ca)/1000).toFixed(2)}`, - `${((now - Math.min(arb.times.ab, arb.times.bc, arb.times.ca))/1000).toFixed(2)}` + `${((now - arb.depth.ab.eventTime)/1000).toFixed(2)}`, + `${((now - arb.depth.bc.eventTime)/1000).toFixed(2)}`, + `${((now - arb.depth.ca.eventTime)/1000).toFixed(2)}`, + `${((now - Math.min(arb.depth.ab.eventTime, arb.depth.bc.eventTime, arb.depth.ca.eventTime))/1000).toFixed(2)}` ]); }); From d21cfdd78c65730620f624ee7ebdd1b2507a8522 Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Fri, 7 Jun 2019 19:24:54 -0400 Subject: [PATCH 02/13] tmp: log depth caches --- src/main/ArbitrageExecution.js | 10 ++++++++++ src/main/BinanceApi.js | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index 6c085a2..ff10bc3 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -26,6 +26,7 @@ const ArbitrageExecution = { ArbitrageExecution.inProgressSymbols.add(symbol.c); logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${(startTime - Math.min(depth.ab.eventTime, depth.bc.eventTime, depth.ca.eventTime)).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); + const PRE_EXECUTION_DEPTH_CACHE = BinanceApi.getDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); return ArbitrageExecution.execute(calculated) .then((actual) => { @@ -34,6 +35,8 @@ const ArbitrageExecution = { // Results are only collected when a trade is executed if (!CONFIG.TRADING.ENABLED) return; + const POST_EXECUTION_DEPTH_CACHE = BinanceApi.getDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); + logger.execution.debug(); logger.execution.debug(`AB Expected Conversion: ${calculated.start.toFixed(8)} ${symbol.a} into ${calculated.b.toFixed(8)} ${symbol.b}`); logger.execution.debug(`AB Observed Conversion: ${actual.a.spent.toFixed(8)} ${symbol.a} into ${actual.b.earned.toFixed(8)} ${symbol.b}`); @@ -44,6 +47,13 @@ const ArbitrageExecution = { logger.execution.debug(`CA Expected Conversion: ${calculated.c.toFixed(8)} ${symbol.c} into ${calculated.a.toFixed(8)} ${symbol.a}`); logger.execution.debug(`CA Observed Conversion: ${actual.c.spent.toFixed(8)} ${symbol.c} into ${actual.a.earned.toFixed(8)} ${symbol.a}`); logger.execution.debug(); + logger.execution.debug(`Pre-calculation depth cache`); + logger.execution.debug(calculated.depth); + logger.execution.debug(`Pre-execution depth cache`); + logger.execution.debug(PRE_EXECUTION_DEPTH_CACHE); + logger.execution.debug(`Post-execution depth cache`); + logger.execution.debug(POST_EXECUTION_DEPTH_CACHE); + logger.execution.debug(); const delta = { a: actual.a.earned - actual.a.spent, diff --git a/src/main/BinanceApi.js b/src/main/BinanceApi.js index 946d2e9..c63b8e9 100644 --- a/src/main/BinanceApi.js +++ b/src/main/BinanceApi.js @@ -25,6 +25,14 @@ const BinanceApi = { }); }, + getDepths(abTicker, bcTicker, caTicker) { + return { + ab: binance.depthCache(abTicker), + bc: binance.depthCache(bcTicker), + ca: binance.depthCache(caTicker) + }; + }, + marketBuy(ticker, quantity) { logger.execution.info(`${binance.getOption('test') ? 'Test: Buying' : 'Buying'} ${quantity} ${ticker} @ market price`); return new Promise((resolve, reject) => { From dde7c918ab38dfb98c4678e19f8318761dd5a198 Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Sat, 8 Jun 2019 14:38:04 -0400 Subject: [PATCH 03/13] Fix variable by reference bug --- src/main/ArbitrageExecution.js | 6 ++++-- src/main/BinanceApi.js | 14 ++++++++++---- src/main/CalculationNode.js | 10 +++++----- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index ff10bc3..ffab266 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -26,7 +26,8 @@ const ArbitrageExecution = { ArbitrageExecution.inProgressSymbols.add(symbol.c); logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${(startTime - Math.min(depth.ab.eventTime, depth.bc.eventTime, depth.ca.eventTime)).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); - const PRE_EXECUTION_DEPTH_CACHE = BinanceApi.getDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); + const PRE_EXECUTION_DEPTH_CACHE = BinanceApi.cloneDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); + logger.execution.debug(`Pre Time: ${PRE_EXECUTION_DEPTH_CACHE[0].eventTime} ${PRE_EXECUTION_DEPTH_CACHE[1].eventTime} ${PRE_EXECUTION_DEPTH_CACHE[2].eventTime}`); return ArbitrageExecution.execute(calculated) .then((actual) => { @@ -35,7 +36,8 @@ const ArbitrageExecution = { // Results are only collected when a trade is executed if (!CONFIG.TRADING.ENABLED) return; - const POST_EXECUTION_DEPTH_CACHE = BinanceApi.getDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); + const POST_EXECUTION_DEPTH_CACHE = BinanceApi.cloneDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); + logger.execution.debug(`Post Time: ${POST_EXECUTION_DEPTH_CACHE[0].eventTime} ${POST_EXECUTION_DEPTH_CACHE[1].eventTime} ${POST_EXECUTION_DEPTH_CACHE[2].eventTime}`); logger.execution.debug(); logger.execution.debug(`AB Expected Conversion: ${calculated.start.toFixed(8)} ${symbol.a} into ${calculated.b.toFixed(8)} ${symbol.b}`); diff --git a/src/main/BinanceApi.js b/src/main/BinanceApi.js index c63b8e9..0c0f864 100644 --- a/src/main/BinanceApi.js +++ b/src/main/BinanceApi.js @@ -25,14 +25,20 @@ const BinanceApi = { }); }, - getDepths(abTicker, bcTicker, caTicker) { + cloneDepth(ticker) { + const tmp = binance.depthCache(ticker); return { - ab: binance.depthCache(abTicker), - bc: binance.depthCache(bcTicker), - ca: binance.depthCache(caTicker) + eventTime: tmp.eventTime, + lastUpdateId: tmp.lastUpdateId, + asks: {...tmp.asks}, + bids: {...tmp.bids} }; }, + cloneDepths(...tickers) { + return tickers.map(ticker => BinanceApi.cloneDepth(ticker)); + }, + marketBuy(ticker, quantity) { logger.execution.info(`${binance.getOption('test') ? 'Test: Buying' : 'Buying'} ${quantity} ${ticker} @ market price`); return new Promise((resolve, reject) => { diff --git a/src/main/CalculationNode.js b/src/main/CalculationNode.js index fdba1ea..fecfedf 100644 --- a/src/main/CalculationNode.js +++ b/src/main/CalculationNode.js @@ -1,5 +1,5 @@ const CONFIG = require('../../config/config'); -const binance = require('node-binance-api')(); +const BinanceApi = require('./BinanceApi'); const MarketCache = require('./MarketCache'); const CalculationNode = { @@ -27,9 +27,9 @@ const CalculationNode = { bc: 0, ca: 0, depth: { - ab: binance.depthCache(trade.ab.ticker), - bc: binance.depthCache(trade.bc.ticker), - ca: binance.depthCache(trade.ca.ticker) + ab: BinanceApi.cloneDepth(trade.ab.ticker), + bc: BinanceApi.cloneDepth(trade.bc.ticker), + ca: BinanceApi.cloneDepth(trade.ca.ticker) }, a: 0, b: 0, @@ -77,7 +77,7 @@ const CalculationNode = { orderBookConversion(amountFrom, symbolFrom, symbolTo, ticker) { let i, j, rate, quantity, exchangeableAmount; - let orderBook = binance.depthCache(ticker) || {}; + let orderBook = BinanceApi.cloneDepth(ticker) || {}; const bidRates = Object.keys(orderBook.bids || {}); const askRates = Object.keys(orderBook.asks || {}); let amountTo = 0; From 5ef6dbbb78dd0394f6163acacc011b1a333c0c6c Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Sun, 9 Jun 2019 21:03:31 -0400 Subject: [PATCH 04/13] Abstract Actual Delta Calculation --- src/main/ArbitrageExecution.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index ffab266..b6631b1 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -57,20 +57,15 @@ const ArbitrageExecution = { logger.execution.debug(POST_EXECUTION_DEPTH_CACHE); logger.execution.debug(); - const delta = { - a: actual.a.earned - actual.a.spent, - b: actual.b.earned - actual.b.spent, - c: actual.c.earned - actual.c.spent - }; const percent = { - a: delta.a / actual.a.spent * 100, - b: delta.b / actual.b.spent * 100, - c: delta.c / actual.c.spent * 100 + a: actual.a.delta / actual.a.spent * 100, + b: actual.b.delta / actual.b.spent * 100, + c: actual.c.delta / actual.c.spent * 100 }; - logger.execution.info(`${symbol.a} delta:\t ${delta.a < 0 ? '' : ' '}${delta.a.toFixed(8)} (${percent.a < 0 ? '' : ' '}${percent.a.toFixed(4)}%)`); - logger.execution.info(`${symbol.b} delta:\t ${delta.b < 0 ? '' : ' '}${delta.b.toFixed(8)} (${percent.b < 0 ? '' : ' '}${percent.b.toFixed(4)}%)`); - logger.execution.info(`${symbol.c} delta:\t ${delta.c < 0 ? '' : ' '}${delta.c.toFixed(8)} (${percent.c < 0 ? '' : ' '}${percent.c.toFixed(4)}%)`); + logger.execution.info(`${symbol.a} delta:\t ${actual.a.delta < 0 ? '' : ' '}${actual.a.delta.toFixed(8)} (${percent.a < 0 ? '' : ' '}${percent.a.toFixed(4)}%)`); + logger.execution.info(`${symbol.b} delta:\t ${actual.b.delta < 0 ? '' : ' '}${actual.b.delta.toFixed(8)} (${percent.b < 0 ? '' : ' '}${percent.b.toFixed(4)}%)`); + logger.execution.info(`${symbol.c} delta:\t ${actual.c.delta < 0 ? '' : ' '}${actual.c.delta.toFixed(8)} (${percent.c < 0 ? '' : ' '}${percent.c.toFixed(4)}%)`); logger.execution.info(`BNB commission: ${(-1 * actual.fees).toFixed(8)}`); logger.execution.info(); }) @@ -186,6 +181,10 @@ const ArbitrageExecution = { [actual.c.spent, actual.a.earned, fees] = ArbitrageExecution.parseActualResults(calculated.trade.ca.method, resultsCA); actual.fees += fees; + + actual.a.delta = actual.a.earned - actual.a.spent; + actual.b.delta = actual.b.earned - actual.b.spent; + actual.c.delta = actual.c.earned - actual.c.spent; } return actual; @@ -236,6 +235,12 @@ const ArbitrageExecution = { actual.fees += fees; } return actual; + }) + .then((actual) => { + actual.a.delta = actual.a.earned - actual.a.spent; + actual.b.delta = actual.b.earned - actual.b.spent; + actual.c.delta = actual.c.earned - actual.c.spent; + return actual; }); }, From 2d423cc402163d5dac6a5a9c7ce49fa9f7e011cf Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Sun, 9 Jun 2019 21:43:08 -0400 Subject: [PATCH 05/13] Log Ping --- src/main/Main.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/Main.js b/src/main/Main.js index 119a943..57bdf73 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -19,7 +19,11 @@ if (CONFIG.TRADING.ENABLED) console.log(`WARNING! Order execution is enabled!\n` ArbitrageExecution.refreshBalances() .then(() => SpeedTest.multiPing(5)) - .then((pings) => console.log(`Successfully pinged the Binance api in ${CalculationNode.average(pings).toFixed(0)} ms`)) + .then((pings) => { + const msg = `Successfully pinged the Binance api in ${CalculationNode.average(pings).toFixed(0)} ms`; + console.log(msg); + logger.performance.info(msg); + }) .then(BinanceApi.exchangeInfo) .then(exchangeInfo => MarketCache.initialize(exchangeInfo, CONFIG.TRADING.WHITELIST, CONFIG.INVESTMENT.BASE)) .then(() => logger.execution.debug({configuration: CONFIG})) From 6bd3b9ac65dc6ed427c9a95bec369e1f96a5c46c Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Sun, 9 Jun 2019 21:44:25 -0400 Subject: [PATCH 06/13] Remove Depth Logs --- src/main/ArbitrageExecution.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index b6631b1..2c806cc 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -26,8 +26,6 @@ const ArbitrageExecution = { ArbitrageExecution.inProgressSymbols.add(symbol.c); logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${(startTime - Math.min(depth.ab.eventTime, depth.bc.eventTime, depth.ca.eventTime)).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); - const PRE_EXECUTION_DEPTH_CACHE = BinanceApi.cloneDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); - logger.execution.debug(`Pre Time: ${PRE_EXECUTION_DEPTH_CACHE[0].eventTime} ${PRE_EXECUTION_DEPTH_CACHE[1].eventTime} ${PRE_EXECUTION_DEPTH_CACHE[2].eventTime}`); return ArbitrageExecution.execute(calculated) .then((actual) => { @@ -36,9 +34,6 @@ const ArbitrageExecution = { // Results are only collected when a trade is executed if (!CONFIG.TRADING.ENABLED) return; - const POST_EXECUTION_DEPTH_CACHE = BinanceApi.cloneDepths(calculated.trade.ab.ticker, calculated.trade.bc.ticker, calculated.trade.ca.ticker); - logger.execution.debug(`Post Time: ${POST_EXECUTION_DEPTH_CACHE[0].eventTime} ${POST_EXECUTION_DEPTH_CACHE[1].eventTime} ${POST_EXECUTION_DEPTH_CACHE[2].eventTime}`); - logger.execution.debug(); logger.execution.debug(`AB Expected Conversion: ${calculated.start.toFixed(8)} ${symbol.a} into ${calculated.b.toFixed(8)} ${symbol.b}`); logger.execution.debug(`AB Observed Conversion: ${actual.a.spent.toFixed(8)} ${symbol.a} into ${actual.b.earned.toFixed(8)} ${symbol.b}`); @@ -49,13 +44,9 @@ const ArbitrageExecution = { logger.execution.debug(`CA Expected Conversion: ${calculated.c.toFixed(8)} ${symbol.c} into ${calculated.a.toFixed(8)} ${symbol.a}`); logger.execution.debug(`CA Observed Conversion: ${actual.c.spent.toFixed(8)} ${symbol.c} into ${actual.a.earned.toFixed(8)} ${symbol.a}`); logger.execution.debug(); - logger.execution.debug(`Pre-calculation depth cache`); - logger.execution.debug(calculated.depth); - logger.execution.debug(`Pre-execution depth cache`); - logger.execution.debug(PRE_EXECUTION_DEPTH_CACHE); - logger.execution.debug(`Post-execution depth cache`); - logger.execution.debug(POST_EXECUTION_DEPTH_CACHE); - logger.execution.debug(); + + logger.execution.trace(`Depth cache used for calculation:`); + logger.execution.trace(calculated.depth); const percent = { a: actual.a.delta / actual.a.spent * 100, From 30c23c7dca5338aa489e4acd5347915c16930279 Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Sun, 9 Jun 2019 22:18:45 -0400 Subject: [PATCH 07/13] Check for Enough Optimization Ticks --- src/main/Main.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/Main.js b/src/main/Main.js index 57bdf73..eaf8f73 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -38,7 +38,6 @@ ArbitrageExecution.refreshBalances() .then(() => { console.log(); console.log(`Execution Strategy: ${CONFIG.TRADING.EXECUTION_STRATEGY}`); - console.log(`Optimization Ticks: ${((CONFIG.INVESTMENT.MAX - CONFIG.INVESTMENT.MIN) / CONFIG.INVESTMENT.STEP).toFixed(0)} ticks(s)`); console.log(`Execution Limit: ${CONFIG.TRADING.EXECUTION_CAP} execution(s)`); console.log(`Profit Threshold: ${CONFIG.TRADING.PROFIT_THRESHOLD.toFixed(2)}%`); console.log(`Age Threshold: ${CONFIG.TRADING.AGE_THRESHOLD} ms`); @@ -119,6 +118,21 @@ function checkConfig() { logger.execution.error(msg); throw new Error(msg); } + if (CONFIG.INVESTMENT.STEP <= 0) { + const msg = `INVESTMENT.STEP must be a positive value`; + logger.execution.error(msg); + throw new Error(msg); + } + if (CONFIG.INVESTMENT.MIN > CONFIG.INVESTMENT.MAX) { + const msg = `INVESTMENT.MIN cannot be greater than INVESTMENT.MAX`; + logger.execution.error(msg); + throw new Error(msg); + } + if ((CONFIG.INVESTMENT.MIN !== CONFIG.INVESTMENT.MAX) && (CONFIG.INVESTMENT.MAX - CONFIG.INVESTMENT.MIN) / CONFIG.INVESTMENT.STEP < 1) { + const msg = `Not enough steps between INVESTMENT.MIN and INVESTMENT.MAX using step size of ${CONFIG.INVESTMENT.STEP}`; + logger.execution.error(msg); + throw new Error(msg); + } if (CONFIG.TRADING.WHITELIST.length > 0 && !CONFIG.TRADING.WHITELIST.includes(CONFIG.INVESTMENT.BASE)) { const msg = `Whitelist must include the base symbol of ${CONFIG.INVESTMENT.BASE}`; logger.execution.debug(`Whitelist: [${CONFIG.TRADING.WHITELIST}]`); From 6a25b63eb5157486f82f015ae5e3bb349464cb35 Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Sun, 9 Jun 2019 22:18:55 -0400 Subject: [PATCH 08/13] Remove Depth Pruning --- src/main/Main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/Main.js b/src/main/Main.js index eaf8f73..82d0e19 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -59,8 +59,6 @@ function calculateArbitrage() { let errorCount = 0; let results = {}; - MarketCache.pruneDepthsAboveThreshold(CONFIG.DEPTH.SIZE); - MarketCache.relationships.forEach(relationship => { try { const calculated = CalculationNode.optimize(relationship); From 4ee9efe0acc5a885e8b699b8cc6a10b014e9a562 Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Tue, 11 Jun 2019 00:59:33 -0400 Subject: [PATCH 09/13] Reverse Orderbook Math --- src/main/ArbitrageExecution.js | 6 +- src/main/CalculationNode.js | 123 +++++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 30 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index 2c806cc..f6fdfc8 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -35,13 +35,13 @@ const ArbitrageExecution = { if (!CONFIG.TRADING.ENABLED) return; logger.execution.debug(); - logger.execution.debug(`AB Expected Conversion: ${calculated.start.toFixed(8)} ${symbol.a} into ${calculated.b.toFixed(8)} ${symbol.b}`); + logger.execution.debug(`AB Expected Conversion: ${calculated.a.spent.toFixed(8)} ${symbol.a} into ${calculated.b.earned.toFixed(8)} ${symbol.b}`); logger.execution.debug(`AB Observed Conversion: ${actual.a.spent.toFixed(8)} ${symbol.a} into ${actual.b.earned.toFixed(8)} ${symbol.b}`); logger.execution.debug(); - logger.execution.debug(`BC Expected Conversion: ${calculated.b.toFixed(8)} ${symbol.b} into ${calculated.c.toFixed(8)} ${symbol.c}`); + logger.execution.debug(`BC Expected Conversion: ${calculated.b.spent.toFixed(8)} ${symbol.b} into ${calculated.c.earned.toFixed(8)} ${symbol.c}`); logger.execution.debug(`BC Observed Conversion: ${actual.b.spent.toFixed(8)} ${symbol.b} into ${actual.c.earned.toFixed(8)} ${symbol.c}`); logger.execution.debug(); - logger.execution.debug(`CA Expected Conversion: ${calculated.c.toFixed(8)} ${symbol.c} into ${calculated.a.toFixed(8)} ${symbol.a}`); + logger.execution.debug(`CA Expected Conversion: ${calculated.c.spent.toFixed(8)} ${symbol.c} into ${calculated.a.earned.toFixed(8)} ${symbol.a}`); logger.execution.debug(`CA Observed Conversion: ${actual.c.spent.toFixed(8)} ${symbol.c} into ${actual.a.earned.toFixed(8)} ${symbol.a}`); logger.execution.debug(); diff --git a/src/main/CalculationNode.js b/src/main/CalculationNode.js index fecfedf..077a70e 100644 --- a/src/main/CalculationNode.js +++ b/src/main/CalculationNode.js @@ -22,7 +22,6 @@ const CalculationNode = { let calculated = { id: `${trade.symbol.a}-${trade.symbol.b}-${trade.symbol.c}`, trade: trade, - start: investmentA, ab: 0, bc: 0, ca: 0, @@ -31,36 +30,62 @@ const CalculationNode = { bc: BinanceApi.cloneDepth(trade.bc.ticker), ca: BinanceApi.cloneDepth(trade.ca.ticker) }, - a: 0, - b: 0, - c: 0 + a: { + spent: 0, + earned: 0, + delta: 0 + }, + b: { + spent: 0, + earned: 0, + delta: 0 + }, + c: { + spent: 0, + earned: 0, + delta: 0 + } }; if (trade.ab.method === 'Buy') { - const dustedAB = CalculationNode.orderBookConversion(investmentA, trade.symbol.a, trade.symbol.b, trade.ab.ticker); - calculated.b = calculated.ab = CalculationNode.calculateDustless(trade.ab.ticker, dustedAB); + // Buying BA + const dustedB = CalculationNode.orderBookConversion(investmentA, trade.symbol.a, trade.symbol.b, trade.ab.ticker); + calculated.b.earned = calculated.ab = CalculationNode.calculateDustless(trade.ab.ticker, dustedB); + calculated.a.spent = CalculationNode.orderBookReverseConversion(calculated.b.earned, trade.symbol.b, trade.symbol.a, trade.ab.ticker); } else { - calculated.start = calculated.ab = CalculationNode.calculateDustless(trade.ab.ticker, investmentA); - calculated.b = CalculationNode.orderBookConversion(calculated.ab, trade.symbol.a, trade.symbol.b, trade.ab.ticker); + // Selling AB + calculated.a.spent = calculated.ab = CalculationNode.calculateDustless(trade.ab.ticker, investmentA); + calculated.b.earned = CalculationNode.orderBookConversion(calculated.a.spent, trade.symbol.a, trade.symbol.b, trade.ab.ticker); } if (trade.bc.method === 'Buy') { - const dustedBC = CalculationNode.orderBookConversion(calculated.b, trade.symbol.b, trade.symbol.c, trade.bc.ticker); - calculated.c = calculated.bc = CalculationNode.calculateDustless(trade.bc.ticker, dustedBC); + // Buying CB + const dustedC = CalculationNode.orderBookConversion(calculated.b.earned, trade.symbol.b, trade.symbol.c, trade.bc.ticker); + calculated.c.earned = calculated.bc = CalculationNode.calculateDustless(trade.bc.ticker, dustedC); + calculated.b.spent = CalculationNode.orderBookReverseConversion(calculated.c.earned, trade.symbol.c, trade.symbol.b, trade.bc.ticker); } else { - calculated.bc = CalculationNode.calculateDustless(trade.bc.ticker, calculated.b); - calculated.c = CalculationNode.orderBookConversion(calculated.bc, trade.symbol.b, trade.symbol.c, trade.bc.ticker); + // Selling BC + calculated.b.spent = calculated.bc = CalculationNode.calculateDustless(trade.bc.ticker, calculated.b.earned); + calculated.c.earned = CalculationNode.orderBookConversion(calculated.b.spent, trade.symbol.b, trade.symbol.c, trade.bc.ticker); } if (trade.ca.method === 'Buy') { - const dustedCA = CalculationNode.orderBookConversion(calculated.c, trade.symbol.c, trade.symbol.a, trade.ca.ticker); - calculated.a = calculated.ca = CalculationNode.calculateDustless(trade.ca.ticker, dustedCA); + // Buying AC + const dustedA = CalculationNode.orderBookConversion(calculated.c.earned, trade.symbol.c, trade.symbol.a, trade.ca.ticker); + calculated.a.earned = calculated.ca = CalculationNode.calculateDustless(trade.ca.ticker, dustedA); + calculated.c.spent = CalculationNode.orderBookReverseConversion(calculated.a.earned, trade.symbol.a, trade.symbol.c, trade.ca.ticker); } else { - calculated.ca = CalculationNode.calculateDustless(trade.ca.ticker, calculated.c); - calculated.a = CalculationNode.orderBookConversion(calculated.ca, trade.symbol.c, trade.symbol.a, trade.ca.ticker); + // Selling CA + calculated.c.spent = calculated.ca = CalculationNode.calculateDustless(trade.ca.ticker, calculated.c.earned); + calculated.a.earned = CalculationNode.orderBookConversion(calculated.c.spent, trade.symbol.c, trade.symbol.a, trade.ca.ticker); } - calculated.percent = (calculated.a - calculated.start) / calculated.start * 100 - (CONFIG.TRADING.TAKER_FEE * 3); + // Calculate deltas + calculated.a.delta = calculated.a.earned - calculated.a.spent; + calculated.b.delta = calculated.b.earned - calculated.b.spent; + calculated.c.delta = calculated.c.earned - calculated.c.spent; + + calculated.percent = (calculated.a.delta / calculated.a.spent * 100) - (CONFIG.TRADING.TAKER_FEE * 3); if (!calculated.percent) calculated.percent = 0; return calculated; @@ -76,38 +101,40 @@ const CalculationNode = { }, orderBookConversion(amountFrom, symbolFrom, symbolTo, ticker) { - let i, j, rate, quantity, exchangeableAmount; + if (amountFrom === 0) return 0; + + let amountTo = 0; + let i, rate, quantity, exchangeableAmount; let orderBook = BinanceApi.cloneDepth(ticker) || {}; const bidRates = Object.keys(orderBook.bids || {}); const askRates = Object.keys(orderBook.asks || {}); - let amountTo = 0; - if (amountFrom === 0) return 0; if (parseFloat(bidRates[0]) > parseFloat(askRates[0])) throw new Error(`Spread does not exist for ${ticker}`); if (ticker === symbolFrom + symbolTo) { for (i=0; i parseFloat(askRates[0])) throw new Error(`Spread does not exist for ${ticker}`); + + if (ticker === symbolFrom + symbolTo) { + for (i=0; i Date: Fri, 14 Jun 2019 00:43:27 -0400 Subject: [PATCH 10/13] Symbol destructuring --- src/main/ArbitrageExecution.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index f6fdfc8..182e28e 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -77,6 +77,7 @@ const ArbitrageExecution = { isSafeToExecute(calculated) { const now = new Date().getTime(); + const { symbol } = calculated.trade; // Profit Threshold is Not Satisfied if (calculated.percent < CONFIG.TRADING.PROFIT_THRESHOLD) return false; @@ -89,16 +90,16 @@ const ArbitrageExecution = { logger.execution.trace(`Blocking execution because ${ArbitrageExecution.getAttemptedPositionsCount()} executions have been attempted`); return false; } - if (ArbitrageExecution.inProgressSymbols.has(calculated.trade.symbol.a)) { - logger.execution.trace(`Blocking execution because ${calculated.trade.symbol.a} is currently involved in an execution`); + if (ArbitrageExecution.inProgressSymbols.has(symbol.a)) { + logger.execution.trace(`Blocking execution because ${symbol.a} is currently involved in an execution`); return false; } - if (ArbitrageExecution.inProgressSymbols.has(calculated.trade.symbol.b)) { - logger.execution.trace(`Blocking execution because ${calculated.trade.symbol.b} is currently involved in an execution`); + if (ArbitrageExecution.inProgressSymbols.has(symbol.b)) { + logger.execution.trace(`Blocking execution because ${symbol.b} is currently involved in an execution`); return false; } - if (ArbitrageExecution.inProgressSymbols.has(calculated.trade.symbol.c)) { - logger.execution.trace(`Blocking execution because ${calculated.trade.symbol.c} is currently involved in an execution`); + if (ArbitrageExecution.inProgressSymbols.has(symbol.c)) { + logger.execution.trace(`Blocking execution because ${symbol.c} is currently involved in an execution`); return false; } if (ArbitrageExecution.getAttemptedPositionsCountInLastSecond() > 1) { From f7b6772b251583ba107ff62af82391e7d74be48d Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Fri, 14 Jun 2019 00:54:48 -0400 Subject: [PATCH 11/13] Log Individual Depth Cache Ages --- src/main/ArbitrageExecution.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index 182e28e..18b3dba 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -13,11 +13,15 @@ const ArbitrageExecution = { executeCalculatedPosition(calculated) { const startTime = new Date().getTime(); - const { depth } = calculated; - const { symbol } = calculated.trade; - if (!ArbitrageExecution.isSafeToExecute(calculated)) return false; + const { symbol } = calculated.trade; + const age = { + ab: startTime - calculated.depth.ab.eventTime, + bc: startTime - calculated.depth.bc.eventTime, + ca: startTime - calculated.depth.ca.eventTime + }; + // Register position as being attempted ArbitrageExecution.attemptedPositions[startTime] = calculated.id; ArbitrageExecution.inProgressIds.add(calculated.id); @@ -25,7 +29,10 @@ const ArbitrageExecution = { ArbitrageExecution.inProgressSymbols.add(symbol.b); ArbitrageExecution.inProgressSymbols.add(symbol.c); - logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${(startTime - Math.min(depth.ab.eventTime, depth.bc.eventTime, depth.ca.eventTime)).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); + logger.execution.info(`Attempting to execute ${calculated.id} with an age of ${Math.max(age.ab, age.bc, age.ca).toFixed(0)} ms and expected profit of ${calculated.percent.toFixed(4)}%`); + logger.execution.debug(`${calculated.trade.ab.ticker} depth cache age: ${age.ab.toFixed(0)} ms`); + logger.execution.debug(`${calculated.trade.bc.ticker} depth cache age: ${age.bc.toFixed(0)} ms`); + logger.execution.debug(`${calculated.trade.ca.ticker} depth cache age: ${age.ca.toFixed(0)} ms`); return ArbitrageExecution.execute(calculated) .then((actual) => { From 198a051f321baf7c83ac98452ce0f58a3836bb4b Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Fri, 14 Jun 2019 00:55:41 -0400 Subject: [PATCH 12/13] Avoid Unknown Depth Cache Updates --- src/main/ArbitrageExecution.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/ArbitrageExecution.js b/src/main/ArbitrageExecution.js index 18b3dba..ec45cab 100644 --- a/src/main/ArbitrageExecution.js +++ b/src/main/ArbitrageExecution.js @@ -91,7 +91,7 @@ const ArbitrageExecution = { // Age Threshold is Not Satisfied const ageInMilliseconds = now - Math.min(calculated.depth.ab.eventTime, calculated.depth.bc.eventTime, calculated.depth.ca.eventTime); - if (ageInMilliseconds > CONFIG.TRADING.AGE_THRESHOLD) return false; + if (isNaN(ageInMilliseconds) || ageInMilliseconds > CONFIG.TRADING.AGE_THRESHOLD) return false; if (CONFIG.TRADING.EXECUTION_CAP && ArbitrageExecution.getAttemptedPositionsCount() >= CONFIG.TRADING.EXECUTION_CAP) { logger.execution.trace(`Blocking execution because ${ArbitrageExecution.getAttemptedPositionsCount()} executions have been attempted`); From 303f886d948787d6a6416ef0d3251019b1c34dcd Mon Sep 17 00:00:00 2001 From: Brandon Mino Date: Fri, 14 Jun 2019 00:56:48 -0400 Subject: [PATCH 13/13] Update Version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cb32bac..cde7237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "binance-triangle-arbitrage", - "version": "5.1.1", + "version": "5.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8eb8afd..eea9136 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "binance-triangle-arbitrage", - "version": "5.1.1", + "version": "5.1.2", "private": true, "engines": { "node": "11.10.1",