gruelbox/orko

Script Example: SMA Trader

AwooOOoo opened this issue · 1 comments

I've been tinkering with the scripting capabilities and wrote a simple trader and thought I would share it in case anyone is interested and for possible inclusion in the documentation as it uses many of the available features and helped me learn a number of qwerks of using the script engine.

SMA Trader

Trading cryptocurrencies (or any other financial market) involves substantial risk, and there is always the potential for loss. Your trading results may vary. No representation is being made that this algorithm will guarantee profits, or not result in losses from trading.

SMA Trader is a simple buy /sell trader based on Simple Moving Averages (SMA). The following are the input parameters for the script. If you are going to use this script please create these variables.

Name Description Default Required
intervalSize Interval (in minutes) to to take each sample 5 Yes
fastAverageLength Length of fast average. Needs to be smaller than slow average. 12 Yes
slowAverageLength Length of slow average. Needs to be larger than fast average 26 Yes
startInTrade Whether we are starting in (money in base) or out (money in quote) of the trade false Yes
amount Amount to be traded 1 Yes

The intervalSize defines an interval at which a ticker is created internally to tracker the price at the defined interval. The fastAverageLength and slowAverageLength define the number of ticks from the ticker defined by the intervalSize. Since there is no current access to history data in the script it, you need to wait for the moving averages to fill up before they can be used. This time is the intervalSize * [fast|slow]AverageLength so since the defaults are 5, 12 & 26, this means you need to wait 5 minutes * 12 samples for the fast average and 5 minutes * 26 samples for the slow average so be careful what you set these to as it will take a while to be ready.

Here is an example with really short lengths which will have the system ready quickly for testing, but obviously won't be much use for trading.
image

Here is what you can expect from the Server Notifications. The trades themselves are highlighted and updates to the SMA values are provided at each interval.
image

Code

var precision = 2; // Accuracy of amounts to display
var quotes = {};   // Contains arrays of historical data to calculate the SMAs
var lastPrice;     // The last price data received
var subscription;  // Current Subscription Object
var interval;      // Interval Object 

/*
Initiates a trade

Improvement:When we have #983 implemented we can read the actual balance and not just have a fixed amount
*/
function trade(side) {
  var precision = 4;

  // Not a good implementation as it always assumes you have the same fixed amount available
  var params;
  var amount;
  if (side === "BUY") { 
    amount = parameters.amount;
    params = {
      market: parameters.selectedCoin,
      direction: BUY,  // BUY is not quoted so I can't use a variable
      price: lastPrice,
      amount: amount 
    }
  } else { 
    amount = parameters.amount * lastPrice;
    params = {
      market: parameters.selectedCoin,
      direction: SELL, // SELL is not quoted so I can't use a variable
      price: lastPrice,
      amount: amount
    }
  }

  if (side === "BUY") params.side = BUY;
  else params.side = SELL;

  notifications.info("Trade: " + side + "ing " + amount + " for " + lastPrice);

  // Place the order on the exchange
  trading.limitOrder(params);
}

/*
Calculates a moving average

Improvement: When #985 is addressed we won't have to manually create a SMA function
*/
function simpleMovingAverage(name, averageLength, price) {     
  var sma = 0;

  if (!(name in quotes)) quotes[name] = [];

  if (quotes[name].length < averageLength) {
    quotes[name].push(price);
    notifications.info("Filling the SMA(" + averageLength  + "): " + quotes[name].length + "/" + averageLength)
    sma = undefined;
  } else {
    quotes[name].shift();
    quotes[name].push(price);

    for(c = 0; c < quotes[name].length; c++) { sma += quotes[name][c]; }
    sma /= averageLength;

    notifications.info("SMA(" + averageLength + ") = " + sma.toFixed(2));
  }

  return sma;
}

/*
Update the moving averages and trade if conditions are met
*/
function updateSMAs() {
  // Update the moving averages
  var fast = simpleMovingAverage("fast", parameters.fastAverageLength, lastPrice);
  var slow = simpleMovingAverage("slow", parameters.slowAverageLength, lastPrice);

  if (slow !== undefined && fast !== undefined) {
   if (fast > slow && state.persistent.get("inTrade") === "false") {
      trade("BUY");
      state.persistent.set("inTrade", "true");
    } else if (fast < slow && state.persistent.get("inTrade") === "true") {
      trade("SELL");
      state.persistent.set("inTrade", "false");
    }
  }
}

/*
Executes when the script is started
*/
function start() {
  notifications.info("SMA Trader starting ");

  // Check our lengths are the right way around or the trades will be backwards
  if (parameters.fastAverageLength > parameters.slowAverageLength) {
    notifications.alert("The fast average needs to be smaller than the slow average. Aborting!!!");
    return FAILURE_PERMANENT
  }

  // Initialize the starting trade state
  if (parameters.startInTrade === "true") state.persistent.set("inTrade", "true");
  else state.persistent.set("inTrade", "false");

  // Setup a time to trigger at the requested interval
  interval = setInterval(
    function(event) {
      updateSMAs();
    },
    Math.round(1000 * 60 * parameters.intervalSize)
  )

  subscription = events.setTick(
    function(event) {
      lastPrice = event.ticker().getLast();
    },
    parameters.selectedCoin
  )
  return RUNNING
}

/* 
Executes when the script is stopped
*/
function stop() {
  events.clear(subscription);  // Stop the subscription
  clearInterval(interval);     // Clear the interval
}

If you think this is of value I can do a PR on the wiki. Thoughts?

Create a showcase example to test different aspects of the script engine. Submitted a PR in the Wiki

gruelbox/orko-wiki@master...AwooOOoo:master#diff-9539630766db4c6743745087f04d55bd