I'm having trouble buying on open/ selling on close using daysteps
-
Hi everyone,
I've been gone for a few months but I'm back at it! Glad to see the community is still as active as when I went dark around April!
Anyway, I'm trying to make a strategy that buys on open using the open price and sells on close using the close price. I have daily data and i'm trying to make use of the
daysteps.py
example and theDayStepsFilter
.I sort of have it working. My strategy buys on open using the open price, and it sells on close, BUT it sells using the open price instead of the close price. I'm not sure what's going on.
First, here is a sample log (reproduced here as is, which is a bit out of order):
2014-12-08, BUY EXECUTED, Price: 41.91, Cost: 41.91, Comm 0.04 2014-12-08, SELL EXECUTED, Price: 41.91, Cost: 41.91, Comm 0.04 2014-12-08, OPERATION PROFIT, GROSS 0.00, NET -0.08 0236,0471,0236,0236,2014-12-08T23:59:59.999989,41.91,41.91 2014-12-08, BUY CREATE, 41.91 0236,0472,0236,0236,2014-12-08T23:59:59.999989,41.91,41.37 2014-12-08, SELL CREATE, 41.37
First, I issue a buy for $41.91 which is the open price. That is executed with the open price of $41.91.
Then on the closing bar of the day I issue a sell using the closing price of $41.37 and BUT it's executed using the opening price of $41.91.
I've tried futzing with the order types of the buy and sell but could not get it working.
Here is the next() function in the strategy and hopefully someone could point out my error:
def next(self): self.callcounter += 1 txtfields = list() txtfields.append('%04d' % len(self.data)) txtfields.append('%04d' % self.callcounter) txtfields.append('%04d' % len(self)) txtfields.append('%04d' % len(self.data0)) txtfields.append(self.data.datetime.datetime(0).isoformat()) txtfields.append('%.2f' % self.data0.open[0]) txtfields.append('%.2f' % self.data0.close[0]) print(','.join(txtfields)) if len(self.data) > self.lcontrol: self.log('BUY CREATE, %.2f' % self.dataclose[0]) self.buy() else: self.log('SELL CREATE, %.2f' % self.dataclose[0]) self.sell() self.lcontrol = len(self.data)
Here's all the code (which is basically daysteps with the logging code and the code to buy/ sell on open and close.
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 the backtrader platform import backtrader as bt # Create a Stratey class TestStrategy(bt.Strategy): def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def __init__(self): # Keep a reference to the "close" line in the data[0] dataseries self.dataclose = self.datas[0].close self.lcontrol = 0 # control if 1st or 2nd call def start(self): self.lcontrol = 0 # control if 1st or 2nd call self.callcounter = 0 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(): self.log( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) else: # Sell self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) self.bar_executed = len(self) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') 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): self.callcounter += 1 txtfields = list() txtfields.append('%04d' % len(self.data)) txtfields.append('%04d' % self.callcounter) txtfields.append('%04d' % len(self)) txtfields.append('%04d' % len(self.data0)) txtfields.append(self.data.datetime.datetime(0).isoformat()) txtfields.append('%.2f' % self.data0.open[0]) txtfields.append('%.2f' % self.data0.close[0]) print(','.join(txtfields)) if len(self.data) > self.lcontrol: self.log('BUY CREATE, %.2f' % self.dataclose[0]) self.buy() else: self.log('SELL CREATE, %.2f' % self.dataclose[0]) self.sell() self.lcontrol = len(self.data) if __name__ == '__main__': # Create a cerebro entity cerebro = bt.Cerebro() # Datas are in a subfolder of the samples. Need to find where the script is # because it could have been called from anywhere modpath = os.path.dirname(os.path.abspath(sys.argv[0])) datapath = os.path.join(modpath, 'orcl_2014.txt') # Create a Data Feed data = bt.feeds.YahooFinanceCSVData( dataname=datapath, # Do not pass values before this date fromdate=datetime.datetime(2014, 1, 1), # Do not pass values before this date todate=datetime.datetime(2014, 12, 31), # Do not pass values after this date reverse=False) # Add the Data Feed to Cerebro #data.addfilter(DayStepsCloseFilter) data.addfilter(bt.filters.DayStepsFilter) cerebro.adddata(data) # Add a strategy cerebro.addstrategy(TestStrategy) # Set our desired cash start cerebro.broker.setcash(100000.0) # consider a bar with the same time as the end of session to be the end of the session cerebro.broker.set_eosbar(True) # Set the commission - 0.1% ... divide by 100 to remove the % cerebro.broker.setcommission(commission=0.001) # Print out the starting conditions print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue()) # Run over everything cerebro.run() # Print out the final result print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
Thanks everyone!
-
I continued searching for answers and I came across
cerebro.broker.set_coc(True)
.It sort of works but I'm still not sure I'm doing this right.
This does let my sell order use the close price BUT it sells on the next day. The logs are in the right order here though and you can see it sells at the right price ($41.37) but on the 9th instead of the 8th.
2014-12-05, SELL CREATE, 41.93 2014-12-08, SELL EXECUTED, Price: 41.93, Cost: 42.02, Comm 0.04 2014-12-08, OPERATION PROFIT, GROSS -0.09, NET -0.17 0236,0471,0236,0236,2014-12-08T23:59:59.999989,41.91,41.91 2014-12-08, BUY CREATE, 41.91 2014-12-08, BUY EXECUTED, Price: 41.91, Cost: 41.91, Comm 0.04 0236,0472,0236,0236,2014-12-08T23:59:59.999989,41.91,41.37 2014-12-08, SELL CREATE, 41.37 2014-12-09, SELL EXECUTED, Price: 41.37, Cost: 41.91, Comm 0.04 2014-12-09, OPERATION PROFIT, GROSS -0.54, NET -0.62
-
@brettelliot said in I'm having trouble buying on open/ selling on close using daysteps:
It sort of works but I'm still not sure I'm doing this right.
You were doing it wrong before. You cannot use the
close
price for selling unless you cheat (set_coc
->set_cheat_on_close
), because when you issue the order you have already seen the price. And using a price which has been seen (and the price has already influenced the calculation of the indicators) is cheating@brettelliot said in I'm having trouble buying on open/ selling on close using daysteps:
BUT it sells on the next day.
The evaluation is done after the bar is gone.
-
OK that makes sense. What's the right way to use daily data and make two trades... one buys at the open price and the other sells at the closing price?
-
Imho: there is no right way because to really do that and not cheat yourself you need a smaller timeframe.
But if you want to do it:
-
Use
cheat-on-open
and issue an order innext_open
: it will be executed with the opening price. Issue the order addingcoc=False
(to avoid it being hit bycheat-on-close
) to eitherbuy
/sell
See: Docs - Cheat on Open
-
Activate also
cheat-on-close
and send a 2nd order duringnext
(use nococ
parameter for the order) and it will be executed with theclose
price)
-
-
Thank you!!
-
Or use hourly data - then you can buy at 9am and sell at 5pm...