Created
June 17, 2018 03:00
-
-
Save snowkidind/47fa07c7a2145161554c7997ff625ecd to your computer and use it in GitHub Desktop.
Binance structural idea for trading bot asset management, in progress
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let db = require('./dbaccess'); | |
let utilities = require('./utilities'); | |
let binance = require('./binance'); | |
let chalk = require('chalk'); | |
// manage tokens | |
// manage balances | |
// delegate profits, fees and taxes | |
// the token cycle is the process of buying, selling and delegating the costs associated | |
// with the trade in order to 1. identify what state the application is in, or 2. to have | |
// separate control of token balances so that things can be systematically be set aside | |
// for operating costs such as fees, taxes and profits | |
let dOptions; | |
let tokenObj = {}; | |
let balancesObject = {}; | |
let transactionId; | |
let closeTxData; | |
let initTxData; | |
module.exports = { | |
initToken:function(daemonOptions, ready){ | |
dOptions = daemonOptions; | |
// load token from database or if doesn't exist, initialize token object | |
db.getToken(function(token){ | |
if (0){ // some of this code is still experimental. after all its a gist... | |
// console.log("token exists in db"); | |
tokenObj = token; | |
// synchronize balances with asset manager | |
ready(true); | |
} | |
else { | |
// console.log("generating new token object"); | |
generateToken(function(tokenObj){ | |
db.saveToken(tokenObj); | |
ready(true); | |
}); | |
} | |
}); | |
}, | |
// update token object balances | |
updateCycleState:function(stream){ | |
// poll tether and usdt for balances | |
const tetherIndex = stream.B.map(function (x) { | |
return x.a; | |
}).indexOf('USDT'); | |
const btcIndex = stream.B.map(function (x) { | |
return x.a; | |
}).indexOf('BTC'); | |
let tetherBalance = stream.B[tetherIndex].f; | |
let btcBalance = stream.B[btcIndex].f; | |
const serverBalances = { tether:tetherBalance, btc: btcBalance }; | |
recalculateBalances(serverBalances, null, function(){ | |
updateTokenBalances(stream); | |
}); | |
}, | |
// initialize a transaction with the parent amount and the fee... | |
initializeTransaction:function(initializeTransactionData){ | |
initTxData = initializeTransactionData; // store the payload | |
// just init a bare bones tx bc it will get filled in by the actual cycle state update | |
db.initializeTransaction(initializeTransactionData.pair, function(txId){ | |
// gather an id in order to access this tx later. | |
transactionId = txId; | |
}); | |
}, | |
// should never be called before a transaction is initialized | |
openTransaction:function(buyData){ | |
// object Model | |
// let buy = { | |
// "symbol":"BTCUSDT", | |
// "orderId":119571607, | |
// "clientOrderId": | |
// "7rUtKmq4Ceh6HbgXuVuN9O", | |
// "transactTime":1529154731529, | |
// "price":"0.00000000", | |
// "origQty":"0.00356000", | |
// "executedQty":"0.00356000", | |
// "status":"FILLED", | |
// "timeInForce":"GTC", | |
// "type":"MARKET", | |
// "side":"BUY" | |
// }; | |
// looking for a transaction object. transactionId should be set at this point | |
findTransaction(function(transaction) { | |
// stage two is to open the transaction object on balance update | |
if (dOptions.tradeToAltcoins){ | |
// TODO code in openTransaction for altcoins... | |
console.log("you need to code in openTransaction for altcoins") | |
} | |
else { | |
db.openTransaction(transaction.id, buyData.executedQty, initTxData.parentAmt, initTxData.last, initTxData.feeBuy, function(results){ | |
transactionId = transaction.id; | |
}); | |
} | |
}); | |
}, | |
closeTransaction:function(sellData){ | |
// let sell = { | |
// "symbol":"BTCUSDT", | |
// "orderId":119572142, | |
// "clientOrderId":"OMYpzj08N68Es2HynYVF1D", | |
// "transactTime":1529154759758, | |
// "price":"0.00000000", | |
// "origQty":"0.00356000", | |
// "executedQty":"0.00356000", | |
// "status":"FILLED", | |
// "timeInForce":"GTC", | |
// "type":"MARKET", | |
// "side":"SELL" | |
// }; | |
closeTxData = { pair: sellData.symbol,qty:sellData.executedQty }; | |
findTransaction(function(transaction) { | |
transaction.isClosed = 1; | |
// first close the transaction object | |
closeTransactionObject(transaction, function (closedTransaction) { | |
generateToken(function(tokenObj){ | |
db.saveToken(tokenObj); | |
}); | |
}); | |
}); | |
// Wondering if this will come about... | |
if (sellData.status !== "FILLED") { | |
console.log(chalk.bgMagenta(" Status was not filled ")); | |
console.log(chalk.magenta(JSON.stringify(sellData))); | |
} | |
}, | |
hasOpenTransactions: function(){ | |
findTransaction(function(transaction){ | |
if (transaction){ | |
if (transaction.isClosed === 0){ | |
return true; | |
} | |
} | |
else { | |
return false; | |
} | |
}); | |
}, | |
balances:function(){ | |
return balancesObject; | |
}, | |
token:function(){ | |
// returns the token object for other modules to call upon | |
return tokenObj; | |
}, | |
}; | |
function findTransaction(callback){ | |
// if have the transactionid query database by id else poll db for open tx object | |
if (transactionId > 0) { | |
db.getTransaction(transactionId, function(transactionObject){ | |
callback(transactionObject); | |
}); | |
} | |
// else determine if there is no open tx object | |
else { | |
db.getOpenTransactionObject(function(transactionObject){ | |
if (transactionObject){ | |
callback(transactionObject); | |
} | |
else { | |
// if fails, unable to find an open transaction, return a null transaction to indicate outness. | |
console.log("Unable to find a transaction"); | |
callback(null); | |
} | |
}); | |
} | |
} | |
function closeTransactionObject(transaction, callback){ | |
let isClosed = 1; | |
// will cll this again in just a minute | |
db.closeTransaction(transaction.id, isClosed, closeTxData.qty, 0, 0, 0, 0, 0, 0, 0, 0, 0, function(result){ | |
if (result){ | |
// concern here is that binance only sold a portion of the total | |
// amount and multiple cycleStates will be called until order is fulfilled.. Not sure... | |
// objective here is to add the rest of the fields to the transaction object, update the database. | |
callback(transaction); // closed transaction | |
} | |
else { | |
callback(transaction); // send it anyway | |
console.log("there was an error at db.closeTransactionObject"); | |
} | |
}); | |
} | |
function updateTokenBalances(stream){ | |
const tetherIndex = stream.B.map(function (x) { | |
return x.a; | |
}).indexOf('USDT'); | |
tokenObj.onOrder = stream.B[tetherIndex].l; | |
tokenObj.balance = balancesObject.usdt.availableBalance; | |
const btcIndex = stream.B.map(function (x) { | |
return x.a; | |
}).indexOf('BTC'); | |
tokenObj.tokens[0].onOrder = stream.B[btcIndex].l; | |
// console.log("before..."); | |
// console.log(balancesObject); | |
// console.log("setting btc balance (stream) to: " + balancesObject.btc.availableBalance); | |
tokenObj.tokens[0].balance = balancesObject.btc.availableBalance; | |
// iterate tether tradeable tokens and load into token database | |
for (let i = 0; i < dOptions.tokens.tokens.length; i++) { | |
// altcoin balance will always be altcoin balance | |
const altIndex = stream.B.map(function (x) { | |
return x.a; | |
}).indexOf(dOptions.altToken); | |
// need to iterate alts now | |
for (let j = 0; j < tokenObj.tokens[i].tokens.length; j++) { | |
// Altcoin balances are allowed to pass through directly | |
tokenObj.tokens[i].tokens[j].balance = stream.B[altIndex].f; | |
tokenObj.tokens[i].tokens[j].onOrder = stream.B[altIndex].l; | |
} | |
} | |
} | |
// take raw server balances and account for holds and apply to balances object | |
function recalculateBalances(serverBalances, transaction, callback){ | |
console.log(chalk.yellow("recalculateBalances", JSON.stringify(serverBalances), JSON.stringify(transaction) )); | |
// prototype the object,trash everything in the balances object in memory | |
balancesObject = { | |
usdt:{ | |
profitsTaken:0, | |
taxesTaken:0, | |
feesTaken:0, | |
totalReserved:0, | |
availableBalance:0 | |
} , | |
btc:{ | |
profitsTaken:0, | |
taxesTaken:0, | |
feesTaken:0, | |
totalReserved:0, | |
availableBalance:0 | |
} | |
}; | |
db.getBalanceObject(function(oldBalances){ | |
// the balances object is stored in the database and contains | |
// the retained balances for the application | |
// the payout is as follows: | |
// fee is 1% (two half percent transactions) | |
// tax is 30% after fee | |
// profit deduction is 29% | |
// the rest of gains 40% are reinvested | |
if (oldBalances){ | |
// determine tether balance | |
let totalReservedUSDT = Number(oldBalances.usdt.profitsTaken) + Number(oldBalances.usdt.taxesTaken) + Number(oldBalances.usdt.feesTaken); | |
let availableBalanceUSDT = Number(serverBalances.tether) - Number(totalReservedUSDT); | |
balancesObject.usdt.profitsTaken = oldBalances.usdt.profitsTaken; | |
balancesObject.usdt.taxesTaken = oldBalances.usdt.taxesTaken; | |
balancesObject.usdt.feesTaken = oldBalances.usdt.feesTaken; | |
balancesObject.usdt.totalReserved = totalReservedUSDT; | |
balancesObject.usdt.availableBalance = availableBalanceUSDT; | |
// determine btc balance | |
balancesObject.btc.totalReserved = Number(oldBalances.btc.profitsTaken) + Number(oldBalances.btc.taxesTaken) + Number(oldBalances.btc.feesTaken); | |
balancesObject.btc.availableBalance = Number(serverBalances.btc) - Number(balancesObject.btc.totalReserved); | |
balancesObject.btc.profitsTaken = oldBalances.btc.profitsTaken; | |
balancesObject.btc.taxesTaken = oldBalances.btc.taxesTaken; | |
balancesObject.btc.feesTaken = oldBalances.btc.feesTaken; | |
// if transaction object is null, it means that there are no open transactions, which implicates all in tether or btc | |
if (transaction !== null){ | |
// meaning we are in to an alt / btc | |
if (transaction.isClosed === 0){ | |
// console.log("transaction is not closed"); | |
} | |
// meaning we are back out to btc / usdt | |
else if (transaction.isClosed === 1){ | |
console.log("Pair", transaction.pair); | |
// find out how much in usd the token happens to be worth | |
binance.latestPrice(transaction.pair, function(last){ | |
let valueParentOut = last * closeTxData.qty; | |
// console.log("calculating closed transaction..."); | |
// console.log("Old in the new"); | |
// console.log(serverBalances.tether, balancesObject.usdt.totalReserved) | |
// console.log("tether new: " + (serverBalances.tether - balancesObject.usdt.totalReserved)); // nan | |
// console.log("tether old: " + oldBalances.usdt.availableBalance); // ok | |
// console.log("btc new: " + (serverBalances.btc - balancesObject.btc.totalReserved)); // nan | |
// console.log("btc old: " + oldBalances.btc.availableBalance); | |
// let valueParentOut = serverBalances.tether - balancesObject.usdt.totalReserved - oldBalances.usdt.availableBalance; | |
console.log("valueParentOut", valueParentOut); | |
initTxData = {}; | |
// if the tx is closed, balance will be either in btc or usdt (not in alts) | |
// need to get the difference - in bitcoin of what the transaction yielded | |
// calculate differences in the balances | |
const fees = valueParentOut * 0.0005; // 5 100ths of a percent for now | |
const totalGain = valueParentOut - transaction.valueParentIn - fees - transaction.feeBuy; | |
const taxWitheld = totalGain * 0.25; // 25 percent alloc for tax on gains | |
const profitTook = totalGain * 0.30; | |
const witholding = fees + transaction.feeBuy + taxWitheld + profitTook ; | |
// tax form 8949 fields | |
const netProceeds = valueParentOut - fees; //out price - fee2 | |
const costBasis = transaction.valueParentIn + transaction.feeBuy; // inPrice + fee1 | |
const gain = netProceeds - costBasis; | |
// new witholds only get added to current parent token | |
if (dOptions.tradeToAltcoins){ | |
// all of this should be converted to dollars at the opportune moment | |
if (Math.sign(totalGain) === 1){ | |
balancesObject.btc.profitsTaken += profitTook; | |
} | |
balancesObject.btc.taxesTaken += taxWitheld; | |
balancesObject.btc.feesTaken += fees + transaction.feeBuy; | |
balancesObject.btc.totalReserved += witholding; | |
balancesObject.btc.availableBalance = serverBalances.btc - balancesObject.btc.totalReserved; | |
balancesObject.usdt.availableBalance = serverBalances.usdt - balancesObject.usdt.totalReserved; | |
} | |
else { | |
// only withold profit if winning transaction | |
if (Math.sign(totalGain) === 1){ | |
balancesObject.usdt.profitsTaken += profitTook; | |
} | |
// fees are always taken | |
balancesObject.usdt.feesTaken += fees + transaction.feeBuy; | |
// redeem tax losses if negative | |
balancesObject.usdt.taxesTaken += taxWitheld; | |
balancesObject.usdt.totalReserved += witholding; | |
balancesObject.btc.availableBalance = serverBalances.btc - balancesObject.btc.totalReserved; | |
balancesObject.usdt.availableBalance = serverBalances.tether - balancesObject.usdt.totalReserved; | |
} | |
console.log("Synchronize: ", transaction.id, 1, closeTxData.qty, valueParentOut, last, fees, totalGain, profitTook, taxWitheld); | |
db.saveBalanceObject(balancesObject); | |
// now synchronize the calculations with the db | |
db.closeTransaction(transaction.id, 1, closeTxData.qty, valueParentOut, last, fees, totalGain, profitTook, taxWitheld, netProceeds, costBasis, gain, function(){}); | |
}); | |
} | |
} | |
else { | |
// there is no transaction object. therefore what is, is what is | |
// so if you bought something with the bitcoin it would not really know about it | |
// if the app bought something and you sold it manually, the app would detect the open tx obj | |
// lets just get it working and consider contingencies later... | |
} | |
} | |
else { | |
// need to generate new balances object because FNF in database... | |
// basically put a null balances object in... | |
console.log("Warning - balancesObject Not Found. Creating new zeroed balances object..."); | |
// save new balances object to database | |
db.saveBalanceObject(balancesObject); | |
} | |
callback(); | |
}); | |
// TODO: automate moving witholds to appropriate positions | |
// if balance on exchange left over is movable to a better hiding spot, execute: | |
// fee coverage to bnb | |
// profits to alternate stable coin or long position (ADA) | |
} | |
// scans a freshly updated token object and balances object to determine | |
// if there is a transaction that is unaccounted for | |
function reconcileUnknownTransaction(){ | |
// console.log("Reconciling unknown transaction"); | |
// console.log("tradeAlts: ", dOptions.tradeToAltcoins); | |
// console.log(JSON.stringify(tokenObj)); | |
if (dOptions.tradeToAltcoins){ | |
// TODO code in reconcileUnknownTransaction for altcoins... | |
console.log("you need to code in reconcileUnknownTransaction for altcoins") | |
} | |
else { | |
// trading in btcusdt | |
//console.log("btc balance", tokenObj.tokens[0].balance); | |
//console.log("btc orders", tokenObj.tokens[0].onOrder); | |
//console.log("btc balance", tokenObj.tokens[0].minimumQuantity); | |
let totalBtc = Number(tokenObj.tokens[0].balance) + Number(tokenObj.tokens[0].onOrder); | |
let releasedBtc = totalBtc - Number(balancesObject.btc.totalReserved); | |
if (releasedBtc > tokenObj.tokens[0].minimumQuantity){ | |
// generate a new transaction object | |
db.initializeTransaction('BTCUSD', function(txId){ | |
// TODO valueParentIn, feeBuy | |
db.openTransaction(txId, releasedBtc, 0, 0, function(results){ | |
transactionId = txId; | |
console.log("added transaction buy " + releasedBtc + " for ??? usdt"); | |
}); | |
}); | |
} | |
else { | |
console.log("btc balance does not suggest you are holding btc"); | |
} | |
} | |
} | |
// needs to happen after the balances object is verified with the asset manager | |
function generateToken(cb){ | |
console.log("generate token"); | |
let theToken = dOptions.tokens; | |
// this is where the asset manager will assume control of the tokens. | |
binance.allBalances(function (balances) { | |
let tether = dOptions.tokens.token; | |
let tetherBalance = balances[tether].available; | |
let btcBalance = balances['BTC'].available; | |
const serverBalances = { tether:tetherBalance, btc: btcBalance }; | |
findTransaction(function(transaction){ | |
console.log("ServerBals in generateToken: ", serverBalances); | |
// console.log("Transaction: ", transaction); | |
// console.log("serverBalances: " + JSON.stringify(serverBalances)); | |
// will send a null transaction to recalc because it will know what to do | |
recalculateBalances(serverBalances, transaction, function(){ | |
// console.log(balancesObject); | |
binance.exchangeinfo(function(response) { | |
binance.getAllOpenOrders(function (orders) { | |
theToken.balance = balancesObject.usdt.availableBalance; | |
theToken.onOrder = balances[tether].onOrder; | |
theToken.minimumQuantity = 1; | |
theToken.quantityDecimals = 1; | |
theToken.priceDecimals = 2; | |
// iterate tether market pairs, e.g. btcusdt, bnbusdt | |
for (let i = 0; i < theToken.tokens.length; i++) { | |
let parent = theToken.tokens[i].token; | |
let thisPair = theToken.tokens[i].token + tether; | |
theToken.tokens[i].pair = thisPair; | |
theToken.tokens[i].parent = tether; | |
theToken.tokens[i].onOrder = balances[parent].onOrder; | |
if (theToken.tokens[i].token === 'btc'){ | |
// console.log("setting btc balance (rest) to: " + balancesObject.btc.availableBalance); | |
theToken.tokens[i].balance = balancesObject.btc.availableBalance | |
} | |
else { | |
theToken.tokens[i].balance = balances[parent].available; // old way for non btc tokens | |
} | |
// poll exchange info for token specs | |
const elementPos = response.symbols.map(function (x) { | |
return x.symbol; | |
}).indexOf(thisPair); | |
const exchangeData = response.symbols[elementPos]; | |
// add btc token specs to token object. The specs come in a "filters" object | |
for (let k = 0; k < exchangeData.filters.length - 1; k++) { | |
if (exchangeData.filters[k].filterType === 'LOT_SIZE') { | |
const minQ = exchangeData.filters[k].stepSize; | |
const stepSize = utilities.decimalPlaces(exchangeData.filters[k].stepSize); | |
theToken.tokens[i].quantityDecimals = stepSize; | |
theToken.tokens[i].minimumQuantity = minQ; | |
} | |
if (exchangeData.filters[k].filterType === 'PRICE_FILTER') { | |
const minPrice = utilities.decimalPlaces(exchangeData.filters[k].minPrice); | |
theToken.tokens[i].priceDecimals = minPrice; | |
} | |
if (exchangeData.filters[k].filterType === 'MIN_NOTIONAL') { | |
const minNotional = utilities.decimalPlaces(exchangeData.filters[k].minNotional); | |
// console.log("Minimum Notional: " + minNotional) | |
// not sure why is not added, perhaps it was an earlier thing | |
} | |
} | |
// iterate child tokens of btc level tokens | |
for (let j = 0; j < theToken.tokens[i].tokens.length; j++) { | |
const alt = theToken.tokens[i].tokens[j]; | |
const altPair = alt.token + parent; | |
theToken.tokens[i].tokens[j].pair = altPair; | |
theToken.tokens[i].tokens[j].parent = parent; | |
// Altcoin balances are allowed to pass through directly | |
theToken.tokens[i].tokens[j].balance = balances[alt.token].available; | |
theToken.tokens[i].tokens[j].onOrder = balances[alt.token].onOrder; | |
// poll exchange info for token specs | |
const elementPos = response.symbols.map(function (x) { | |
return x.symbol; | |
}).indexOf(altPair); | |
const exchangeData = response.symbols[elementPos]; | |
// add alt token specs to token object. The specs come in a "filters" object | |
for (let l = 0; l < exchangeData.filters.length - 1; l++) { | |
if (exchangeData.filters[l].filterType === 'LOT_SIZE') { | |
const minQ = exchangeData.filters[l].stepSize; | |
const stepSize = utilities.decimalPlaces(exchangeData.filters[l].stepSize); | |
theToken.tokens[i].tokens[j].quantityDecimals = stepSize; | |
theToken.tokens[i].tokens[j].minimumQuantity = minQ; | |
} | |
if (exchangeData.filters[l].filterType === 'PRICE_FILTER') { | |
const minPrice = utilities.decimalPlaces(exchangeData.filters[l].minPrice); | |
theToken.tokens[i].tokens[j].priceDecimals = minPrice; | |
} | |
} | |
} | |
} | |
tokenObj = theToken; | |
cb(tokenObj); | |
if (transaction === null){ | |
// if null transaction found, need to reconcileUnknownTransaction(); | |
// but want a fresh balances and token object to perform | |
reconcileUnknownTransaction(); | |
} | |
}); | |
}); | |
}); | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment