Writing Your Own AutoTrade Strategy

Utilizing the AutoTrade Platform to its Fullest

This is Part 3 of a three part article series. In the last two articles, we discussed the general concepts behind AutoTrade and how to set up your development environment. We also built a miniature strategy for displaying the current price for the chosen contract using side effects. In this final part of the series, we will be writing a brand new strategy based on Relative Strength Index.

If you’ve been following along with the AutoTrade series, you’ll be fairly well acquainted with Strategies and how the AutoTrade system works. We process data and take cues from indicators to change robot states and enact side effects (like placing an order, or drawing to the console). We also can return a new state when we get other events from the API, like Props or UserSync events, which are useful for tracking your positions and P&L. Now that we understand the core concepts of AutoTrade, let’s try to write our very own ‘real’ strategy.

The Concept

We’re going to build a robot that places orders based on RSI. RSI, or relative strength index, is a popular indicator that gauges whether the price movement is overbought or oversold . The RSI value ranges from 0 to 100. Generally, a contract is considered overbought when the RSI value pushes above 70, and that contract is considered oversold when the RSI value drops below 30. What this means is that we will want to buy when the product is oversold, and sell when it is overbought.

Let’s scaffold out a new Strategy:

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

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

    init(props) {
        return {}
    }

    next(state, [event, payload]) {
        switch(event) {
            case TdEvent.Chart: {
                console.log('got chart 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 = { RsiStrategy }

All the basics of our strategy are here, but we’ll be replacing the console logs with calls to handler functions.

The first thing we should do with our strategy is define its construction parameters. We can do that by adding fields to the params static object.

static params = {
    ...super.params,
    period:     	'int',
    orderQuantity:  'int',
}

We only need a few parameters for the RSI Strategy. period is the number of bars to consider when calculating the RSI, and orderQuantity is the number of contracts to buy or sell when a signal is received.

Next we’ll want to define the internal state of our Strategy. We can do this using the init function:

init(props) {
    return {
        mode: 			LongShortMode.Watch,
        strengthIndex: 	relativeStrengthIndex(props.period),
        hlv: 			highLowVariance(props.period),
        product: 		null,
        position: 		null,
        realizedPnL: 	0,
        buffer: 		new DataBuffer(BarsTransformer)
    }
}

Since this strategy relies on relative strength, I’ve included the relativeStrengthIndex indicator as a part of the state. Also note hlv — the highLowVariance indicator. It will help us determine where to place our dependent brackets orders by considering the market variance over the given period. We’ll also need to track the product and position for the contract we’re currently analyzing and trading. So that we can see our realized profits and losses I’ve also included the realizedPnL field, which we will update on cashBalances Props events.

Handling Events

For our strategy to do anything, we’ll need to react to API events. There are many events we can catch using AutoTrade, but we will focus on just the few that we need. The very first event that your socket will receive is the UserSync event. You can use UserSync events to do things like find out what positions you already hold at strategy startup, or find existing realized P&L. Let’s write a UserSync handler for the RSI strategy. Create a new file in the rsiStrategy folder called onUserSync.js .

const { LongShortMode } = require("../common/longShortMode")

const onUserSync = (prevState, {data, props}) => {

    const { contract } = props
    const { positions, products, cashBalances } = data
    
    let product 	= products.find(p => contract.name.startsWith(p.name))
    const position 	= positions.find(pos => pos.contractId === contract.id)
    let realizedPnL = cashBalances[0]?.realizedPnL || 0

    return {
        state: {
            ...prevState,
            mode: 
                position && position.netPos > 0 ? LongShortMode.Long 
            :   position && position.netPos < 0 ? LongShortMode.Short 
            :   /*else*/                          LongShortMode.Watch,
            product,
            position,
            realizedPnL
        },
        effects: [{ event: 'rsi/draw' }]
    }
}

module.exports = { onUserSync }

We’ve discussed the signature of next and family in detail already, but I’ll review handlers’ signatures again.

type EventHandler = (prevState, { data, props }) => State

It looks almost exactly like next , except that we don’t consider the event string since we already know the event we’re handling. This leaves us with just the payload parameter which we destructure into data and props . In the onUserSync event handler, we are concerned about positions , products , and cashBalances . These are fields that exist on the data object passed to this function. These fields are specific to the UserSync event type.

Note: Look closely at the data here — every field is an array, so be sure to treat them as such!

We wrap up the UserSync handler by simply adding the fields we care about to the state object. This will ‘overwrite’ existing fields with the same name. I say ‘overwrite’ but what really is happening is that we are returning a brand new state object. The whole point of this event is to grab data of interest that may already exist prior to running the chosen Strategy.

Don’t forget to take note of the rsi/draw effect — we’ll use it later on to trigger console UI drawing for our strategy.

Now that we understand UserSync, let’s write another event handler. This time we’ll use the Props event, so create a new file called onProps.js .

const { EntityType } = require("../strategy/entityType")
const { LongShortMode } = require("../common/longShortMode")

const onProps = (prevState, {data, props}) => {
    const { contract } = props
    const { entityType, entity } = data
    
    if(entityType === EntityType.Position && entity.contractId === contract.id) {
        const { netPos } = entity
        return {
            state: {
                ...prevState,
                mode: 
                    netPos > 0  ? LongShortMode.Long
                :   netPos < 0  ? LongShortMode.Short
                :   /*else*/      LongShortMode.Watch,
                position: entity
            },
            effects: [
                {
                    url: 'product/find',
                    data: {
                        name: contract.name.slice(0, contract.name.length - 2)
                    }
                }
            ]
        }
    }

    if(entityType === EntityType.CashBalance) {

        const { realizedPnL } = entity

        return {
            state: {
                ...prevState,
                realizedPnL
            },
            effects: [{ event: 'rsi/draw' }]
        }
    }

    return { state: prevState }
}

module.exports = { onProps }

Props events are all about updating user data. Anything associated with your user will update via Props event. Provided is the EntityType enum-like object to help you figure out what entities you are concerned with. In our case, we care about updates to Positions for our chosen contract and CashBalances for our user. These will allow us to do things like calculate realized and open P&L.

Also take note that we return an effect when we get a position update. This effect says look at the API endpoint for product/find . We also must include the contract we’re trading in the data — we can get that from the props field available in any of the event handler functions. Placing the effect into the state machine doesn’t do anything on its own however; we will need to also catch the response to this endpoint, product/found . Let’s discuss doing that now.

product/found actually has an event handler that is a common function. This means we can import the built-in version and reuse it wherever we want to simply add product to our Strategy internal state. We need to add this dependency at the top of our file:

const { onProductFound } require("../common/onProductFound")

Then we simply add onProductFound to our message-discriminating switch statement in RsiStrategy ’s next function.

next(prevState, [event, payload]) {
    switch(event) {
        case TdEvent.Chart: {
            return onChart(prevState, payload)
        }

        case TdEvent.Props: {
            return onProps(prevState, payload)
        }

        case TdEvent.UserSync: {
            return onUserSync(prevState, payload)
        }
        
		//this one
        case TdEvent.ProductFound: {
            return onProductFound(prevState, payload)
        }

        default: {
            return { state: prevState }
        }
    }
}

I’ve also added the other handler functions to the switch. When we extract out the switch logic to the handler functions (like we have been), we get fairly clean code. But we still haven’t discussed the Chart event handler.

Create another new file, this time called onChart.js . The Chart event handler is very important — it will determine whether or not our Strategy should initiate an order! Let’s take a look at how we can do this. Let’s start with a skeleton:

const onChart = (prevState, {data, props}) => {

    const { mode, buffer, hlv, strengthIndex } = prevState
    const { contract, orderQuantity } = props
           
    const buffData = buffer.getData()

    const lastHlv = hlv.state
    const lastRsi = strengthIndex.state

    const { variance } = hlv(lastHlv, buffData)
    const { overbought, oversold } = strengthIndex(lastRsi, buffData)

	return { 
        state: prevState,
        buffer: buffer.concat(data),
        effects: [{ event: 'rsi/draw' }]
    }
}

When we set up the state, we made all of the variables that I’ve destructured from prevState available to us. This gives us access to the buffer , let’s us know the current mode of the Strategy (Long, Short, or Watch), and allows us to easily grab our state indicators. In this case we are using hlv and strengthIndex which refer to High-Low Variance (basically just a number representing the max price range over a period) and Relative Strength Index (RSI). From these indicators we can get the variance and the overbought or oversold values. We’re going to use overbought to signal a sell, and oversold to signal a buy.

if(mode === LongShortMode.Watch && overbought) {
    return {
        state: {
            ...prevState,
            mode: LongShortMode.Short,
            buffer: buffer.concat(data)
        },
        effects: [
            {
                url: 'orderStrategy/startOrderStrategy',
                data: {
                    contract,
                    action: 'Sell',
                    brackets: [shortBracket],
                    entryVersion,
                }
            },
            { event: 'rsi/draw' }
        ]
    }
}

if(mode === LongShortMode.Watch && oversold) {
    return {
        state: {
            ...prevState,
            mode: LongShortMode.Long,
            buffer: buffer.concat(data)
        },
        effects: [
            {
                url: 'orderStrategy/startOrderStrategy',
                data: {
                    contract,
                    action: 'Buy',
                    brackets: [longBracket],
                    entryVersion
                }
            },
            { event: 'rsi/draw' }
        ]
    }
}

We will use our data to start a simple bracket strategy. If we are in Watch mode, (not Long or Short) and we get an overbought or oversold signal from our RSI indicator, then we should start the strategy. Here is our actual brackets data:

const roundContract = (coeff, divisor) => {
    return +(Math.round(
      (coeff*variance/divisor)/contract.providerTickSize) / (1/contract.providerTickSize)
	)
}

const longBracket = {
    qty: orderQuantity,
    profitTarget: roundContract(1, 1.25),
    stopLoss: roundContract(-1, 5),
    trailingStop: true
}

const shortBracket = {
    qty: orderQuantity,
    profitTarget: roundContract(-1, 1.25),
    stopLoss: roundContract(1, 5),
    trailingStop: true
}

const entryVersion = {
    orderQty: orderQuantity,
    orderType: 'Market',
}

We have a bracket for Long mode and a bracket for Short mode. We determine profit and loss targets using the market variance over the period we are concerned about. For completion’s sake, roundEps will help us round to the number of decimals expected by the contract (so you can still trade tiny-valued contracts like QG with lots of decimals)

This will actually work all on its own already. But we really ought to make some kind of GUI so we know what’s happening.

Making a GUI

When we returned the rsi/draw effect from our event handlers, we set up a hook that we can catch to perform our UI drawing. It’s actually really easy to do, too. The first thing we need to do is write a drawing effect. Make a new file called drawEffect.js :

const calculatePnL = require("../../utils/calculatePnL")
const drawToConsole = require("../../utils/drawToConsole")

const drawEffect = (state, action) => {
    const [event, payload] = action

    if(event === 'rsi/draw') {
        const { props } = payload
        const { contract } = props
        const { product, position, mode, buffer, strengthIndex, realizedPnL } = state

        drawToConsole({
            mode,
            contract: contract.name,      
            netPos: position?.netPos || 0,
            rsi: strengthIndex.state.rsi,
            'p&l': position && position.netPos !== 0 && product 
                ? `$${
                    calculatePnL({
                        price: buffer.last()?.price || buffer.last()?.close || 0, 
                        contract,
                        position,
                        product,
                    }).toFixed(2)
                }` 
                : '$0.00',
            realizedPnL: `$${realizedPnL.toFixed(2)}`
        })    
    }

    return action
}

module.exports = { drawEffect }

Like any effect, we must return an action no matter what, so the last portion of the function is to return the passed action. However, on the occasion that the passed action has the rsi/draw event type we will perform our side effect.

We use two helpers to draw up our GUI — drawToConsole foremost, and calculatePnL to get our real-time open P&L. drawToConsole let’s us use any object to draw a list of labeled values to the console.

We have one last thing to do before our draw side effect works — register it as middleware for this Strategy:

init(props) {
  	//this part registers our draw middleware.
    this.addMiddleware(drawEffect)
    return {
        mode:           LongShortMode.Watch,
        strengthIndex:  relativeStrengthIndex(props.period),
        hlv:            highLowVariance(props.period),
        product:        null,
        position:       null,
        realizedPnL:    0,
        buffer:         new DataBuffer(BarsTransformer)
    }
}

Finally, we can run this Strategy and see how it performs. It does decently over 30 minute and hour bars with a 14-bar period, but it can obviously be greatly improved. Here’s a picture of what the strategy should look like in action:

This Strategy is merely another example of what’s possible with AutoTrade. Some ideas for extending this strategy could be to consider volume or cumulative delta volume, or possibly add moving averages to gauge price vs. RSI. The sky is the limit, and anything JavaScript can do can be worked into the AutoTrade system.

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