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.