Strategies – Framework [development]

To get development started, lets review our basic Exponential Moving Average Crossover strategy. We will refer to this strategy using the name “Phobos”, one of the moons of Mars.

StrategyPhobos
SymbolBTC/USDT
Interval1 Hour [1h], latest close
ExchangeBinance Futures
Fast EMA9 period
Slow EMA20 period

When the latest closed fast EMA (9) crosses up and over the slow EMA (20), we will treat this as a bullish signal and thus go LONG, and vice-versa when the EMA9 crosses down below the EMA20, we will close our long position (if any), and enter a new SHORT position.

Engine

FSM

The engine consists of 2 main parts, the actual Finite State Machine, and a Scanner. For this part, we’ll focus on the FSM.

As you can see, there’s a timer that runs off of a Cron Job. In this example, we set our timer to execute 1 second past every 5 minutes. Why 5 minutes when running on the hourly? Well, not really needed, but as we’ll see in the later UI chapter, it’s nice to have data updated a bit more often than every hour.

Have a look at the strategy configuration. This is where we configure this “Tick Schedule” for the FSM.

bot:
    id: "phobos"
    fsm:
        isEnabled: true
        tradeDelay: 1000 # How big a delay between trades in milliseconds
        tick:
            schedule: "1 */5 * * * *"
    scanner:
        isEnabled: false

trading:
    liveEnabled: false

Now, at every 1 second past every 5 minutes [00:00:01, 00:05:01, 00:10:01, 17:40:01 etc…] the timer kicks in, and fetches all active trades for this strategy in the database and starts to loop through these. Every trade consists of a [symbol, interval, state, among other things]. Thus, if say BTC/USDT is in a long trade, every time the timer is triggered, the code in State_long.js is then executed. And note the above tradeDelay. If you’re trading multiple assets in this same strategy, the FSM engine waits 1000ms before executing the next trade (or symbol). So say you’re also trading Litecoin to the Dollar, then this LTC/USDT symbol will execute at 2 seconds past every 5 minutes, thus respecting your TAAPI.IO rate-limits.

We’ll now go through the implementation of each state, starting with the parent State class

// Require the base framework state class
const BotState = require("taapi-strategies/dist/fsm/BotState.js").default;

// Create a new state class
class State extends BotState
{
    // Constructor
    constructor(trade, config, database, order) {

        // Call parent constructor
        super(trade, config, database, order);

        // Get current price
        this.addCalculation("price", trade.interval, `price`);

        // Get latest closed EMA 9 value
        this.addCalculation("ema", trade.interval, "ema_9", { period: 9, backtrack: 1 });

        // Get latest closed EMA 20 value
        this.addCalculation("ema", trade.interval, "ema_20", { period: 20, backtrack: 1 });
    }

    async tick() {
        throw new Error("Method 'tick()' must be implemented!");
    }
}

module.exports = State;

What happens here is that the first line requires or includes the entire framework, where all tools, functions etc… are located. We then create a new class and extend this to the framework.

The constructor part of the code, is what’s called and run every time a new instance of the class is created. So the first ‘super’ function call is to let the framework know about our trade info, configuration, database and order. Next we’re adding 3 indicators to a bulk call. If your TAAPI.IO plan allows for Multiple Constructs, you can even add other assets / timeframes in the same request.

The tick() function simply tells that the child class must implement this tick() function.

Start

const State = require("./State.js");

class State_start extends State
{
    constructor(trade, config, database, order) {

        super(trade, config, database, order);
    }

    async tick() {

        if(this.config.server.debugMode) {
            console.log(`Tick - ${this.trade.state}:${this.trade.symbol}:${this.trade.interval}`);
        }

        this.executeBulk().then( ta => {

            if(ta.ema_9 > ta.ema_20) {

                // Enter long position
                this.enterPosition("LONG", ta.price).then( enterPositionResult => {

                    // Verify that whole position is filled and take profit and stoploss orders are placed
                    if(enterPositionResult.success) {
                        this.changeState("long");
                    }
                });

            } else if(ta.ema_9 < ta.ema_20) {

                // Enter short position
                this.enterPosition("SHORT", ta.price).then( enterPositionResult => {

                    // Verify that whole position is filled and take profit and stoploss orders are placed
                    if(enterPositionResult.success) {
                        this.changeState("short");
                    }
                });
            }

        });
    }
}

module.exports = State_start;

Scanner

Conclusion