For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/
Close position and selling short on the same bar not working correctly
-
I'm testing a strategy that buys a correct number of shares, and closes the position fine with the same number of shares but when it sells short it does it with very few shares as if there was not enough money left. I understand it's because as the two operations are done at the same point the broker hasn't yet updated the amount of cash from closing the previous operation. How can I fix this?
2020-10-14T23:59:59.999989, Initial portfolio value of 1700.00 1991-04-12T23:59:59.999989, BUY 56 shares at 28.87 1991-04-12T23:59:59.999989, BUY COMPLETED. Size: 56, Price: 28.87, Commission: 0.72 1991-06-19T23:59:59.999989, CLOSE Long Position of 56 shares at 25.54 1991-06-19T23:59:59.999989, SELL 3 shares at 25.54 1991-06-19T23:59:59.999989, SELL COMPLETED. Size: 56, Price: 25.54, Commission: 0.28 1991-06-19T23:59:59.999989, SELL COMPLETED. Size: 3, Price: 25.54, Commission: 0.49 1991-06-20T23:59:59.999989, TRADE COMPLETED, Portfolio: 1512.03, Gross: -186.48, Net: -187.48
from __future__ import (absolute_import, division, print_function, unicode_literals) import sys import os.path import datetime import argparse import backtrader as bt from strategies.strategies import * from sizers import MaxRiskSizer from commissions import DegiroCommission def runstrategy(): cerebro = bt.Cerebro() comminfo = DegiroCommission() cerebro.addstrategy(EmaCrossLongShort, fast=13, slow=49) cerebro.broker.set_cash(1700) cerebro.broker.set_coc(True) cerebro.addsizer(MaxRiskSizer) cerebro.broker.addcommissioninfo(comminfo) modpath = os.path.dirname(os.path.abspath(sys.argv[0])) datapath = os.path.join(modpath, 'data/CPE.csv') data = bt.feeds.YahooFinanceCSVData( dataname=datapath, # Do not pass values before this date #fromdate=datetime.datetime(1990, 10, 1), # Do not pass values after this date #todate=datetime.datetime(2020, 10, 15), reverse=False ) cerebro.adddata(data) start_portfolio_value = cerebro.broker.getvalue() cerebro.run() end_portfolio_value = cerebro.broker.getvalue() pnl = end_portfolio_value - start_portfolio_value print(f'Starting Portfolio Value: {start_portfolio_value:.2f}') print(f'Final Portfolio Value: {end_portfolio_value:.2f}') print(f'PnL: {pnl:.2f}') cerebro.plot() if __name__ == '__main__': runstrategy()
import backtrader as bt import backtrader.indicators as btind class EmaCrossLongShort(bt.Strategy): '''This strategy buys/sells upong the close price crossing upwards/downwards an Exponential Moving Average. It can be a long-only strategy by setting the param "longonly" to True ''' params = dict( fast=13, slow=48, printout=True, longonly=False, ) def log(self, txt, dt=None): if self.p.printout: dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print(f'{dt.isoformat()}, {txt}') def __init__(self): self.orderid = None # to control operation entries fast_ema, slow_ema = btind.MovAv.EMA(period=self.p.fast), btind.MovAv.EMA(period=self.p.slow) self.signal = btind.CrossOver(fast_ema, slow_ema) self.log(f'Initial portfolio value of {self.broker.get_value():.2f}') def start(self): pass def next(self): if self.orderid: return # if an order is active, no new orders are allowed if self.signal > 0.0: # cross upwards if self.position: self.log(f'COVER Short Position of {abs(self.position.size)} shares ' f'at {self.data.close[0]:.2f}') self.close() self.log(f'BUY {self.getsizing()} shares at {self.data.close[0]}') self.buy() elif self.signal < 0.0: if self.position: self.log(f'CLOSE Long Position of {self.position.size} shares ' f'at {self.data.close[0]:.2f}') self.close() if not self.p.longonly: self.log(f'SELL {abs(self.getsizing())} shares at ' f'{self.data.close[0]}') self.sell() def notify_order(self, order): if order.status in [bt.Order.Submitted, bt.Order.Accepted]: return # Await further notifications if order.status == order.Completed: if order.isbuy(): buytxt = f'BUY COMPLETED. ' \ f'Size: {order.executed.size}, ' \ f'Price: {order.executed.price:.2f}, ' \ f'Commission: {order.executed.comm:.2f}' self.log(buytxt, order.executed.dt) else: selltxt = 'SELL COMPLETED. ' \ f'Size: {abs(order.executed.size)}, ' \ f'Price: {order.executed.price:.2f}, ' \ f'Commission: {order.executed.comm:.2f}' self.log(selltxt, order.executed.dt) elif order.status in [order.Expired, order.Canceled, order.Margin]: self.log(f'{order.Status[order.status]}') pass # Simply log # Allow new orders self.orderid = None def notify_trade(self, trade): if trade.isclosed: self.log(f'TRADE COMPLETED, ' f'Portfolio: {self.broker.get_value():.2f}, ' f'Gross: {trade.pnl:.2f}, ' f'Net: {trade.pnlcomm:.2f}') elif trade.justopened: #self.log('TRADE OPENED, SIZE %2d' % trade.size) pass
import backtrader as bt class MaxRiskSizer(bt.Sizer): params = (('risk', 0.95),) def __init__(self): if self.p.risk > 1 or self.p.risk < 0: raise ValueError('The risk parameter is a percentage which must be' 'entered as a float. e.g. 0.5') def _getsizing(self, comminfo, cash, data, isbuy): #return comminfo.getsize(data.close[0], cash * self.p.risk) return round(cash * self.p.risk / data.close[0])
import backtrader as bt class DegiroCommission(bt.CommInfoBase): params = (('per_share', 0.004), ('flat', 0.5),) def _getcommission(self, size, price, pseudoexec): return self.p.flat + size * self.p.per_share
-
@quake004
def _getsizing(self, comminfo, cash, data, isbuy): return round(self.broker.get_value() * self.p.risk / data.close[0])
-
That's right thanks. I've also seen the commission for long and short positions where different. I had to change
commissions.py
:return self.p.flat + abs(size) * self.p.per_share