Strategies – Framework [node.js setup]

> mkdir -p taapi-strategies/strategy

> cd taapi-strategies/strategy

Now let’s initiate a NodeJS project, and install TAAPI.IO Strategies.

> npm init

> npm install --save taapi-strategies

Directory / files Structure

Below you’ll see the folder and files the framework expects.

taapi-strategies/
|-- strategy/
    |-- node_modules
    |-- bot/
    |   |-- README.md
    |   |-- State_<your-state-name>.js
    |   |-- State_start.js
    |   |-- State.js
    |   |-- strategy.yml
    |   
    |-- src/
    |   |-- index.js
    |
    |-- config.yml
    |-- package-lock.json
    |-- package.json

At this point, you should already see the node_modules, package-lock.json and package.json files and folders.

Lets create the remaining items.

> touch config.yml # This creates the config file

> mkdir bot # Create bot folder
> mkdir src # Create src folder
> cd src
> touch index.js # Create index file in src folder

And boot up our Code IDE

> code .

Paste in configuration.

server:
    env: "dev"
    host: "localhost"
    port: 3002
    debugMode: true

api:
    corsOrigin: "*"
    user: "user"
    pass: "pass"

database:
    namePrefix: "taapi_strategies_"
    name: "${BOT_ID}"
    host: "mongo"
    port: 27017
    user: ""
    pass: ""
    protocol: "mongodb"
    collectionPrefix: ""
    replSetName: null
    sslValidate: false
    sslCertFilename: null

exchange:
    id: "binancefutures"
    name: "Binance Futures"

logging:
    errors: true
    stateChanges: true
    ta: false

uptimerobot:
    heartbeatUrl: ""

taapi:
    secret: ""

notifications:
    slack:
        webhookUrl: null
        channel: "bot"
        username: "TAAPI.IO Strategies"
        icon: "information_source"

    telegram:
        bot_key: null

Important: Dont forget to paste in your TAAPI.IO Secret above!

Paste in index file content

// Require taapi-strategies
const TaapiStrategies = require('taapi-strategies');

// Create a new instance of taapi-strategies
const app = new TaapiStrategies.default();

// Start the app
app.start({
    "start-bot": true, // Boots up the Finite State Machine
    "start-api": true  // Boots up a REST API so that we can talk to our bot
});

We’re now done with the main installation and setup. Now let’s get going with our strategy.

Strategy

We will now implement whats with a fancy name, called a “Finite State Machine“. This is a common implementation of the “State Design Pattern“, and the Refactoring Guru did a good job describing this. Our MongoDB database is then used to keep track of the states we’re in.

So in a nut-shell, we have here 3 states well described by their names:

  • Start – This is the start state when the program boots up the first time
  • Long – We’re invested in a long position
  • Short – We’re invested in a short position

And the transitions (lines) describe how we move between states.

This “machine logic” is applicable for every asset we’d like to analyse and potentially trade. So say that we’re looking at BTC/USDT on the 1 hour timeframe from Binance, we can easily analyse the 2 EMAs.

In our bot folder, let’s create 6 files:

  • README.md – A Markdown description of your bot
  • State.js # All state implementations will inherit from this file
  • State_start.js
  • State_long.js
  • State_short.js
  • strategy.yml – Configuration for this unique strategy

Here’s the implementation / content for each file. We will name this strategy after one of the moons of Mars: “Phobos

# Phobos

## Description

Simple bare bone EMA crossover strategy.

Scans the 'shortlist' for EMA crossover setups, long and short.

| Property    | Value                 | Notes                                  |
|-------------|-----------------------|----------------------------------------|
| Timeframe   | 1h                    |                                        |
| Risk/Reward | N/A                   |                                        |
| Target      | Reverse EMA Crossover |                                        |
| Stoploss    | Reverse EMA Crossover |                                        |

const BotState = require("taapi-strategies/dist/fsm/BotState.js").default;

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

        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;
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;
const State = require("./State.js");

class State_long 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}`);
        }

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

            if(ta.ema_9 < ta.ema_20) {

                // Exit position
                this.exitPosition().then( exitPositionResult => {

                    // Verify that whole position is closed
                    if(exitPositionResult.success) {

                        // Enter new opposite 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_long;

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

class State_short 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) {

                // Exit position
                this.exitPosition().then( exitPositionResult => {

                    // Verify that whole position is closed
                    if(exitPositionResult.success) {

                        // Enter new opposite 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");
                            }
                        });
                    }
                });                
            }
        });

    }
}

module.exports = State_short;
bot:
    id: "phobos"
    fsm:
        isEnabled: true
        tradeDelay: 1000
        tick:
            schedule: "15 */5 * * * *"
    scanner:
        isEnabled: false

trading:
    liveEnabled: false

Please go through the above lines of code, especially the ones for states [start, long, short]. It should be a fairly easy read, even for not so experienced coders.

Run

We’re now ready to run our bot

> node src/index.js

Conclusion / Next steps

This article was mainly intended to show how the bits and pieces are stitched together. There’re several things, which are not “optimal” in the above setup.

So let’s improve this, but actually at the same time, make things a bit easier using a couple of tools. Please visit the next chapter where we will properly set things up cloning a git repository, and running multiple bots with Docker.