Skip to content
This repository has been archived by the owner on Feb 15, 2022. It is now read-only.

Is this output correct? #25

Closed
ghostnegotiator opened this issue Aug 17, 2016 · 20 comments
Closed

Is this output correct? #25

ghostnegotiator opened this issue Aug 17, 2016 · 20 comments

Comments

@ghostnegotiator
Copy link

I have been tweaking and playing around in my strategy, and making 24% Profit? That is huge!

Although I am highly aware this stuff is still Alpha and currently bashing my head over theories... this is used on BTC/EUR Market on GDAX. Personally I would not recommend using this strategy unless you are willing to risk to lose your assets or going to use it for TESTING purposes.

08/17/2016 09:49:39 AM CEST [    launcher] cmd `sim` booting
{
  "id": "873aef7aad3c86e7",
  "ticks": 190122,
  "sim_end_timestamp": "08/11/2016 02:00:00 AM CEST",
  "sim_start_bucket": "7d2420",
  "sim_start_time": 1463616000000,
  "sim_start_tick": "7d2420",
  "sim_start_timestamp": "05/19/2016 02:00:00 AM CEST",
  "end_time": 1470873300000,
  "queue": [],
  "actions": [
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 401.5,
      "market": true,
      "size": 0.012453300124533002,
      "rsi": 21,
      "roi": 0.9950556089862149,
      "id": "407315029c53036a",
      "time": 1463660100000,
      "timestamp": "05/19/2016 02:15:00 PM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 397.02,
      "market": true,
      "size": 0.010075059190972749,
      "rsi": 34,
      "roi": 0.98841588513003,
      "id": "4794626ce34a1f46",
      "time": 1463690880000,
      "timestamp": "05/19/2016 10:48:00 PM CEST"
    },
    {
      "type": "sell",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 543.1,
      "market": true,
      "size": 0.016835824017178733,
      "rsi": 91,
      "roi": 1.2343536023729769,
      "id": "0619efd2a9f2bcce",
      "time": 1465691940000,
      "timestamp": "06/12/2016 02:39:00 AM CEST"
    },
    {
      "type": "sell",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 615.1,
      "market": true,
      "size": 0.013468659213742986,
      "rsi": 92,
      "roi": 1.3313279487119265,
      "id": "058e99d711bca5cd",
      "time": 1467349620000,
      "timestamp": "07/01/2016 07:07:00 AM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 610.99,
      "market": true,
      "size": 0.010942276733204496,
      "rsi": 41,
      "roi": 1.3268994535624479,
      "id": "8ed606f7d76fcd23",
      "time": 1467382080000,
      "timestamp": "07/01/2016 04:08:00 PM CEST"
    },
    {
      "type": "sell",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 618.88,
      "market": true,
      "size": 0.012963382717635287,
      "rsi": 100,
      "roi": 1.3371275625266623,
      "id": "d9b76f62c9104c3d",
      "time": 1467464280000,
      "timestamp": "07/02/2016 02:58:00 PM CEST"
    },
    {
      "type": "sell",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 630.97,
      "market": true,
      "size": 0.01037070617410823,
      "rsi": 94,
      "roi": 1.3496657462911588,
      "id": "cb869419a158ff21",
      "time": 1467499980000,
      "timestamp": "07/03/2016 12:53:00 AM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 604.76,
      "market": true,
      "size": 0.013661243936718592,
      "rsi": 39,
      "roi": 1.3279204495852885,
      "id": "cfb9599e1894f76f",
      "time": 1467626520000,
      "timestamp": "07/04/2016 12:02:00 PM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 605.91,
      "market": true,
      "size": 0.010908252226462587,
      "rsi": 39,
      "roi": 1.329188763163851,
      "id": "c5630c4420f947e1",
      "time": 1467659640000,
      "timestamp": "07/04/2016 09:14:00 PM CEST"
    },
    {
      "type": "sell",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 605.11,
      "market": true,
      "size": 0.01321046417192282,
      "rsi": 88,
      "roi": 1.3281319260300974,
      "id": "8d6fd786ffeb7d76",
      "time": 1469389260000,
      "timestamp": "07/24/2016 09:41:00 PM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 593.68,
      "market": true,
      "size": 0.011599333109155103,
      "rsi": 36,
      "roi": 1.3160522775912908,
      "id": "da1ef98b2255b1c5",
      "time": 1469515320000,
      "timestamp": "07/26/2016 08:42:00 AM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 537.3,
      "market": true,
      "size": 0.01025318009341999,
      "rsi": 42,
      "roi": 1.2433883919763675,
      "id": "dff5c22c36dc222f",
      "time": 1470128460000,
      "timestamp": "08/02/2016 11:01:00 AM CEST"
    },
    {
      "type": "sell",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 546.2,
      "market": true,
      "size": 0.014938873978053277,
      "rsi": 95,
      "roi": 1.2566839898168345,
      "id": "e8464ba79b958389",
      "time": 1470585300000,
      "timestamp": "08/07/2016 05:55:00 PM CEST"
    },
    {
      "type": "buy",
      "asset": "BTC",
      "currency": "EUR",
      "exchange": "gdax",
      "price": 540.35,
      "market": true,
      "size": 0.011176366289845821,
      "rsi": 24,
      "roi": 1.2496925967951058,
      "id": "3a1d1568ddbb4650",
      "time": 1470623940000,
      "timestamp": "08/08/2016 04:39:00 AM CEST"
    }
  ],
  "market_price": 534.09,
  "progress": 0.76,
  "balance": {
    "BTC": 0.07093186220205892,
    "EUR": 24.15659809887275
  },
  "check_diff": 2.95,
  "last_close": 541.15,
  "rsi": 62,
  "rsi_ansi": "\u001b[37m62\u001b[39m",
  "recovery_ticks": 72,
  "oversold": false,
  "end_balance": 62.484629839755286,
  "roi": 1.2496925967951058,
  "trades": 14,
  "overbought": false,
  "end_us": 1471420195613869,
  "last_us": 16340535,
  "last_duration": "16s 340ms 535µs",
  "sim_time": 7257300000,
  "sim_duration": "83d 23h 55m",
  "input_hash": "faf4c0f720bd51a2b9946f0e90405d3d48812106"
}

Strategy:

var first_run = true
var last_balance_sig

module.exports = function container (get, set, clear) {
  var c = get('config')
  var o = get('utils.object_get')
  var n = require('numbro')
  var tb = require('timebucket')
  var sig = require('sig')
  var format_currency = get('utils.format_currency')
  var get_timestamp = get('utils.get_timestamp')
  var CoinbaseExchange = require('coinbase-exchange')
  var client
  var asset = 'BTC'
  var currency = 'EUR'
  var rsi_period = '15m'
  var rsi_overbought = 88
  var rsi_oversold = 44
  var check_period = '1m'
  var exchange = 'gdax'
  var selector = 'data.trades.' + exchange + '.' + asset + '-' + currency
  var recovery_ticks = 300
  var trade_pct = 0.20
  var min_trade = 0.01
  var start_balance = 50
  function onOrder (err, resp, order) {
    if (err) return get('logger').error('order err', err, resp, order, {feed: 'errors'})
    if (resp.statusCode !== 200) {
      console.error(order)
      return get('logger').error('non-200 status: ' + resp.statusCode, {data: {statusCode: resp.statusCode, body: order}})
    }
    get('logger').info(exchange, ('order-id: ' + order.id).cyan, {data: {order: order}})
    function getStatus () {
      client.getOrder(order.id, function (err, resp, order) {
        if (err) return get('logger').error('getOrder err', err)
        if (resp.statusCode !== 200) {
          console.error(order)
          return get('logger').error('non-200 status from getOrder: ' + resp.statusCode, {data: {statusCode: resp.statusCode, body: order}})
        }
        if (order.status === 'done') {
          return get('logger').info(exchange, ('order ' + order.id + ' done: ' + order.done_reason).cyan, {data: {order: order}})
        }
        else {
          get('logger').info(exchange, ('order ' + order.id + ' ' + order.status).cyan, {data: {order: order}})
          setTimeout(getStatus, 5000)
        }
      })
    }
    getStatus()
  }
  return [
    // BEGIN DEFAULT TRADE LOGIC
    // sync balance
    function (tick, trigger, rs, cb) {
      if (get('command') !== 'run' || !c.gdax_key) {
        return cb()
      }
      if (!client) {
        client = new CoinbaseExchange.AuthenticatedClient(c.gdax_key, c.gdax_secret, c.gdax_passphrase)
      }
      client.getAccounts(function (err, resp, accounts) {
        if (err) throw err
        if (resp.statusCode !== 200) {
          console.error(accounts)
          get('logger').error('non-200 status from exchange: ' + resp.statusCode, {data: {statusCode: resp.statusCode, body: accounts}})
          return cb && cb()
        }
        rs.balance = {}
        accounts.forEach(function (account) {
          if (account.currency === currency) {
            rs.balance[currency] = n(account.balance).value()
          }
          else if (account.currency === asset) {
            rs.balance[asset] = n(account.balance).value()
          }
        })
        var balance_sig = sig(rs.balance)
        if (balance_sig !== last_balance_sig) {
          get('logger').info(exchange, 'balance'.grey, n(rs.balance[asset]).format('0.000').white, asset.grey, n(rs.balance[currency]).format('0.00').yellow, currency.grey, {feed: 'exchange'})
          first_run = false
          last_balance_sig = balance_sig
        }
        cb && cb()
      })
    },
    function (tick, trigger, rs, cb) {
      // note the last close price
      var market_price = o(tick, selector + '.close')
      if (market_price) {
        rs.market_price = market_price
      }
      rs.ticks || (rs.ticks = 0)
      rs.progress || (rs.progress = 0)
      if (!rs.market_price) return cb()
      if (!rs.balance) {
        // start with start_balance, neutral position
        rs.balance = {}
        rs.balance[currency] = start_balance/2
        rs.balance[asset] = n(start_balance/2).divide(rs.market_price).value()
      }
      rs.ticks++
      if (tick.size !== check_period) {
        return cb()
      }
      rs.progress = 1
      if (rs.recovery_ticks) {
        rs.recovery_ticks--
      }
      // what % are we to a decision?
      rs.progress = recovery_ticks ? n(1).subtract(n(rs.recovery_ticks).divide(recovery_ticks)).value() : 1
      if (rs.recovery_ticks) {
        return cb()
      }
      // check price diff
      var close = o(tick || {}, selector + '.close')
      // get rsi
      var rsi_tick_id = tb(tick.time).resize(rsi_period).toString()
      get('ticks').load(get('app_name') + ':' + rsi_tick_id, function (err, rsi_tick) {
        if (err) return cb(err)
        var rsi = o(rsi_tick || {}, selector + '.rsi')
        var rsi_open = o(rsi_tick || {}, selector + '.open')
        // require minimum data
        // overbought/oversold
        // sanity check
        close || (close = o(rsi_tick || {}, selector + '.close'))
        rs.check_diff = close ? n(close).subtract(rsi_open || close).value() : rs.check_diff || null
        rs.last_close = close
        get('logger').info('trader', '================== TRADER REPORT =================='.yellow, {feed: 'trader'})
        if (!rsi) {
          get('logger').info('trader', ('no ' + rsi_period + ' RSI').red, {feed: 'trader'})
        }
        else if (rsi.samples < c.rsi_periods) {
          get('logger').info('trader', (rsi_period + ' RSI: not enough samples: ' + rsi.samples).red, {feed: 'trader'})
        }
        else if (!close) {
          get('logger').info('trader', ('no close price').red, {feed: 'trader'})
        }
        else if (rs.check_diff === null) {
          get('logger').info('trader', ('not enough ticks to make decision').red, {feed: 'trader'})
        }
        else {
          rs.rsi = Math.round(rsi.value)
          rs.rsi_ansi = rsi.ansi
          if (rsi.value >= rsi_overbought) {
            get('logger').info('trader', 'RSI:'.grey + rs.rsi_ansi, ('anticipating a reversal DOWN. sell at market. (' + format_currency(rs.market_price, currency) + ') diff: ' + format_currency(rs.check_diff, currency)).green, {feed: 'trader'})
            rs.overbought = true
          }
          else if (rsi.value <= rsi_oversold) {
            get('logger').info('trader', 'RSI:'.grey + rs.rsi_ansi, ('anticipating a reversal UP. buy at market. (' + format_currency(rs.market_price, currency) + ') diff: ' + format_currency(rs.check_diff, currency)).green, {feed: 'trader'})
            rs.oversold = true
          }
          else {
            get('logger').info('trader', (rsi_period + ' RSI within HOLD range: ').yellow + rsi.ansi + ' diff: '.grey + format_currency(rs.check_diff, currency).grey, {feed: 'trader'})
          }
        }
        get('logger').info('trader', '================== END REPORT =================='.yellow, {feed: 'trader'})
        rs.recovery_ticks = recovery_ticks + 1
        cb()
      })
    },
    // @todo MACD
    function (tick, trigger, rs, cb) {
      cb()
    },
    // trigger trade signals
    function (tick, trigger, rs, cb) {
      if ((rs.overbought || rs.oversold) && rs.balance && rs.market_price) {
        var size, new_balance = {}
        if (rs.overbought) {
          size = rs.balance[asset]
        }
        else if (rs.oversold) {
          size = n(rs.balance[currency]).divide(rs.market_price).value()
        }
        // scale down size a little, to prevent out-of-balance errors
        size = n(size || 0).multiply(trade_pct).value()
        // min size
        if (!size || size < min_trade) {
          if (rs.overbought) {
            get('logger').info('trader', 'RSI:'.grey + rs.rsi_ansi, ('not enough ' + asset + ' to execute sell!').red, {feed: 'trader'})
          }
          else if (rs.oversold) {
            get('logger').info('trader', 'RSI:'.grey + rs.rsi_ansi, ('not enough ' + currency + ' to execute buy!').red, {feed: 'trader'})
          }
          rs.overbought = rs.oversold = false
          return cb()
        }
        if (rs.overbought) {
          new_balance[currency] = n(rs.balance[currency]).add(n(size).multiply(rs.market_price)).value()
          new_balance[asset] = n(rs.balance[asset]).subtract(size).value()
        }
        else if (rs.oversold) {
          new_balance[asset] = n(rs.balance[asset]).add(size).value()
          new_balance[currency] = n(rs.balance[currency]).subtract(n(size).multiply(rs.market_price)).value()
        }
        // consolidate balance
        var new_end_balance = n(new_balance[currency]).add(n(new_balance[asset]).multiply(rs.market_price)).value()
        var new_roi = n(new_end_balance).divide(start_balance).value()
        rs.balance = new_balance
        rs.end_balance = new_end_balance
        rs.roi = new_roi
        rs.trades || (rs.trades = 0)
        rs.trades++
        trigger({
          type: rs.overbought ? 'sell' : 'buy',
          asset: asset,
          currency: currency,
          exchange: exchange,
          price: rs.market_price,
          market: true,
          size: size,
          rsi: rs.rsi,
          roi: rs.roi
        })
        if (get('command') === 'run' && c.gdax_key) {
          var params = {
            type: 'market',
            size: n(size).format('0.000000'),
            product_id: asset + '-' + currency
          }
          client[rs.overbought ? 'sell' : 'buy'](params, function (err, resp, order) {
            onOrder(err, resp, order)
          })
        }
        rs.overbought = rs.oversold = false
      }
      cb()
    }
    // END DEFAULT TRADE LOGIC
  ]
}
@ToX82
Copy link

ToX82 commented Aug 17, 2016

Using the default logic with these parameters

var rsi_overbought = 75
var rsi_oversold = 25

I'm having a monster ROI of more than 29%... am I correct?
The full json output is quite long, so I put it here: http://pastebin.com/anL8PLyZ

@ghostnegotiator
Copy link
Author

  "check_diff": -1.24,
  "last_close": 540.91,
  "rsi": 66,
  "rsi_ansi": "\u001b[37m66\u001b[39m",
  "recovery_ticks": 106,
  "oversold": false,
  "end_balance": 68.16419008020816,
  "roi": 1.363283801604163,
  "trades": 15,
  "overbought": false,
  "end_us": 1471436619196636,
  "last_us": 16178277,
  "last_duration": "16s 178ms 277µs",
  "sim_time": 7257300000,
  "sim_duration": "83d 23h 55m",
  "input_hash": "cb07443106072b8b5526e926acde1d91689c0cf9"
}

With current config (first post) &

var trade_pct = 0.30

@carlos8f
Copy link
Contributor

carlos8f commented Aug 17, 2016

Was getting barely 10% ROI on the old RSI overbought/oversold strategy. I recoded the default_logic.js to reverse the RSI signals (buy when RSI is high, etc, anticipating intensifying trend), added a few delta checks, now I'm getting 1.6 (60%) ROI 🎉

New logic is in zenbot master branch. result data:

https://gist.github.com/carlos8f/f20a1894c3264347d913d3eef862c3f5

@carlos8f
Copy link
Contributor

also note the new min_roi_delta which accepts a loss of up to %6 per trade, in the simulation, this param prevented trading during the early Aug crash, which would've resulted in ~ 12% loss.

@grigio
Copy link
Contributor

grigio commented Aug 17, 2016

@carlos8f cool I'm looking forward to test it on a real exchange with zenbot 3 :P
when will be available trader.js for gdax or another exchange?

@carlos8f
Copy link
Contributor

new roi = 1.89!

https://gist.github.com/carlos8f/e8237b3089a2b316093e5e8aac1469e8

@grigio trader.js will basically just be helper functions for default_logic.js to use, not necessary at the moment if you're using gdax (gdax support manually implemented in default_logic.js) but sometime in the next week I'll fill in trader.js for bitfinex, kraken, poloniex, etc. so you can switch from one exchange for another in the trader easily.

@grigio
Copy link
Contributor

grigio commented Aug 18, 2016

Ah ok, i"ll try to hardcode the kraken client in my logic then.

@carlos8f
Copy link
Contributor

hardcode the kraken client in my logic then.

yes! the logic is open-ended so if you want to have it send you a txt message instead of perform a trade, you can do that too :)

@grigio
Copy link
Contributor

grigio commented Aug 18, 2016

Ok :) i thought you were going to decouple the market logic from the exchange client implementation.. This is why i asked news for trader.js reference

@ghostnegotiator
Copy link
Author

Mother of god...

You guys are awesome! :)

@carlos8f
Copy link
Contributor

yeah, that's the benefit of algorithmic trading... remove all emotion! pure efficiency!

@ghostnegotiator
Copy link
Author

Also I am wondering why it did not trade the past few days on your screenshot. How come?

@carlos8f
Copy link
Contributor

Simulations always end on Wed. 5pm PDT for consistency, which was last week when I ran the sim.

@ghostnegotiator
Copy link
Author

That explains!

@ghostnegotiator
Copy link
Author

I broke it...

  "trend": "DOWN",
  "op": "sell",
  "new_end_balance": 1974.8036920118473,
  "new_roi": 1.9748036920118472,
  "new_roi_delta": -0.0004689106031668,
  "hold_ticks_active": 0,
  "end_balance": 1974.8036920118473,
  "roi": 1.9748036920118472,
  "trades": 187,
  "balance_warning": true,
  "end_us": 1471501073640250,
  "last_us": 36673251,
  "last_duration": "36s 673ms 251µs",
  "sim_time": 7237860000,
  "sim_duration": "83d 18h 31m",
  "input_hash": "2068e5a631fdf6d3df221b4c9581c79e833705d7"

@ghostnegotiator
Copy link
Author

  "new_end_balance": 2042.1839604725053,
  "new_roi": 2.042183960472505,
  "new_roi_delta": -0.0004848606779632,
  "hold_ticks_active": 0,
  "end_balance": 2042.1839604725053,
  "roi": 2.042183960472505,
  "trades": 164,
  "balance_warning": true,
  "end_us": 1471501211087849,
  "last_us": 36151901,
  "last_duration": "36s 151ms 901µs",
  "sim_time": 7237860000,
  "sim_duration": "83d 18h 31m",
  "input_hash": "2068e5a631fdf6d3df221b4c9581c79e833705d7"

204% 🔥

@ghostnegotiator
Copy link
Author

Alright last simulation post...

    "current_gain": 0,
    "current_loss": 1.79,
    "gain_sum": 5.730000000000075,
    "avg_gain": 0.4407692307692365,
    "loss_sum": 9.440000000000055,
    "avg_loss": 0.7261538461538504,
    "avg_gain_2": 0.40928571428571964,
    "avg_loss_2": 0.8021428571428609,
    "relative_strength": 0.5102404274265404
  },
  "trend": "DOWN",
  "op": "sell",
  "new_end_balance": 2177.2508908258765,
  "new_roi": 2.1772508908258765,
  "new_roi_delta": -0.0000107250708675,
  "hold_ticks_active": 0,
  "end_balance": 2177.2508908258765,
  "roi": 2.1772508908258765,
  "trades": 176,
  "balance_warning": true,
  "end_us": 1471501826316120,
  "last_us": 37020513,
  "last_duration": "37s 20ms 513µs",
  "sim_time": 7237860000,
  "sim_duration": "83d 18h 31m",
  "input_hash": "2068e5a631fdf6d3df221b4c9581c79e833705d7"

A woppin 217% 🔥 🔥 🔥

@carlos8f
Copy link
Contributor

so 🔥 217%, I'd like to see the full json

@ghostnegotiator
Copy link
Author

var first_run = true
var last_balance_sig
var sync_start_balance = false

module.exports = function container (get, set, clear) {
  var c = get('config')
  var o = get('utils.object_get')
  var n = require('numbro')
  var tb = require('timebucket')
  var sig = require('sig')
  var format_currency = get('utils.format_currency')
  var get_timestamp = get('utils.get_timestamp')
  var CoinbaseExchange = require('coinbase-exchange')
  var client
  function onOrder (err, resp, order) {
    if (err) return get('logger').error('order err', err, resp, order, {feed: 'errors'})
    if (resp.statusCode !== 200) {
      console.error(order)
      return get('logger').error('non-200 status: ' + resp.statusCode, {data: {statusCode: resp.statusCode, body: order}})
    }
    get('logger').info('gdax', ('order-id: ' + order.id).cyan, {data: {order: order}})
    function getStatus () {
      client.getOrder(order.id, function (err, resp, order) {
        if (err) return get('logger').error('getOrder err', err)
        if (resp.statusCode !== 200) {
          console.error(order)
          return get('logger').error('non-200 status from getOrder: ' + resp.statusCode, {data: {statusCode: resp.statusCode, body: order}})
        }
        if (order.status === 'done') {
          return get('logger').info('gdax', ('order ' + order.id + ' done: ' + order.done_reason).cyan, {data: {order: order}})
        }
        else {
          get('logger').info('gdax', ('order ' + order.id + ' ' + order.status).cyan, {data: {order: order}})
          setTimeout(getStatus, 5000)
        }
      })
    }
    getStatus()
  }
  return [
    // BEGIN DEFAULT TRADE LOGIC
    // default params
    function (tick, trigger, rs, cb) {
      rs.asset = 'BTC'
      rs.currency = 'EUR'
      rs.rsi_period = '1h'
      rs.rsi_up = 63
      rs.rsi_down = 47
      rs.check_period = '1m'
      rs.exchange = 'gdax'
      rs.selector = 'data.trades.' + rs.exchange + '.' + rs.asset + '-' + rs.currency
      rs.hold_ticks = 100 // hold x check_period after trade
      rs.trade_pct = 0.95 // trade % of current balance
      rs.min_trade = 0.01
      rs.start_balance = 1000
      rs.min_roi_delta = -0.06 // accept a loss of up to 6%
      cb()
    },
    // sync balance if key is present and we're in the `run` command
    function (tick, trigger, rs, cb) {
      if (get('command') !== 'run' || !c.gdax_key) {
        return cb()
      }
      if (!client) {
        client = new CoinbaseExchange.AuthenticatedClient(c.gdax_key, c.gdax_secret, c.gdax_passphrase)
      }
      client.getAccounts(function (err, resp, accounts) {
        if (err) throw err
        if (resp.statusCode !== 200) {
          console.error(accounts)
          get('logger').error('non-200 status from exchange: ' + resp.statusCode, {data: {statusCode: resp.statusCode, body: accounts}})
          return cb()
        }
        rs.balance = {}
        accounts.forEach(function (account) {
          if (account.currency === rs.currency) {
            rs.balance[rs.currency] = n(account.balance).value()
          }
          else if (account.currency === rs.asset) {
            rs.balance[rs.asset] = n(account.balance).value()
          }
        })
        if (first_run) {
          sync_start_balance = true
        }
        var balance_sig = sig(rs.balance)
        if (balance_sig !== last_balance_sig) {
          get('logger').info(rs.exchange, 'balance'.grey, n(rs.balance[rs.asset]).format('0.000').white, rs.asset.grey, n(rs.balance[rs.currency]).format('0.00').yellow, rs.currency.grey, {feed: 'exchange'})
          last_balance_sig = balance_sig
        }
        cb()
      })
    },
    function (tick, trigger, rs, cb) {
      // note the last close price
      rs.market_price = o(tick, rs.selector + '.close')
      rs.ticks || (rs.ticks = 0)
      rs.progress || (rs.progress = 0)
      if (!rs.market_price) return cb()
      if (!rs.balance) {
        // start with start_balance, neutral position
        rs.balance = {}
        rs.balance[rs.currency] = n(rs.start_balance).divide(2).value()
        rs.balance[rs.asset] = n(rs.start_balance).divide(2).divide(rs.market_price).value()
      }
      rs.ticks++
      if (tick.size !== rs.check_period) {
        return cb()
      }
      // check price diff
      rs.close = o(tick || {}, rs.selector + '.close')
      // get rsi
      rs.rsi_tick_id = tb(tick.time).resize(rs.rsi_period).toString()
      get('ticks').load(get('app_name') + ':' + rs.rsi_tick_id, function (err, rsi_tick) {
        if (err) return cb(err)
        var rsi = o(rsi_tick || {}, rs.selector + '.rsi')
        var trend
        if (rsi) {
          rs.rsi = rsi
        }
        // require minimum data
        rs.close || (rs.close = o(rsi_tick || {}, rs.selector + '.close'))
        if (!rs.rsi) {
          get('logger').info('trader', ('no ' + rs.rsi_period + ' RSI for tick ' + rs.rsi_tick_id).red, {feed: 'trader'})
        }
        else if (rs.rsi.samples < c.rsi_periods) {
          get('logger').info('trader', (rs.rsi_period + ' RSI: not enough samples for tick ' + rs.rsi_tick_id + ': ' + rs.rsi.samples).red, {feed: 'trader'})
        }
        else if (!rs.close) {
          get('logger').info('trader', ('no close price for tick ' + rs.rsi_tick_id).red, {feed: 'trader'})
        }
        else {
          if (rs.rsi.value >= rs.rsi_up) {
            trend = 'UP'
          }
          else if (rs.rsi.value <= rs.rsi_down) {
            trend = 'DOWN'
          }
          else {
            trend = null
          }
        }
        if (trend !== rs.trend) {
          get('logger').info('trader', 'RSI:'.grey + rs.rsi.ansi, ('trend: ' + rs.trend + ' -> ' + trend).yellow, {feed: 'trader'})
          delete rs.balance_warning
          delete rs.roi_warning
        }
        rs.trend = trend
        cb()
      })
    },
    // @todo MACD
    function (tick, trigger, rs, cb) {
      cb()
    },
    // trigger trade signals
    function (tick, trigger, rs, cb) {
      if (rs.trend && rs.balance && rs.market_price) {
        var size, new_balance = {}
        // delay buying or selling, perhaps the trend intensifies
        if (rs.hold_ticks_active) {
          rs.hold_ticks_active--
        }
        if (rs.hold_ticks_active) {
          rs.progress = n(1).subtract(n(rs.hold_ticks_active).divide(rs.hold_ticks)).value()
          return cb()
        }
        rs.progress = 1
        if (rs.trend === 'DOWN') {
          // calculate sell size
          size = rs.balance[rs.asset]
        }
        else if (rs.trend === 'UP') {
          // calculate buy size
          size = n(rs.balance[rs.currency]).divide(rs.market_price).value()
        }
        size = n(size || 0).multiply(rs.trade_pct).value()
        // min size
        if (!size || size < rs.min_trade) {
          if (!rs.balance_warning) {
            get('logger').info('trader', 'trend: '.grey, rs.trend, ('not enough balance, aborting trade!').red, {feed: 'trader'})
          }
          rs.balance_warning = true
          return cb()
        }
        if (rs.trend === 'DOWN') {
          // SELL!
          new_balance[rs.currency] = n(rs.balance[rs.currency]).add(n(size).multiply(rs.market_price)).value()
          new_balance[rs.asset] = n(rs.balance[rs.asset]).subtract(size).value()
          rs.op = 'sell'
        }
        else if (rs.trend === 'UP') {
          // BUY!
          new_balance[rs.asset] = n(rs.balance[rs.asset]).add(size).value()
          new_balance[rs.currency] = n(rs.balance[rs.currency]).subtract(n(size).multiply(rs.market_price)).value()
          rs.op = 'buy'
        }
        else {
          // unknown trend
          get('logger').info('trader', ('unkown trend (' + rs.trend + ') aborting trade!').red, {feed: 'trader'})
          return cb()
        }
        // consolidate balance
        rs.new_end_balance = n(new_balance[rs.currency]).add(n(new_balance[rs.asset]).multiply(rs.market_price)).value()
        if (sync_start_balance) {
          rs.start_balance = rs.new_end_balance
          sync_start_balance = false
        }
        rs.new_roi = n(rs.new_end_balance).divide(rs.start_balance).value()
        rs.new_roi_delta = n(rs.new_roi).subtract(rs.roi || 0).value()
        if (rs.roi && rs.new_roi_delta < rs.min_roi_delta) {
          if (!rs.roi_warning) {
            get('logger').info('trader', ('new ROI below delta threshold (' + n(rs.new_roi_delta).format('%0.000') + ' < ' + n(rs.min_roi_delta).format('%0.000') + ') aborting ' + rs.op + '!').red, {feed: 'trader'})
          }
          rs.roi_warning = true
          return cb()
        }
        rs.hold_ticks_active = rs.hold_ticks + 1
        rs.balance = new_balance
        rs.end_balance = rs.new_end_balance
        rs.roi = rs.new_roi
        rs.trades || (rs.trades = 0)
        rs.trades++
        var trade = {
          type: rs.op,
          asset: rs.asset,
          currency: rs.currency,
          exchange: rs.exchange,
          price: rs.market_price,
          market: true,
          size: size,
          rsi: rs.rsi.value,
          roi: rs.roi
        }
        trigger(trade)
        if (get('command') === 'run' && c.gdax_key) {
          var params = {
            type: 'market',
            size: n(size).format('0.000000'),
            product_id: rs.asset + '-' + rs.currency
          }
          client[rs.op](params, function (err, resp, order) {
            onOrder(err, resp, order)
          })
        }
      }
      cb()
    },
    function (tick, trigger, rs, cb) {
      first_run = false
      cb()
    }
    // END DEFAULT TRADE LOGIC
  ]
}

NOTE: BTC/EUR

@carlos8f
Copy link
Contributor

Zenbot 4 is out, so I'm cleaning up 3.x issues. Please use and test 4, thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants