Skip to content

Algorithms

Algorithm concepts

What happens to your code, in the order it happens.

An algorithm is a JavaScript file, but Cloud9Trader does not simply run it top to bottom. It reads the file first, works out what prices and indicators it needs, loads them, and then calls your functions as prices arrive. Knowing the order explains most of the mistakes people make. New to writing one? Build the moving average crossover first, then come back here.

  1. Your code is read before it runs. Cloud9Trader checks the syntax and the rules, and finds every instrument, timeframe and indicator you name, such as sma(20), H1.rsi(14) or INSTRUMENTS["GBP_USD:OANDA"]. That is why names must be written as literal text: sma(period) with a variable works, because the period is an external or a number, but INSTRUMENTS[name] built at run time finds nothing.
  2. History is loaded and the indicators are warmed up. Each indicator runs over bars from before your start date, so it has a value on the first bar. See History before the start.
  3. The top-level code runs once, when the first price or bar arrives. Anything outside a function goes here: checking your externals, setting starting values.
  4. Your handlers run as prices arrive. onTick runs on every price update and onIntervalClose when each bar of your run’s timeframe closes. Before each call, the API’s values, such as BID, CLOSE and OPEN_POSITIONS_COUNT, are set for that moment.
// Runs once, on the first price.
let barsSeen = 0;
function onIntervalClose() {
// Runs at the close of every bar.
barsSeen = barsSeen + 1;
log("Bars so far", barsSeen, "close", CLOSE);
}
function onTick() {
// Runs on every price update.
if (SPREAD > 3) {
log("Wide spread", SPREAD);
}
}

You do not need both handlers, but with neither your algorithm does nothing. The same code runs the same way in a backtest and in a trading session.

Other timeframes and instruments have handlers too. Assign a function to H1.onIntervalClose, INSTRUMENTS["GBP_USD:OANDA"].onTick and so on. See onIntervalClose and Use several timeframes and instruments.

A variable declared inside a handler starts again on every call. A variable declared at the top level, outside every function, keeps its value from one call to the next. That is how an algorithm remembers what happened on the last bar.

The common mistake is to declare the variable inside the handler:

function onIntervalClose() {
let previousClose; // starts again, empty, on every bar
if (previousClose !== undefined && CLOSE > previousClose) {
log("Higher close"); // never runs
}
previousClose = CLOSE;
}

Move the declaration out, and the value set on one bar is there on the next:

let previousClose;
function onIntervalClose() {
if (previousClose !== undefined && CLOSE > previousClose) {
log("Higher close");
}
previousClose = CLOSE;
}

On the first bar previousClose is still undefined, so check for that before you use it.

A top-level declaration can read the API. let wasAbove = sma(5) > sma(20); is worked out once, when the top-level code runs, and keeps its value between calls like any other top-level variable. A plain value, such as const PERIOD = 20;, is set before anything else, so getRunUpCount can read it too.

An instrument can have a shorter name. Declare it at the top level and use it anywhere:

const gbpusd = INSTRUMENTS["GBP_USD:OANDA"];
gbpusd.H4.onIntervalClose = function () {
const level = gbpusd.H4.sma(10);
log("GBP/USD 4-hour SMA", level);
};

The shorter name must be set at the top level: inside a function it is refused when the run starts.

A handler assigned to a timeframe or instrument is a function () { … } written in place, and its variables start again on every call, as in any handler. H1.onIntervalClose = checkHour; is refused when the run starts; call checkHour() from inside a function () { … } instead.

Externals are the settings you want to change without editing code: a stop distance, a moving average’s period, the largest spread you will trade. Declare them at the top of your algorithm:

const externals = [
{ name: "period", defaultValue: 20 },
{ name: "stopPips", defaultValue: 25 },
];
if (period < 2) {
error("period must be at least 2");
}
function onIntervalClose() {
if (OPEN_POSITIONS_COUNT === 0 && CLOSE > sma(period)) {
openPosition("BUY", { quantity: 1000, stopLossPips: stopPips });
}
}
  • Each external becomes a number your code uses by name, here period and stopPips. Read it; do not assign to it.
  • You set the values for each run. The Backtest, Optimize and Trade forms list your externals, filled in with each defaultValue.
  • The optimizer searches them. An optimization runs many backtests over a range for each external and shows which values fitted the past best.
  • Check them at the top level. The top-level code runs before the first trade, so an error() there stops a run whose settings make no sense.
  • A plain list of names, const externals = ["period", "stopPips"];, also works, with no default values.

An algorithm often needs bars from before its start date: to warm up an indicator, or to read the last 50 closes with prices(50). Cloud9Trader loads it for you:

  • Indicators load their own history. sma(200) loads enough bars before your start to have a value from the first bar.
  • Your algorithm can ask for more. Two optional functions, declared at the top level, return a number of bars:
    • getRunUpCount(): bars to run through before the start date, so your handlers have warmed up. The default is 1, so onIntervalClose can fire on the start time with the bar before it.
    • getBufferSize(): bars of past prices to keep ready for price() and prices(). The default is 0, so only what the indicators load is kept.
  • Both are capped at 1,000 bars. If your code reads further back than was loaded, the prices load while your algorithm waits, with a warning in the Log. In a live session that pause can delay a trade, so ask for enough up front:
function getBufferSize() {
return 100;
}
function onIntervalClose() {
const closes = prices(100);
log("Highest of the last 100 closes", Math.highest(closes));
}
  • Mistakes in the code. Syntax errors are marked in the editor as you type. The other rules are checked when a backtest or session starts, and the run stops with the reason in its Log. That covers a variable you did not declare, reassigning an API name such as BID, and a shorter instrument name set inside a function (Top-level values from the API).
  • An error thrown while running is written to the Log with the line it came from. A backtest stops at the first one. A live session stops after three within 60 seconds.
  • Your own checks call error("message") to stop the run with your message.
  • To find out what your code is doing, write values to the Log with log() and draw them on the chart with plot(). See Debug an algorithm.

Modern JavaScript up to ES2017 works: let and const, arrow functions, template strings, destructuring, the spread operator, classes and **. Newer syntax is refused when the run starts, including optional chaining (a?.b), ??, and catch without a variable. Timers, promises, async and outside libraries are not available. Maths adds helpers such as Math.highest, and arrays have the usual built-in methods, such as map, filter and reduce.