Skip to content

Instantly share code, notes, and snippets.

@dbose
Created April 7, 2021 11:42
Show Gist options
  • Save dbose/22d0ae47e8d5ac7cf392c7237a99b424 to your computer and use it in GitHub Desktop.
Save dbose/22d0ae47e8d5ac7cf392c7237a99b424 to your computer and use it in GitHub Desktop.
DCA to BTC / ETH portfolio
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