Statistical arbitrage framework help
-
I'm trying to simply create a framework where I can follow the order placement and execution for a statistical arbitrage strategy.
It is not working as expected leading to incorrect order executions. I would appreciate it if anyone can provide me with some assistance.from __future__ import (absolute_import, division, print_function, unicode_literals) import datetime # For datetime objects import os.path # To manage paths import sys # To find out the script name (in argv[0]) import backtrader as bt # Create a Stratey class TestStrategy(bt.Strategy): def log(self, txt, dt=None): ''' Logging function for this strategy''' dt = dt or self.data0.datetime.date(0) tt = self.data.datetime.time(0) print('%s, %s, %s' % (dt.isoformat(), tt.isoformat(), txt)) def __init__(self): # Keep a reference to the "close" line in the data[0] dataseries self.dataclose1 = self.datas[0].close self.dataclose2 = self.datas[1].close # To keep track of pending orders and buy price/commission self.order = None self.orderid = None self.buyprice1 = None self.buyprice2 = None self.buycomm = None # Add a MovingAverageSimple indicator self.sma = bt.indicators.SimpleMovingAverage( self.data0, period=15) def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enough cash if order.status in [order.Completed]: if order.isbuy() and self.position.size > 0: self.log( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' % (order.executed.price, order.executed.value, order.executed.comm, self.position.size)) self.buyprice = order.executed.price self.buycomm = order.executed.comm elif order.issell() and self.position.size < 0: self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' % (order.executed.price, order.executed.value, order.executed.comm, self.position.size)) self.sellprice = order.executed.price self.sellcomm = order.executed.comm elif order.issell() and self.position.size == 0: self.log( 'CLOSING LONG POSITION, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' % (order.executed.price, order.executed.value, order.executed.comm, self.position.size)) elif order.isbuy() and self.position.size == 0: self.log( 'CLOSING SHORT POSITION, Price: %.2f, Cost: %.2f, Comm: %.2f, Size: %.2f' % (order.executed.price, order.executed.value, order.executed.comm, self.position.size)) else: # Sell self.log('ERROR - Something went wrong!') self.bar_executed = len(self) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') self.order = None def notify_trade(self, trade): if not trade.isclosed: return self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) def next(self): # Check if an order is pending ... if yes, we cannot send a 2nd one if self.orderid: return # Check if we are in the market if not self.position: # Not yet ... we MIGHT BUY if ... if self.dataclose1[0] > self.sma[0]: # BUY, BUY, BUY!!! (with all possible default parameters) self.log('BUY CREATE, %.2f' % self.dataclose1[0]) self.order = self.buy(data=self.data0) self.log('SELL CREATE, %.2f' % self.dataclose2[0]) self.order = self.sell(data = self.data1) # Keep track of the created order to avoid a 2nd order else: if self.dataclose1[0] < self.sma[0]: # SELL, SELL, SELL!!! (with all possible default parameters) self.log('CLOSE series1, %.2f' % self.dataclose1[0]) self.close(self.data0) self.log('CLOSE series2, %.2f' % self.dataclose2[0]) self.close(self.data1) # Keep track of the created order to avoid a 2nd order if __name__ == '__main__': cerebro = bt.Cerebro(writer=True) cerebro.addstrategy(TestStrategy) modpath = os.path.dirname(os.path.abspath(sys.argv[0])) datapath1 = os.path.join(modpath, 'SPI#5min.txt') datapath2 = os.path.join(modpath, '@ES#.txt') #Create a data feed data0 = bt.feeds.GenericCSVData( dataname=datapath1, # Do not pass values before this date fromdate=datetime.datetime(2019, 5, 25), # Do not pass values after this date todate=datetime.datetime(2019, 5, 27), timeframe = bt.TimeFrame.Minutes, compression = 5, nullvalue = 0.0, dtformat = ('%d/%m/%Y'), tmformat = ('%H:%M:%S'), datetime=0, time=1, open=2, low=3, high=4, close=5, volume=6, openinterest=-1 ) # Add the 1st data to cerebro cerebro.adddata(data0) # Create the 2nd data data1 = bt.feeds.GenericCSVData( dataname=datapath2, # Do not pass values before this date fromdate=datetime.datetime(2019, 1, 1), # Do not pass values after this date todate=datetime.datetime(2019, 5, 27), timeframe = bt.TimeFrame.Minutes, compression = 5, nullvalue = 0.0, dtformat = ('%d/%m/%Y'), tmformat = ('%H:%M:%S'), datetime=0, time=1, open=2, low=3, high=4, close=5, volume=6, openinterest=-1 ) # Add the 2nd data to cerebro cerebro.adddata(data1) cerebro.broker.setcash(100000.0) cerebro.addwriter(bt.WriterFile, csv=True, out="trade_history.csv") print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) cerebro.run() print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
-
Maybe you can share more detail on what you expect and what is wrong?
From what I can see the orders are executed, so seems nothing wrong happens.PS sorry didn't get my mind-reading skill yet.
-
Hi ab_trader
Thanks for your response.
I am expecting that once a trade signal is generated in the def next() method, the next method to be called is def notify_order() followed by def notify_trade().
When an entry signal is generated in def next() this should result in an output of:
BUY CREATE
SELL CREATE
followed by:
BUY EXECUTED
SELL EXECUTED
from the def notify_order() method followed by:
no log output from the def notify_trade() method.Once an exit signal is generated in def next() the following outputs are expected:
CLOSE series1
CLOSE series2
followed by:
CLOSING LONG POSITION
CLOSING SHORT POSITION
from def notify_order() followed by:
OPERATION PROFIT,
from the def notify_trade() methodI'm not getting this flow at all in the output.
The csv file is also showing strange results. At the beginning of the file, entries of the 2 symbols are happening on consecutive bars instead simultaneously and eventually only one symbol gets opened and not the second symbol.I hope this provides you with enough information as to what I am hoping to resolve.
Thanks -
@juliev it seems you have some issues with the data feeds. First I've ran you script on the daily bars (I have no intraday data) and it worked well with one exception: for order notification you have the following cases only:
order.isbuy() and self.position.size > 0
forBUY EXECUTED
order.issell() and self.position.size > 0
forSELL EXECUTED
order.isbuy() and self.position.size == 0
forCLOSING SHORT POSITION
order.issell() and self.position.size == 0
forCLOSING LONG POSITION
Second, your order issue statements (BUY CREATE, SELL CREATE) should be printed with te current bar timestamp, but order notification statements sould be printed with the timestamp of the next bar. It seems to me that somehow
next()
is called twice in your case. Add the following logging line:def next(self): # Check if an order is pending ... if yes, we cannot send a 2nd one if self.orderid: return self.log('%s %0.2f - %s %0.2f: pos %d' % (self.data0._name, self.dataclose1[0], self.data1._name, self.dataclose2[0], self.position.size)) ...
If it appears only once per timestamp in the output than it is good.