From 7b33ee140612dee02edc7eacacda544e1ceb4c06 Mon Sep 17 00:00:00 2001 From: Luis Fernando Molina Date: Sat, 10 Mar 2018 11:58:25 +0100 Subject: [PATCH] balance and availableBalance handling --- .vs/AACloudPlatform/v15/.suo | Bin 76800 -> 76800 bytes Assistant.js | 253 ++++++++++++++++++++++++++++------- Context.js | 34 ++++- Run.js | 10 ++ this.vm.config.json | 2 +- 5 files changed, 242 insertions(+), 57 deletions(-) diff --git a/.vs/AACloudPlatform/v15/.suo b/.vs/AACloudPlatform/v15/.suo index 33b4fe5957fa362e7d1f2652476223d6f90bb189..6883bb021f0aaad24a441853130109cd02a171a9 100644 GIT binary patch delta 1562 zcmb7^e@v8h9LJyU_nv!phX=xW9iWFi2*SmMoqEcoA0u->WqB~yY|BlYQEN~^V4@Yo zmTh9m-KPdC0{Tj~u&v?q{R8Kkf$QgLg-bUg7yQ9i%(if7KU_fXFWnVuy6U^V_IZCk zpYM<7c|Si|$Mn`Qy(37$z0QnOO-t4^%?9Qd78VdR2J%i;uHsJf(n0U;Q(v=nvdL&? zQQg}bSS5!4~iQY~{_Kmtaoc7swu2m*H6K^*lszzcY%zGhHnQJ9Aa`vQb zN^NIW3gmOL$<@w;n_KP*E-TiZVpay{l6zL`(p-6-8#UPMv`1d^88*vnaug?-F!iYK zigDlHt;o)h3VE?k5ZqW zk*=02Ty$~KrY}2<&q94IPOYD+d+VvCdyK^v)#z*r)y?`@&tmq~+Xmh7jh}YBafS^p zR(fNl@?=jEXNiR&=9DwSZ}Zk-zr;8UVS;Uoi)3%C+D4ze+uS)EU^(*j_i7l0XR|2F zx;yQgGwcoaHA)?0g7TCw^E#78-FH%(mKAx`L&BMNBl=VY4b^~+=9^^E%Zk}3JS z0?7s!7>)drP51wLi1t`64{lLYFR^;b)!#)8>U*n5{Kn+>GE=9jx-Qvu_%S;*%#_N~ zGs8A$v`RswAFFkAn SqpgpIhr&s0FgcdT9De}L4oTGj delta 1877 zcmc(gZA@EL7=U|DZ%YdWhb`S0AG>8^7P{5~<)f(BA|PY_pb43AF2-$)Il{m}iyy$~ zjETvZO8aIRbB^HI2E;9+^yv6uWQ#G2KiF6195IVlv&3oGA2XB1WYqW63qivo|2)Za z-;Z<7d(L~_bMFn#O2Jua&}pG6rL9t_)}X3J=Hv0W@CrR|(w9Z`gN0R=nW-D3oCmZ6 zBrIi@redEF(IHwyMjk`-ND6}CRM`lIia$h5sF%QGiZSO2#VdN!P=9KPZ4ddfNJq%G zy40n3%a-(t83weURD2mI-HKj`G6#Ou*)pG3JoPBID7u)L33h#n)P^2t);HygB>hTv zCrV+97C^ll*@qkq`PPSNo8EJwvGOXWZ{q=q>FZ1+3-?H5@mL@78T-?f01`@(-#WyM zh=5L%;zTTn9odM86WM^W01*r4qU=Qqk+n!ZvI%h@;v{kq5vvGgF;c47R+JT(&@IWU z+pPFN*1WFCM56_cXv$r0D(;1xM3}eG_cqdlh~@iGioE-gAQDaXQ*wX@Rt=D78me#b zfMI~J$7$GH&+FIDs=2)8XToXj?^OE&j=yK-f67`lvr0HdFIIR5y#si9IfqesANc_J z5E(>5NEn&tn;WJyQ4gh@n&(NV$;DmH8N#w@)Fo3c=YC7IyK&szM0mn#D0`Z+JQ}si z^rT#kBMM_A+^~Th0!xF9H#S5tn3z}-Bhf~oY>jEyE0pb|Y&&Jml+{t@*RpyVZ6y?5 z@)p2IzX2jW-+`g$KHEmc*j^m2W!)xrfQAn}OwSrmb{-FgTh5%hbR_#)_h%|BH@6J@ zNLf%~VVZIe%Pii5WYtNS9C9Qzc6PMx-sOjp6E5((j0L^DJ#MGBth&ToZhx-GTVi)P zoo>6gxT3;dTU=LH;jVR;)p?zL!#I0Cw0|^5PJ!igC-e>7B9Vk|=D3H9CbZjQY4Gfr z739+s{ELrYCS=(qH?4HZWL3?w0iIcD0Hgo4y7NKx?>{Ss^W5;<-ylQ+W;WxNlf@qhCo8Fz%KxfRuJ<}J}kE$bJ z9pS4xR&M=zmaxN=eM3b_?=F^fp(u{!PujZDgMtgg)63MgoL!^IMMcf!&2IOOzDLw} zaSzh}Uat%__$If()pMy(dBV!KpW9E^ZLGr|mo4H;y3k(4e@ZnP21NWqMDxS-V>Za- zzrgjMY~1$!O#;4%@gL loopBody -> confirmOrderWasPartiallyExecuted -> Cannot be confirmed that a partially execution was done well."); callBack(global.DEFAULT_FAIL_RESPONSE); @@ -247,21 +252,15 @@ */ - context.executionContext.positions[i].amountA = exchangePositions[j].amountA; - context.executionContext.positions[i].amountB = exchangePositions[j].amountB; - context.executionContext.positions[i].date = (new Date(exchangePositions[j].date)).valueOf(); + position.amountA = exchangePositions[j].amountA; + position.amountB = exchangePositions[j].amountB; + position.date = (new Date(exchangePositions[j].date)).valueOf(); - for (k = 0; k < pTrades.length; k++) { - - context.executionContext.positions[i].pTrades.push(pTrades[k]); - - context.newHistoryRecord.newTrades++; - - } + applyTradesToContext(pTrades); let newTransaction = { - type: context.executionContext.positions[i].type + " partially executed", - position: context.executionContext.positions[i] + type: position.type + " partially executed", + position: position }; context.executionContext.transactions.push(newTransaction); @@ -282,7 +281,7 @@ /* Position not found: we need to know if the order was executed. */ - getPositionTradesAtExchange(context.executionContext.positions[i].id, confirmOrderWasExecuted); + getPositionTradesAtExchange(position.id, confirmOrderWasExecuted); function getPositionTradesAtExchange(pPositionId, innerCallBack) { @@ -362,8 +361,8 @@ sumAssetA = sumAssetA + exchangePositions[j].fee; if ( - context.executionContext.positions[i].amountA !== sumAssetA || - context.executionContext.positions[i].amountB !== sumAssetB + position.amountA !== sumAssetA || + position.amountB !== sumAssetB ) { logger.write("[ERROR] ordersExecutionCheck -> loopBody -> confirmOrderWasExecuted -> Cannot be confirmed that the order was executed. It must be manually cancelled by the user or cancelled by the exchange itself."); callBack(global.DEFAULT_FAIL_RESPONSE); @@ -376,19 +375,13 @@ */ - context.executionContext.positions[i].status = "executed"; + position.status = "executed"; - for (k = 0; k < pTrades.length; k++) { - - context.executionContext.positions[i].pTrades.push(pTrades[k]); - - context.newHistoryRecord.newTrades++; - - } + applyTradesToContext(pTrades); let newTransaction = { - type: context.executionContext.positions[i].type + " executed", - position: context.executionContext.positions[i] + type: position.type + " executed", + position: position }; context.executionContext.transactions.push(newTransaction); @@ -436,13 +429,54 @@ callBack(global.DEFAULT_OK_RESPONSE); } + + function applyTradesToContext(pTrades) { + + for (k = 0; k < pTrades.length; k++) { + + position.pTrades.push(pTrades[k]); + + context.newHistoryRecord.newTrades++; + + /* Calculate Balances */ + + if (trade.type === 'buy') { + + context.executionContext.balance.assetA = context.executionContext.balance.assetA - trade.amountA; + context.executionContext.balance.assetB = context.executionContext.balance.assetB + trade.amountB; + + context.executionContext.availableBalance.assetB = context.executionContext.availableBalance.assetB + trade.amountB; + } + + if (trade.type === 'sell') { + + context.executionContext.balance.assetA = context.executionContext.balance.assetA + trade.amountA; + context.executionContext.balance.assetB = context.executionContext.balance.assetB - trade.amountB; + + context.executionContext.availableBalance.assetA = context.executionContext.availableBalance.assetA + trade.amountA; + } + } + + /* Calculate Profits */ + + if (context.executionContext.investment.assetA > 0) { + + context.executionContext.profits.assetA = (context.executionContext.balance.assetA - context.executionContext.investment.assetA) / context.executionContext.investment.assetA; + } + + if (context.executionContext.investment.assetB > 0) { + + context.executionContext.profits.assetB = (context.executionContext.balance.assetB - context.executionContext.investment.assetB) / context.executionContext.investment.assetB; + } + } + } catch (err) { logger.write("[ERROR] ordersExecutionCheck -> err = " + err.message); callBack(global.DEFAULT_FAIL_RESPONSE); } } - function putPosition(pType, pRate, pAmountA, pAmountB, callBack) { + function putPosition(pType, pRate, pAmountA, pAmountB, callBackFunction) { try { if (FULL_LOG === true) { logger.write("[INFO] putPosition -> Entering function."); } @@ -451,6 +485,62 @@ if (FULL_LOG === true) { logger.write("[INFO] putPosition -> pAmountA = " + pAmountA); } if (FULL_LOG === true) { logger.write("[INFO] putPosition -> pAmountB = " + pAmountB); } + /* Validations that the limits are not surpassed. */ + + if (pType === 'buy') { + + if (pAmountA > context.executionContext.availableBalance.assetA) { + + logger.write("[ERROR] putPosition -> Input Validations -> pAmountA is grater than the Available Balance."); + logger.write("[ERROR] putPosition -> Input Validations -> pAmountA = " + pAmountA); + logger.write("[ERROR] putPosition -> Input Validations -> Available Balance = " + context.executionContext.availableBalance.assetA); + + let err = { + result: global.DEFAULT_FAIL_RESPONSE.result; + message: 'pAmountA is grater than the Available Balance.' + }; + + callBackFunction(err); + return; + } + } + + if (pType === 'sell') { + + if (pAmountB > context.executionContext.availableBalance.assetB) { + + logger.write("[ERROR] putPosition -> Input Validations -> pAmountB is grater than the Available Balance."); + logger.write("[ERROR] putPosition -> Input Validations -> pAmountB = " + pAmountB); + logger.write("[ERROR] putPosition -> Input Validations -> Available Balance = " + context.executionContext.availableBalance.assetB); + + let err = { + result: global.DEFAULT_FAIL_RESPONSE.result; + message: 'pAmountB is grater than the Available Balance.' + }; + + callBackFunction(err); + return; + } + } + + if (pAmountB * pRate !== pAmountA) { + + logger.write("[ERROR] putPosition -> Input Validations -> pAmountB * pRate !== pAmountA."); + logger.write("[ERROR] putPosition -> Input Validations -> pAmountB = " + pAmountA); + logger.write("[ERROR] putPosition -> Input Validations -> pAmountB = " + pAmountB); + logger.write("[ERROR] putPosition -> Input Validations -> pAmountB = " + pRate); + + let err = { + result: global.DEFAULT_FAIL_RESPONSE.result; + message: 'pAmountB * pRate !== pAmountA' + }; + + callBackFunction(err); + return; + } + + /* All validations passed, we proceed. */ + exchangeAPI.putPosition(global.MARKET, pType, pRate, pAmountA, pAmountB, onResponse); function onResponse(err, pPositionId) { @@ -484,37 +574,50 @@ context.executionContext.transactions.push(newTransaction); context.newHistoryRecord.newPositions++; - context.newHistoryRecord.rate = pRate; - callBack(global.DEFAULT_OK_RESPONSE); + recalculateRateAverages(); + + /* Recalculate Available Balances */ + + if (position.type === 'buy') { + + context.executionContext.availableBalance.assetA = context.executionContext.availableBalance.assetA - trade.amountA; + } + + if (position.type === 'sell') { + + context.executionContext.availableBalance.assetB = context.executionContext.availableBalance.assetB - trade.amountB; + } + + callBackFunction(global.DEFAULT_OK_RESPONSE); return; } break; case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. logger.write("[ERROR] putPosition -> onResponse -> Retry Later. Requesting Execution Retry."); - callBack(global.DEFAULT_RETRY_RESPONSE); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); return; } break; case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. logger.write("[ERROR] putPosition -> onResponse -> Operation Failed. Aborting the process."); - callBack(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); return; } break; } } catch (err) { logger.write("[ERROR] putPosition -> onResponse -> err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { logger.write("[ERROR] putPosition -> err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } - function movePosition(pPosition, pNewRate, callBack) { + function movePosition(pPosition, pNewRate, callBackFunction) { try { if (FULL_LOG === true) { logger.write("[INFO] movePosition -> Entering function."); } @@ -550,9 +653,9 @@ for (let i = 0; i < context.executionContext.positions.length; i++) { - if (context.executionContext.positions[i].id === pPosition.id) { + if (position.id === pPosition.id) { - context.executionContext.positions[i] = newPosition; + position = newPosition; break; } @@ -567,36 +670,88 @@ context.executionContext.transactions.push(newTransaction); context.newHistoryRecord.movedPositions++; - context.newHistoryRecord.rate = pNewRate; - callBack(global.DEFAULT_OK_RESPONSE); + recalculateRateAverages(); + + callBackFunction(global.DEFAULT_OK_RESPONSE); } break; case global.DEFAULT_RETRY_RESPONSE.result: { // Something bad happened, but if we retry in a while it might go through the next time. logger.write("[ERROR] movePosition -> onResponse -> Retry Later. Requesting Execution Retry."); - callBack(global.DEFAULT_RETRY_RESPONSE); + callBackFunction(global.DEFAULT_RETRY_RESPONSE); return; } break; case global.DEFAULT_FAIL_RESPONSE.result: { // This is an unexpected exception that we do not know how to handle. logger.write("[ERROR] movePosition -> onResponse -> Operation Failed. Aborting the process."); - callBack(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); return; } break; } } catch (err) { logger.write("[ERROR] movePosition -> onResponse -> err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } } catch (err) { logger.write("[ERROR] movePosition -> err = " + err.message); - callBack(global.DEFAULT_FAIL_RESPONSE); + callBackFunction(global.DEFAULT_FAIL_RESPONSE); } } + function recalculateRateAverages() { + + /* This function calculates the weighted rates of buy and sell positions. This is a way to summarize with only one rate, where the positions are. */ + + let sumBuyWeightedRates = 0; + let sumSellWeightedRates = 0; + + let sumBuyWeights = 0; + let sumSellWeights = 0; + + for (let i = 0; i < context.executionContext.positions.length; i++) { + + let position = context.executionContext.positions[i]; + + if (position.type === 'buy') { + + sumBuyWeightedRates = sumBuyWeightedRates + position.rate * position.amountB + sumBuyWeights = sumBuyWeights + position.amountB; + + } + + if (position.type === 'sell') { + + sumSellWeightedRates = sumSellWeightedRates + position.rate * position.amountB + sumSellWeights = sumSellWeights + position.amountB; + + } + + } + + context.newHistoryRecord.buyAvgRate = sumBuyWeightedRates / sumBuyWeights; + context.newHistoryRecord.sellAvgRate = sumSellWeightedRates / sumSellWeights; + } + + function getPositions() { return JSON.parse(JSON.stringify(context.executionContext.positions)); } + + function getBalance() { + return JSON.parse(JSON.stringify(context.executionContext.balance)); + } + + function getAvailableBalance() { + return JSON.parse(JSON.stringify(context.executionContext.availableBalance)); + } + + function getInvestment() { + return JSON.parse(JSON.stringify(context.executionContext.investment)); + } + + function getProfits() { + return JSON.parse(JSON.stringify(context.executionContext.profits)); + } }; \ No newline at end of file diff --git a/Context.js b/Context.js index cbad33bed..6902c5669 100644 --- a/Context.js +++ b/Context.js @@ -38,10 +38,15 @@ executionContext: undefined, // Here is the business information of the last execution of this bot process. newHistoryRecord : { date: undefined, - rate: 0, // This will be used to know where to plot this information in the time line. + buyAvgRate: 0, + sellAvgRate: 0, newPositions: 0, newTrades: 0, - movedPositions: 0 + movedPositions: 0, + profitsAssetA: 0, + profitsAssetB: 0, + profitsCombinedInA: 0, + profitsCombinedInB: 0 }, initialize: initialize, saveThemAll: saveThemAll @@ -316,11 +321,20 @@ thisObject.executionHistory = []; thisObject.executionContext = { - investment: { + investment: { // This is used to calculate profits. assetA: 0, - assetB: 0 + assetB: global.INITIAL_INVESTMENT }, - availableBalance: { + balance: { // This is the total balance that includes positions at the order book + funds available to be traded. + assetA: 0, + assetB: global.INITIAL_INVESTMENT // It starts with the initial investment. + }, + availableBalance: { // This is the balance the bot has at any moment in time available to be traded (not in positions at the order book). + assetA: 0, + assetB: global.INITIAL_INVESTMENT // It starts with the initial investment. + }, + profitsAsset: global.PROFIT_BASE_ASSET, + profits: { assetA: 0, assetB: 0 }, @@ -467,10 +481,16 @@ let newRecord = [ thisObject.newHistoryRecord.date.valueOf(), - thisObject.newHistoryRecord.rate, + thisObject.newHistoryRecord.buyAvgRate, + thisObject.newHistoryRecord.sellAvgRate, + thisObject.newHistoryRecord.marketRate, thisObject.newHistoryRecord.newPositions, thisObject.newHistoryRecord.newTrades, - thisObject.newHistoryRecord.movedPositions + thisObject.newHistoryRecord.movedPositions, + thisObject.newHistoryRecord.profitsAssetA, + thisObject.newHistoryRecord.profitsAssetB, + thisObject.newHistoryRecord.profitsCombinedInA, + thisObject.newHistoryRecord.profitsCombinedInB ]; thisObject.executionHistory.push(newRecord); diff --git a/Run.js b/Run.js index a447366de..27a98fa55 100644 --- a/Run.js +++ b/Run.js @@ -127,6 +127,16 @@ global.dailyFilePeriods = global.dailyFilePeriods = JSON.parse(global.dailyFilePeriods); +/* +In the current release, the platform is going to be used for the first competition, so we have a few parameters just for that. They will be removed later +once we advance into multiple competitions scheme. +*/ + +global.INITIAL_INVESTMENT = 0.0001; +global.PROFIT_BASE_ASSET = 'BTC'; + +/* Some LAGACY code starts here. Pending for clean up. */ + const START_ALL_MONTHS = false; diff --git a/this.vm.config.json b/this.vm.config.json index aa2288eab..1a59ed415 100644 --- a/this.vm.config.json +++ b/this.vm.config.json @@ -2,5 +2,5 @@ "bot": { "path": "../Bots/AAMasters/AAMariam-Trading-Bot" }, - "stopGracefully": "false" + "stopGracefully": "true" } \ No newline at end of file