@ab_trader Thanks for your tip! I think I was able to code up a custom commission scheme (CommInfo_BinancePerp) that reproduces Binance perpetual futures where margin = position_value/mult. When I look at a minimal example of buying one contract and selling it a couple of days later, the numbers roughly add up but there is some mismatch between the buy price and the portfoliovalue/cash.
I use this mini-example with (mult = 1), and (commission = 0) and the logs confuse me, If I start with 1000$, then buy one contract @ 396.36$, why do I have pvalue: 973.6$, cash: 590.44$ ? This can't even be explained by the difference between open/close on that day.
My mini-example:
import backtrader as bt
from datetime import datetime
class MiniExample(bt.Strategy):
def __init__(self):
# To keep track of pending orders
self.orders = None
def next(self):
if len(self) == 1: #on second candle
self.orders = self.buy(size = 1)
if len(self) == 5: #on 6th candle
self.orders = self.sell(size = 1)
dt = self.datas[0].datetime.date(0)
print(f"{dt} - getvalue: {round(self.broker.getvalue(),2)}$, getcash: {round(self.broker.getcash(),2)}$, getposition: {self.broker.getposition(self.datas[0]).size}, open: {self.datas[0].open[0]}$, close: {self.datas[0].close[0]}$")
def log(self, txt, dt=None):
''' Logging function for this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def notify_order(self, order):
if order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
print('Order Canceled/Margin/Rejected')
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(f"BUY EXECUTED, {order.executed.size} units @ {order.executed.price}$")
elif order.issell():
self.log(f"SELL EXECUTED, {order.executed.size} units @ {order.executed.price}$")
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Write down: no pending order
self.orders = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
# define custom commission scheme that reproduces Binance perpetual future trading
class CommInfo_BinancePerp(bt.CommInfoBase):
params = (
('stocklike', False), # Futures
('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission
)
def _getcommission(self, size, price, pseudoexec):
return size * price * self.p.commission * self.p.mult
def get_margin(self, price):
return price / self.p.mult
def getsize(self, price, cash):
'''Returns fractional size for cash operation @price'''
return self.p.mult * (cash / price)
# initialize cerebro object
cerebro = bt.Cerebro()
# add strategy
cerebro.addstrategy(MiniExample)
# add custom commission scheme to cerebro element
binance_leverage = 1
comminfo = CommInfo_BinancePerp(
commission=0, # 0.1%
mult=binance_leverage
)
cerebro.broker.addcommissioninfo(comminfo)
cerebro.broker.set_shortcash(False)
# set initial cash
cerebro.broker.setcash(1000)
# add data
data = bt.feeds.YahooFinanceData(dataname='ETH-USD', fromdate = datetime(2020,11,1), todate = datetime(2020,11,7))
cerebro.adddata(data)
# execute
back = cerebro.run()
My logs:
2020-11-01 - pvalue: 1000.0$, cash: 1000.0$, pos: 0, O: 386.59$, C: 396.36$
2020-11-02, BUY EXECUTED, 1 units @ 396.36$
2020-11-02 - pvalue: 973.6$, cash: 590.44$, pos: 1, O: 396.36$, C: 383.16$
2020-11-03 - pvalue: 982.48$, cash: 594.88$, pos: 1, O: 383.16$, C: 387.6$
2020-11-04 - pvalue: 1011.56$, cash: 609.42$, pos: 1, O: 387.6$, C: 402.14$
2020-11-05 - pvalue: 1035.42$, cash: 621.35$, pos: 1, O: 402.14$, C: 414.07$
2020-11-06, SELL EXECUTED, -1 units @ 414.07$
2020-11-06, OPERATION PROFIT, GROSS 17.71, NET 17.71
2020-11-06 - pvalue: 1017.71$, cash: 1017.71$, pos: 0, O: 414.07$, C: 454.72$
Thanks again for any further help, really struggling with this.