Introducing AutoTrade

This is part one of a three part series about using the Tradovate AutoTrade framework to create auto-trading strategies. This article will outline broader concepts about the framework and is intended as an introduction to using the software. We will go into greater detail about the internal workings of Tradovate AutoTrade later in the series.

Developing a quality trading strategy that works for you can take years of research and trial-and-error. Once you’ve found something that works, you’ll wish that you could automate it. Now that Tradovate offers its API to everyday users, you can do just that.

Many traders take cues from indicators — depending on the complexity of the conditions behind these cues and indicators, it is usually possible to automate them. Automation can be a great way to take the emotional pressure out of day trading, especially if you already have a pre-determined set of cues or ‘rules’ that you already follow. The robot also doesn’t need to eat, sleep or take breaks, giving it a great efficiency advantage compared to a human trader.

Note : I will assume the reader has at least a working knowledge of NodeJS and JavaScript, if not I’d suggest starting with those before you continue with this guide.

To build our futures-trading robot, you will need to get access to the Tradovate API. To access the API you will need to get a few things in order first:

  • Be a registered Tradovate user with non-social credentials (meaning you didn’t sign up using Google and have a username/password credential pair).
  • Have a LIVE account with at least $1,000 in equity.
  • Generate an API Key.

You can get a detailed guide on how to tackle this task here.

Once you’re setup with your Tradovate API access, go clone the example-api-trading-strategy repository. Once you’ve gotten the repo, open a terminal and navigate to the containing folder. Take care of installing the dependencies with yarn :

yarn install

And, if you update your environment variables, you can give the robot a test run using:

yarn start

I’ll explain how to setup your environment variables as we cover the basics of how the platform works.

General Concepts

Tradovate AutoTrade is an event-driven auto trading framework. The AutoTrade application catches events sent from the Tradovate API and allows you, the developer, to decide how to process those events. It takes care of managing the real-time subscriptions so that you can focus on responding to the events. The Strategy class can catch any event that the API can produce, from DOM or Chart updates to User Data updates, order modifications, and more. To see the primary event types, look in the strategy template file, yourCustomStratey.js . The main events are listed in next in the switch statement as TdEvent s.

Props type events have additional subtypes, as these consist of the create and update events on entities that your user cares about.

Let’s configure our robot and get a closer look. Open index.js . There are three main sections to this file — the imports, the environment variable declarations, and the main entry point function. The environment variables are right near the top, below the large commented section.

const { acquireAccess }      = require("./utils/acquireAccess")
const { configureRobot }     = require("./utils/configureRobot")
const { CrossoverStrategy }  = require("../strategies/crossoverStrategy")
const { YourCustomStrategy } = require("../strategies/yourCustomStrategy")

//ENVIRONMENT VARIABLES ------------------------------------------------------

//Set some process variables for ease-of-access. These values will 
//be globally available through the process.env object. This is part 
//of the configuration of the robot, so be sure to use the correct values here.

//HTTP_URL can be changed to either the demo or live variant. Demo uses your 
//demo account (if you have one)
//USER should be your username or email used for your Trader account
//PASS should be the password assoc with that account

process.env.HTTP_URL    = 'https://demo.tradovateapi.com/v1'
process.env.WS_URL      = 'wss://demo.tradovateapi.com/v1/websocket'
process.env.MD_URL      = 'wss://md.tradovateapi.com/v1/websocket'
process.env.USER        = ''    
process.env.PASS        = '' 
process.env.SEC         = ''
process.env.CID         = 0

//END ENVIRONMENT VARIABLES --------------------------------------------------

const ALL_STRATEGIES = {
    'Crossover Strategy': CrossoverStrategy,
    'Your Custom Strategy': YourCustomStrategy
}

async function main() {

    // // // // // // // // // // // // // // // //
    // Login Section                             //
    // // // // // // // // // // // // // // // //

    await acquireAccess()

    // // // // // // // // // // // // // // // //
    // Configuration Section                     //
    // // // // // // // // // // // // // // // //

    const Strategy = await configureRobot(ALL_STRATEGIES)

    // Once constructed, the strategy will run in an event-driven manner.
    // Each tick will fire the Strategy.tick(prevState, data) function, which
    // will produce the next Strategy state. Each tick event considers the 
    // previous state and the incoming data.
}

main()

The index.js file. Remember to set your environment variables before trying to run the robot!

You’ll need to supply your own Tradovate credentials, security key and application CID. If you followed along with the gaining access guide from above, you should already have these. Take note that you can also switch from demo to live by changing the URLs to their live equivalents.

Next we declare our strategies. As you create your own strategies, you can add them as options to the configuration step by placing them in the ALL_STRATEGIES object.

The main entry function is very straightforward. First we acquire our access token by making a request to the Tradovate REST API. We use the acquireAccess utility function to make it as easy as possible. Next, we call configureRobot , passing it the ALL_STRATEGIES object as a parameter. This function runs the configuration routine based on your chosen Strategy class. It then constructs a new strategy, which will begin an event-driven chain of calls defined by the author of the strategy. Now that we know how to configure the index.js file, let’s give the robot a test run.

Running the Example CrossoverStrategy Class

After you’ve installed your dependencies, you can simply run yarn start from the terminal to start the robot. You’ll be presented with some initial choices and data, such as choosing the account and contract you’d like to trade. Then the configuration routine will run, and the parameters from CrossoverStrategy.params will be requested as user input. Once you are satisfied with your choices, the robot should clear the console and begin showing you live data updates about its status and performance so far.

Hey! Does the configuration part annoy you? I found when testing the robot, running the config each time was a huge pain. If you’re in the same boat, simply comment out the configureRobot call and instantiate your strategy class directly in the main function after acquireAccess . Provide the strategy you are instantiating an object with your chosen parameters hardcoded. The only caveat is that you’ll need to provide every parameter, including the getChart request data, the contract you want to trade, and all the params you define on your strategy subclass, as well. I’d suggest using the askForContract utility to acquire a contract using the console, but dropping the configureRobot part while you’re running many iterations of your strategy.

Extending the Robot

Extending the AutoTrade platform is as easy as defining your own custom Strategy class. The Strategy base class defines functionality and behavior that is shared across all strategy instances. For example, each strategy instance maintains its own subscription to live data from the contract you choose to trade. Conveniently, you don’t have to worry about opening or maintaining those subscriptions, because it’s done for you in the base Strategy class. Open yourCustomStrategy.js and we’ll take a look at how we can hook into AutoTrade’s Tradovate API events.

const { Strategy, TdEvent } = require('../strategies/strategy')

class YourCustomStrategy extends Strategy {
    constructor(params) {
        super(params)	
    }		

    init(props) {
        return {
            //your initial state here!
        }
    }

    next(state, [event, payload]) {
        switch(event) {
            case TdEvent.Chart: {
                console.log('got chart event')
                break
            }

            case TdEvent.DOM: {
                console.log('got DOM event')
                break
            }

            case TdEvent.Histogram: {
                console.log('got histogram event')
                break
            }

            case TdEvent.Quote: {
                console.log('got quote event')
                break
            }

            case TdEvent.UserSync: {
                console.log('got user sync event')
                break
            }

            case TdEvent.Props: {
                console.log('got props event')
                break
            }

            default: {
                return state
            }
        }
    }

    static params = {
        ...super.params,
    }

}

module.exports = { YourCustomStrategy }

We can declare our initial robot state in the init function. You can see that each type of event that can be emitted by the API can be caught in the next function via switch statement. Catching any event is optional, so you will only have to define what you need for the specific strategy you’re developing. You can pass on all the cases you don’t care about by simply returning the existing state in the switch 's default case.

Note: I’d suggest utilizing the concept of function extraction when designing the responses to events. As an example, imagine you place all your strategy code in the switch statement. This is theoretically fine, but it will be very difficult to change it later, or even read it if your strategy becomes large. Instead, consider calling a function from the switch when an event is received, eg. case TdEvent.Chart: return onChart(state, data); . You could even keep these functions in separate files!

Each event type has a different response object shape . Let’s take a look at each:

Quote

{
  "timestamp":"2017-04-13T04:59:06.588Z",
  "contractId":123456,
  "entries": {
    "Bid": { "price":18405, "size":7 },
    "TotalTradeVolume": { "size":4118 },
    "Offer": { "price":18410, "size":12 },
    "LowPrice": { "price":18355 },
    "Trade": { "price":18405, "size":2 },
    "OpenInterest": { "size":40702 },
    "OpeningPrice": { "price":18515 },
    "HighPrice": { "price":18520 },
    "SettlementPrice": { "price":18520 }
  }
}

DOM

{
  "contractId":123456, 
  "timestamp":"2017-04-13T11:33:57.488Z",
  "bids": [ 
    {"price":2335.25,"size":33},
    {"price":2333,"size":758},
    //...
  ],
  "offers": [ 
    {"price":2335.5,"size":255},
    {"price":2337.75,"size":466},
    //...
  ]
}

Chart

For the MinuteBar type:

{
  "timestamp":"2017-04-13T11:00:00.000Z",
  "open":2334.25,
  "high":2334.5,
  "low":2333,
  "close":2333.75,
  "upVolume":4712,
  "downVolume":201,
  "upTicks":1333,
  "downTicks":82,
  "bidVolume":2857,
  "offerVolume":2056
}

And the Tick variety:

{
  "charts": [                   
    {
      "id": 16335,            
      "s": "db",              
      "td": 20190718,         
      "bp": 11917,
      "bt": 1563421179735,
      "ts": 0.25,             
      "tks": [                
        {
          "t": 0, 
          "p": 0,        
          "s": 3,      
          "b": -1,       
          "a": 0,
          "bs": 122,      
          "as": 28,       
          "id": 11768401 
        },
        //...
      ]
    },
    //alternate end of history
    //response object
    {
      "id": 16335,
      eoh: true                
    }
  ]
}

User Sync

See the user/syncRequest response documentation.

Props

Props is an overarching event type for user data updates. Each response is broken into this structure:

{
  	eventType: 'Updated',
  	entityType: 'position',
    entity: {
      	contractId: 123456789,
       	netPos: 0,
        bought: 15,
        sold: 15,
        //...
    }
}

This is an example of a position-updated Props event. Each Props event has the entityType , eventType , and entity fields. You can import an enum-like object EntityType from the strategy.js file that contains valid entity types for sorting your incoming Props messages. If you ever need a reference to what the entity data for a type looks like, refer back to the User Sync response data. A position type entity will look like the items in the positions array, a product type entity will look like the elements in the products array, and so on.

Thinking Functionally

The most important part of the Strategy is the next function. next takes the previous state and some injected dependencies to produce the next state of your robot. You can think of next as a type of reducer function. Consider the standard Array.prototype.reduce function. It works like this:

A sum reducer.

This simple reducer sums an array of numbers starting with seed . But if we look closely at the call to array.reduce , we can see it takes two parameters —

  • The reducer function itself, a function taking a the previous value and b the next value and returning some combination of them, a + b here.
  • The seed item. seed is the starting item. a will be equal to the seed value on the very first iteration of reduce. For a sum , this value should probably be zero.

The robot’s next works much in the same way, but instead of a fixed length list of data (like the array) we are dealing with an incoming stream of data, and our seed value is the return of the init function. In our next function, you can think of state as the a variable, and data from the emitted payload object as the b value. Each incoming message will trigger the reducer to produce a new state for the robot. Let’s look closely at the signature defined in the template file:

next(state, [event, payload]) {...} //=> next state

The first argument, state , is fairly straightforward. It’s the last known state of the robot. The next parameter is a tuple of the event string name and the payload data object that I call the action . We can use the action event and payload to differentiate between what state the robot should be in and what state transitions to make based on the data from payload . We make the static props object available via payload to inject our dependent context.

We need to inject our dependencies to keep our next function pure . That’s why we inject props into the payload object. To say that a function is pure is to say that when given the same input parameters, it will always produce the same output values, and it doesn’t rely on state outside of the function parameters or the function itself to produce those values. So given the same input state and action tuple, next would always produce an identical result state.

Further Reading

We covered the basics and overarching concepts behind the Tradovate AutoTrade framework. But we still need to cover the details of making calls using the Tradovate API, and we haven’t implemented our own custom strategy yet either. We will cover these topics over the course of two additional articles that make up this series.

For more information on using the Tradovate API see our official documentation, or the JS tutorial suite.

Disclaimer: Futures trading and algorithmic trading involve a substantial risk of loss which should be understood prior to trading and may not be suitable for all investors. Therefore, carefully consider whether trading is suitable for you. The information provided in this article is for the sole purpose of education and assistance in making independent investment decisions. Tradovate, LLC has taken reasonable measures to ensure the accuracy of the information contained herein; however, Tradovate, LLC does not guarantee its accuracy, and is not liable for any loss or damage which may arise directly or indirectly from such content or from an inability to access such information, for any delay in or failure of the transmission or the receipt of any instruction or notification in connection therewith. Any recommendations or trading analysis found herein are provided for educational and illustrative purposes only and should not be used in connection with the formation or execution of any trading decisions.

Any opinions, news, research, analyses, prices, reports, graphs, charts, or other information contained herein is provided for informational purposes only and does not constitute investment advice or recommendations. Tradovate, LLC is not liable for any loss or damage, including without limitation, any loss of profit, which may arise directly or indirectly from use of or reliance on any such information. You acknowledge and agree that you bear responsibility for your own investment research and investment decisions, and that Tradovate, LLC shall not be held liable by you or any others for any decision made or action taken by you or others based upon reliance on or use of information or materials obtained or accessed through use of Tradovate, LLC.

Please read carefully the CFTC required disclaimer regarding hypothetical results below.

HYPOTHETICAL PERFORMANCE RESULTS HAVE MANY INHERENT LIMITATIONS, SOME OF WHICH ARE DESCRIBED BELOW. NO REPRESENTATION IS BEING MADE THAT ANY ACCOUNT WILL OR IS LIKELY TO ACHIEVE PROFITS OR LOSSES SIMILAR TO THOSE SHOWN. IN FACT, THERE ARE FREQUENTLY SHARP DIFFERENCES BETWEEN HYPOTHETICAL PERFORMANCE RESULTS AND THE ACTUAL RESULTS SUBSEQUENTLY ACHIEVED BY ANY PARTICULAR TRADING PROGRAM.

ONE OF THE LIMITATIONS OF HYPOTHETICAL PERFORMANCE RESULTS IS THAT THEY ARE GENERALLY PREPARED WITH THE BENEFIT OF HINDSIGHT. IN ADDITION, HYPOTHETICAL TRADING DOES NOT INVOLVE FINANCIAL RISK, AND NO HYPOTHETICAL TRADING RECORD CAN COMPLETELY ACCOUNT FOR THE IMPACT OF FINANCIAL RISK IN ACTUAL TRADING. FOR EXAMPLE, THE ABILITY TO WITHSTAND LOSSES OR TO ADHERE TO A PARTICULAR TRADING PROGRAM IN SPITE OF TRADING LOSSES ARE MATERIAL POINTS WHICH CAN ALSO ADVERSELY AFFECT ACTUAL TRADING RESULTS. THERE ARE NUMEROUS OTHER FACTORS RELATED TO THE MARKETS IN GENERAL OR TO THE IMPLEMENTATION OF ANY SPECIFIC TRADING PROGRAM WHICH CANNOT BE FULLY ACCOUNTED FOR IN THE PREPARATION OF HYPOTHETICAL PERFORMANCE RESULTS AND ALL OF WHICH CAN ADVERSELY AFFECT ACTUAL TRADING RESULTS.

1 Like