Guide

Prologue

Quickstart

The easiest way to get started is to clone the example repo. There you'll find a minimal setup for running a very simple strategy. If you'd rather set it up yourself – follow along below.

Step 1. Install DevAlpha

Install using npm.

npm install devalpha

Also, create a new file: index.js. Open it up in your favorite editor and get to work.

Step 2. Setup your Feeds

DevAlpha allows you to pipe in any kind of data, not just OHLC-bars. Though in this example, we'll just use a sequence of fake closing prices.

Let's create a feeds object which we will later give to DevAlpha for processing. The keys will be used as event names in your strategy, so choose wisely. In the Events reference you'll find a list of reserved event names which you cannot use.

// index.js
import { createTrader } from "devalpha"

const feeds = {
  myClosingPrices: [
    {
      timestamp: 1500000000000, // 2017-07-14
      close: 100
    },
    {
      timestamp: 1500086400000, // 2017-07-15
      close: 100
    },
    ...
  ]
}

All feed events must have a timestamp property. If not, DevAlpha will simply skip the event. For more advanced setups such as reading from CSV, cleaning data, or using recurrence schedules to run your strategy at regular time intervals, check out the Complex Feeds section.

Step 3. Define your Strategy

The strategy function will be called each time an event occurs. This includes the built-in events such as @@devalpha/ORDER_FILLED.

// index.js
const hasPosition = false
const strategy = (context, action) {

  // We"re only interested in events coming from our feed
  // of closing prices
  if (action.type === "myClosingPrices") {

    // Hmm...
    const signal = Math.random()

    // Place sell order if signal is weak
    if (signal < 0.3 && hasPosition) {
      context.order({
        identifier: "MSFT",
        price: action.payload.close,
        quantity: -1000
      })
      hasPosition = false

    // Place buy order if signal is strong
    } else if (signal > 0.7 && !hasPosition) {
      context.order({
        identifier: "MSFT",
        price: action.payload.close,
        quantity: 1000
      })
      hasPosition = true
    }
  }
}

The context object contains three functions:

  • state(): Get the portfolio state object.
  • order(): Place an order.
  • cancel(): Cancel an order.

Read more about context in the Strategy section.

Step 4. Run it all

Having defined our feeds and our strategy, we supply it to the imported createTraderfunction. The createTrader function returns an unconsumed stream, which means that we have to consume it somehow.

Generally speaking there are three ways of going on about doing this.

  • createTrader(...).each((x) => console.log(x)): Consumes the stream, and runs the passed function for every item.
  • createTrader(...).done((x) => console.log(x)): Consumes the stream, and runs the passed function when the stream ends.
  • createTrader(...).resume(): Consumes a stream and does nothing else. Streams are really powerful, and there are tons of other cool things we can do with them. You'll learn more about them in the Using Streams section.

For now though, we'll settle with a simple done() and be done with it.

// index.js
createTrader({
  feeds
}, strategy)
.done((x) => {
  console.log("Finished backtest!")
})

Step 5. Activating the Dashboard

In order to visualize your backtest, you need to send the data to your browser.

The data never leaves your computer. It is only passed locally, from the Node process to your browser. Activate the dashboard by setting active: true, like so:

// index.js
createTrader({
  feeds,
  dashboard: {
    active: true
  }
}, strategy)
.done((x) => {
  console.log("Finished backtest!")
})

Finally, let's run the code:

$ node index.js
Waiting for DevAlpha...

Open up the backtester and click "Run Backtest".

If everything worked out as expected you should be looking at a beautiful charts and valuable metrics describing the performance of the strategy.

If not, feel free to create an issue at GitHub.

Next Steps

There's a lot more to you can do with DevAlpha.

  • Read more about how to use streams
  • Learn how to parse data from a CSV-file
  • Get the most out of backtesting

Using Streams

What is a stream?

A stream has a source which produces data and a consumer which listens to and reacts to the data. The source will not push any data unless it has at least one consumer, and it will not push data faster than its consumer can handle.

In-between the source and the consumer, we have a multitude of opportunities to transform the stream in certain ways. We could for example divide the stream into two, so that we can have two consumers. Or more.

We could also for example edit the data coming from the source, so to make it easier for the consumer to handle.

Lastly, note that when a stream has been consumed, you cannot consume it again. This means that if you would like for a stream to have multiple consumers, you have to either fork it or observe it. You can find more information about forking and observing here.

Node.js Streams

Perhaps you've seen them appear in the form of process.stdin or process.stdout. Or you might have used the native fs.createReadStream function. The latter example will read contets of a file in chunks and emit an event for each chunk. When the end is reached, the stream is closed.

We can create something similar, but instead of reading from files we can use arrays, promises or a custom function of our liking, as we'll see below.

Highland

Streams are a built-in concept in Node.js, but they can sometimes be a little awkward to work with. To make Streams easier to handle, there are helper libraries such as Highland. The Highland library is used extensively in the DevAlpha source code.

Below is an example of how we can create a Stream using Highland. The source of our stream will be an array, and the consumer will be a function which logs each item to a console.

// Use the array [1, 2, 3, 4] as a source.
const source = [1, 2, 3, 4]

// Create a stream from the source.
const stream = _(source)

// Consume the stream using the `each`-method
stream.each((x) => console.log(x))

The _(source) constructor is called a Stream Constructor. You supply it with whatever you want the source of your stream to be, and it returns an unconsumed stream.

It is a good idea to get comfortable with Highland. Check out the Complex Feeds section for a nice example of how we can utilize Highland to read and parse CSV files.

Basics

Strategy

Each time DevAlpha receives an event, it will call your strategy function with the current context (that is, the state of your portfolio), and the action (that is, the actual event).

const strategy = (context, action) => {
  if (action.type === "myEvent") {
    context.order({
      identifier: "GOOG",
      price: 100,
      quantity: 1000
    })
  }
}

Context

The context object consists of three functions. We'll go through each one below.

State

The context.state() function returns the current portfolio state. The portfolio state contains information about placed orders, how much commission you've paid, what positions you have, their total value, etc.

Check out the API Overview for more information on what you can find in the state.

Order

By calling the context.order() function you can execute an order. The function takes an object as parameter, which contains information about your order. Below are some examples.

// Place a buy order
context.order({
  identifier: "GOOG",
  quantity: 100,
  price: 100
})
// Place a sell order
context.order({
  identifier: "AAPL",
  quantity: -100,
  price: 100
})

In the next major version of DevAlpha you will also be able to place percentage orders and market orders. The identifier property is required, and since DevAlpha currently only supports limit orders, so is the quantity and the price property.

Cancel

Lastly we have the context.cancel() function which simply cancels an order. The function takes an order id as parameter.

Order ids are generated when the order is created by DevAlpha, and so you retrieve order ids by listening to the ORDERCREATED event or the ORDERPLACED event.

You can also retrieve order ids by using the context.state().order property.

Note that when performing real time trading, orders might be filled before you"re able to cancel them.

Action

The action object consists of two properties: type and payload. The type is a string denoting the event type. The event types can be either internal ones, such as those in the Events reference, or they can come from the feeds, as defined by you. Read more about creating feeds in the Basic Feeds section.

The payload is guaranteed to contain a timestamp property, which is a millisecond unix timestamp describing when the event occured. Other than that the payload might contain different properties depending on the event type. There's a list of properties for each internal event type in the Events reference.

Basic Feeds

One of the greatest features in DevAlpha is that it is you (the developer) who supplies the data. This is done using the feeds property in the DevAlpha configuration. When DevAlpha receives the feeds, it will try to convert each one into a stream (unless it already is).

Creating Feeds

Let's begin by looking at some examples.

import _ from "highland"
import { createTrader } from "devalpha"

createTrader({
  feeds: {
    myArray: [{ timestamp: 0, price: 100 }, ...],
    myPromise: fetch("https://..."),
    myGenerator: _((push, next) => { ... }),
    myEventEmitter: _(eventEmitter, "prices"),
    myFileStream: _(fs.createReadStream("prices.csv")),
    myStdInStream: _(process.stdin),
    ...
  }
}, (context, action) => {
  if (action.type === 'myArray') {
    ...
  } else if (action.type === 'myFileStream') {
    ...
  }
})

There are a ton of different ways of creating feeds (as you can see in the example above). The only requirement while creating feeds is that each event in your feed contains a timestamp property, otherwise it will be skipped.

In the Complex Feeds section you can read more about how to set up feeds.

Event Flow

Backtesting

As long as you have not set backtesting: false in your configuration, DevAlpha is running in backtesting mode. In this case, DevAlpha will take all of the feeds and sort them based on the timestamp property. This means that you can use multiple feeds, from many different sources, and their events will still be run chronologically.

Realtime Trading

If DevAlpha is running in realtime trading mode (that is, you've set backtesting: false in your configuration), it will simply merge all of the feeds and run their events in the order they are recevied, without looking at the timestamp property.

Dashboard

Usage

The DevAlpha dashboard allows you to run and analyze backtests. As a non-registered user, you're able to see all of the trades, commonly user performance metrics, and a chart containing the daily and cumulative performance of your algorithm.

The dashboard can be activated by configuring DevAlpha as so:

const createTrader({
  dashboard: {
    active: true
  }
}, (context, action) => {
  ...
})

Now, open the backtester. At the top left of your screen you should see Connected.

If you do no see the "connected" sign, please open a GitHub issue.

How it works

As stated before, DevAlpha makes heavy use of streams. When activating the dashboard DevAlpha will fork the stream, which splits the stream in two, allowing two consumers to consume the same stream. One forked stream is returned by the createTrader function, just as usual. The other forked stream remains unconsumed until it receives a specific initialization event from the dashboard.

The communication between the DevAlpha node process and the DevAlpha dashboard is made through a socket, locally, on your computer. One fine thing about this is that your code and your data never leaves your computer.

Order Flow

Overview

The order placement mechanism includes some internal middleware, and is divided into three phases: request, placement, and filling.

Request

When you run context.order() in your strategy a ORDER_REQUESTED event is dispatched. The broker middleware snaps up the event, and then validates and formats the details of the order. No network request has been made to the brokerage yet.

Placement

As the broker middleware finishes building the order, it dispatches a new event: ORDER_CREATED, which contains the formatted order.

This notifies the guard middleware, which analyzes the created order and dispatches ORDER_REJECTED if the order is disallowed. This might happen if we've set margin: false in our configuration, and we aren't liquid enough to place the order.

If the guard middleware lets the order through, the broker middleware is notified yet again, but this time around it actually sends a network request to the brokerage.

If the request was accepted, an ORDERPLACED will be dispatched, and you'll now find the order in context.state().orders. If the request was not accepted, an ORDERFAILED will be dispatched.

Filling

If the order is filled, an ORDER_FILLED event is dispatched. If it was partially filled, DevAlpha will update context.state().orders accordingly. If the order was completely filled, DevAlpha will remove it from context.state().orders.

Backtesting vs. Realtime trading

In backtesting mode we want all orders which are requested during one event, to be executed and filled instantly, before the next event is run.

This is in contrast to realtime trading, in which events are dispatched as they arrive, meaning that there might be several other events between an order request and a fill.

Using Guards

The guard middleware has the capability to reject orders which do not fulfill some criterion. There are three properties available for configuration.

  • margin: Should buying on margin be allowed?
  • shorting: Should it be possible to own a negative amount of shares?
  • restricted: A list containing identifiers of restricted instruments.

Here's how we can configure the guard:

const createTrader({
  guard: {
    margin: false,
    shorting: false,
    restricted: ['GOOG', 'AMZN']
  }
}, (context, action) => {
  ...
})

By configuring the guard like this, we will not be able to buy on margin, we will not be able to own a negative amount of shares, and we will not be able to trade GOOG or AMZN.

Advanced

Complex Feeds

Reading data from CSV-files

For our backtests we might want to read data from a CSV-file. We can make use of the built-in fs.createReadStream function for this task.

import * as fs from "fs"

const csvStream = fs.createReadStream("myData.csv")
We can also use a Highland pipeline for parsing the data into a nice format.

import _ from "highland"

const pipeline = _.pipeline(
  _.split(),
  _.map((item) => {
    const parsed = item.split(",")
    return {
      identifier: parsed[0],
      timestamp: new Date(parsed[1]).getTime(),
      open: parseFloat(parsed[2]),
      high: parseFloat(parsed[3]),
      low: parseFloat(parsed[4]),
      close: parseFloat(parsed[5])
    }
  })
)

The pipeline function creates a so called "through stream", which is able to pass data through a series of functions or another stream.

Here's how we use it:

import { createTrader } from "devalpha"

createTrader({
  feeds: {
    myData: _(csvStream).through(pipeline)
  },
  dashboard: {
    active: true
  }
}, (context, action) => {
  if (action.type === "myData") {
    ...
  }
})

Realtime Trading

If you have created a custom broker, going into realtime trading is as simple as setting backtesting: false in your configuration.

Recipes

Example Strategy

An example strategy, utilizing the well-known Dart Throwing Monkey-technique can be found at https://github.com/devalpha-io/devalpha-example.