diff --git a/.vs/AACloudPlatform/v15/.suo b/.vs/AACloudPlatform/v15/.suo index 33b4fe595..6883bb021 100644 Binary files a/.vs/AACloudPlatform/v15/.suo and b/.vs/AACloudPlatform/v15/.suo differ diff --git a/Assistant.js b/Assistant.js index eda75e11e..e50224ebf 100644 --- a/Assistant.js +++ b/Assistant.js @@ -14,7 +14,11 @@ initialize: initialize, putPosition: putPosition, movePosition: movePosition, - getPositions: getPositions + getPositions: getPositions, + getBalance: getBalance, + getAvailableBalance: getAvailableBalance, + getInvestment: getInvestment, + getProfits: getProfits }; let bot = BOT; @@ -29,8 +33,6 @@ let context; let exchangeAPI; - const INITIAL_INVESTMENT = 0.001; - return thisObject; function initialize(pContext, pExchangeAPI, callBackFunction) { @@ -178,9 +180,12 @@ function loopBody() { try { + + let position = context.executionContext.positions[i]; + for (let j = 0; j < exchangePositions.length; j++) { - if (context.executionContext.positions[i].id === exchangePositions[j].id) { + if (position.id === exchangePositions[j].id) { /* @@ -190,7 +195,7 @@ */ - if (context.executionContext.positions[i].amountB === parseFloat(exchangePositions[j].amountB)) { + if (position.amountB === parseFloat(exchangePositions[j].amountB)) { /* Position is still there, untouched. Nothing to do here. */ @@ -199,7 +204,7 @@ } else { - getPositionTradesAtExchange(context.executionContext.positions[i].id, confirmOrderWasPartiallyExecuted); + getPositionTradesAtExchange(position.id, confirmOrderWasPartiallyExecuted); return; function confirmOrderWasPartiallyExecuted(pTrades) { @@ -233,8 +238,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 -> 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