Created
April 7, 2021 11:42
-
-
Save dbose/22d0ae47e8d5ac7cf392c7237a99b424 to your computer and use it in GitHub Desktop.
DCA to BTC / ETH portfolio
This file contains hidden or 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
const BTCMarkets = require('btc-markets') | |
const async = require('async') | |
const _ = require('lodash') | |
const schedule = require('node-schedule') | |
const winston = require('winston') | |
const os = require('os') | |
const log = winston.createLogger({ | |
transports: [ | |
new (winston.transports.File)({ | |
// log which takes care of all the MAS scoring - node clusters and servers | |
filename: './dca.log', | |
level: 'info', | |
json: true, | |
eol: '\r\n' | |
}), | |
new winston.transports.Console() | |
] | |
}) | |
// load public and private keys from a json file | |
const KEYS = require('./keys.json') | |
// the total amount I want to spend each DCA duration, ex 120 AUD | |
const DCA_SPEND = 120; | |
// 10:00 on day-of-month 1 - https://crontab.guru/#0_10_1_*_* | |
// Monthly: "0 10 1 * *" | |
// Weekly: 0 10 * * 1 | |
// 2 Minutely: */2 * * * * | |
// Daily: 0 10 * * * | |
const DCA_SCHEDULE = "0 11 * * 1"; | |
// the currency I want to buy in | |
const FIAT_CURR = "AUD" | |
// all values are in SATOSHIS | |
// https://en.bitcoin.it/wiki/Satoshi_(unit) | |
const SATOSHI = 100000000 | |
// Portfolio breakdown | |
// BTC Max | |
const TOKEN_PERCENT_BTCMAX = { | |
BTC: 1.0, // 100% | |
ETH: 0.0, // 0% | |
LINK: 0.0, // 0% | |
} | |
const TOKEN_PERCENT_ETHMAX = { | |
BTC: 0.0, // 0% | |
ETH: 1.0, // 100% | |
LINK: 0.0, // 0% | |
} | |
const TOKEN_PERCENT_ETH_LINK = { | |
BTC: 0.0, // 00% | |
ETH: 0.5, // 50% | |
LINK: 0.5, // 50% | |
} | |
const TRADING_FEE = 0.85; | |
var client = new BTCMarkets(KEYS.public, KEYS.secret) | |
var currentTokenPercent = TOKEN_PERCENT_BTCMAX; | |
/** | |
* [Return an object that contains all the last prices for each token] | |
*/ | |
function getTokenPrices(cb) { | |
async.parallel({ | |
BTC: function(callback) { | |
client.getTick('BTC', FIAT_CURR, function(e, res) { | |
callback(e, res.lastPrice) | |
}) | |
}, | |
ETH: function(callback) { | |
client.getTick('ETH', FIAT_CURR, function(e, res) { | |
callback(e, res.lastPrice) | |
}) | |
}, | |
LINK: function(callback) { | |
client.getTick('LINK', FIAT_CURR, function(e, res) { | |
callback(e, res.lastPrice) | |
}) | |
} | |
}, cb) | |
} | |
async function getAUDBalance() { | |
return new Promise((resolve, reject) => { | |
client.getAccountBalances(function(e, res) { | |
if (e) { | |
reject(e); | |
} | |
var aud_bal, btc_bal; | |
_.forIn(res, function(obj) { | |
if (obj.currency == 'AUD') { | |
resolve(obj.balance/SATOSHI); | |
} | |
}); | |
}); | |
}); | |
} | |
async function getBalance() { | |
return new Promise((resolve, reject) => { | |
client.getAccountBalances(function(e, res) { | |
if (e) { | |
reject(e); | |
} | |
var aud_bal = 0, btc_bal = 0, eth_bal = 0; | |
_.forIn(res, function(obj) { | |
if (obj.currency == 'AUD') { | |
aud_bal = obj.balance/SATOSHI; | |
} | |
if (obj.currency == 'BTC') { | |
btc_bal = obj.balance/SATOSHI; | |
} | |
if (obj.currency == 'ETH') { | |
eth_bal = obj.balance/SATOSHI; | |
} | |
}); | |
resolve({aud_bal, btc_bal, eth_bal}); | |
}); | |
}); | |
} | |
async function getPrice(coin) { | |
return new Promise((resolve, reject) => { | |
client.getTick(coin, FIAT_CURR, function(e, res) { | |
if (e) { | |
reject(e); | |
} | |
resolve(res.lastPrice); | |
}); | |
}); | |
} | |
/** | |
* [buyHandler Take the token prices and submit orders in proportion] | |
*/ | |
function buyHandler(e, res) { | |
if (e) { | |
log.error('An error occurred when getting prices, cancelling for today...') | |
return; | |
} | |
_.forIn(res, function(price, coin) { | |
//log.info("Token: " + coin + ", Price: " + price); | |
var toSpend = (DCA_SPEND) * currentTokenPercent[coin] | |
toSpend = toSpend - (toSpend * TRADING_FEE/100) | |
var volume = toSpend / price | |
if (toSpend > 0) { | |
var orderPrice = Math.round(price * SATOSHI) | |
var orderVolume = Math.round(volume * SATOSHI) | |
log.info('--- ' + coin + ' ---') | |
log.info('\r\nprice: $' + price) | |
log.info('\r\nspend: $' + toSpend) | |
log.info('\r\nvolume: ' + volume + ' ' + coin ) | |
client.createOrder(coin, FIAT_CURR, null, orderVolume, 'Bid', 'Market', null, function(e, res) { | |
if (res.success) { | |
log.info('\r\nordered ' + volume + ' ' + coin + ' @ $' + price + ' -- [$' + toSpend + ']') | |
} else { | |
log.info('\r\nerror placing ' + coin + ' order') | |
log.info(res) | |
} | |
}); | |
} | |
}) | |
} | |
// ==== MAIN ==== | |
log.info('DCA-CRYPTO: Booting [' + DCA_SCHEDULE + '] ----'); | |
var run = schedule.scheduleJob(DCA_SCHEDULE, function() { | |
log.info('\nDCA-CRYPTO: ==== ' + new Date() + ' ====') | |
getBalance().then((balance) => { | |
if (balance.aud_bal > DCA_SPEND) { | |
/* Choose a portfolio allocation: Go for 1 BTC, 32 ETH -- in order */ | |
if (balance.btc_bal < 0.8) { | |
currentTokenPercent = TOKEN_PERCENT_BTCMAX; | |
} | |
else { | |
if (balance.eth_bal < 32) { | |
currentTokenPercent = TOKEN_PERCENT_ETHMAX; | |
} | |
else { | |
currentTokenPercent = TOKEN_PERCENT_ETH_LINK; | |
} | |
} | |
/* purchase coins */ | |
getTokenPrices(buyHandler) | |
} | |
else { | |
log.info('\r\nDCA-CRYPTO: insufficient balance. Please fund the account'); | |
} | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment