Hi Guys,
I need help with converting a tradingview strategy over into backtrader. I have been working on this for a couple of months and can't seem to get the numbers to matchup. We are getting the data from the Bybit API and loading into a pandas dataframe. We then use backtrader to load the dataframe into Cerebro.
Please see below for the BackTrader Strategy and Pandas Data.
class LongCloseLarpStrat(bt.Strategy):
params = (('fastMA',22),('slowMA', 23))
def log(self, txt, dt=None):
''' Logging function for this strategy'''
dt = dt or self.datas[0].datetime.datetime(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.livetrades = {}
self.counter = 0
self.fastMA = bt.indicators.ExponentialMovingAverage(period=self.p.fastMA)
self.slowMA = bt.indicators.ExponentialMovingAverage(period=self.p.slowMA)
self.crossover = bt.indicators.CrossOver(self.fastMA, self.slowMA)
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted, order.Partial]:# Buy/Sell order submitted/accepted to/by broker/partially filled - Nothing to do
return
# Check if an order has been completed
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, tradeID: %d' %(order.executed.price, order.executed.value, order.executed.comm, order.tradeid))
elif order.issell():
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f, tradeID: %d' %(order.executed.price, order.executed.value, order.executed.comm, order.tradeid))
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
def notify_trade(self, trade):
if trade.justopened:
self.log('New trade just opened with id %d'%(trade.tradeid))
self.livetrades[trade.tradeid] = trade
elif trade.isclosed:
self.log('A trade just closed with id %d'%(trade.tradeid))
self.livetrades.pop(trade.tradeid)
else:
self.log('Trade id %d updated'%(trade.tradeid))
def next(self):
if self.position.size:
if self.crossover < 0:
self.sell()
elif self.crossover > 0:
self.buy()
Pandas Data
class PandasData(bt.feeds.PandasData):
lines = ('turnover','high_low2', 'ema_slow', 'ema_fast', 'macd_delta')
params = (
('datetime', None),
('open','Open'),
('high','High'),
('low','Low'),
('close','Close'),
('volume','Volume'),
('openinterest', None),
('turnover','Turnover'),
('high_low2','high_low2'),
('ema_slow', 'ema_slow'),
('ema_fast', 'ema_fast'),
('macd_delta', 'macd_delta')
)
Please see below for the Pinescript Code.
//@version=3
strategy(title = "test", overlay=true, initial_capital = 10000, pyramiding = 0, commission_type = strategy.commission.percent, commission_value = 0.0, calc_on_order_fills = false, calc_on_every_tick = false, default_qty_type = strategy.percent_of_equity, default_qty_value = 100, currency = currency.USD)
fastMA = ema(close, 22)
slowMA = ema(close, 23)
plot(fastMA, color = red, linewidth = 1, title = "Fast MA")
plot(slowMA, color = blue, linewidth = 1, title = "Slow MA")
buyCondition = crossover(fastMA, slowMA)
closeCondition = crossunder(fastMA, slowMA)
strategy.entry("MA Cross - Long", strategy.long)
strategy.close("MA Cross - Long", closeCondition)
I apologize if I did not ask this question in the correct manner.
Please let me know if there is any additional questions or anything I can add.
Here is a link to the tradingview ticker.
https://www.tradingview.com/symbols/BTCUSD/?exchange=BYBIT
We are working on the 3 hour timeframe.
Also, here is the code for getting the data from bybit.
def get_historical_klines_pd(client, symbol, interval, start_str, end_str=None):
"""Get Historical Klines from Bybit
See dateparse docs for valid start and end string formats
http://dateparser.readthedocs.io/en/latest/
If using offset strings for dates add "UTC" to date string
e.g. "now UTC", "11 hours ago UTC"
:param symbol: Name of symbol pair -- BTCUSD, ETCUSD, EOSUSD, XRPUSD
:type symbol: str
:param interval: Bybit Kline interval -- 1 3 5 15 30 60 120 240 360 720 "D" "M" "W" "Y"
:type interval: str
:param start_str: Start date string in UTC format
:type start_str: str
:param end_str: optional - end date string in UTC format
:type end_str: str
:return: list of OHLCV values
"""
# set parameters for kline()
interval = int(interval)
timeframe = str(interval)
limit = 200
start_ts = int(date_to_milliseconds(start_str)/1000)
end_ts = None
if end_str:
end_ts = int(date_to_milliseconds(end_str)/1000)
else:
end_ts = int(date_to_milliseconds('now')/1000)
# init our list
output_data = []
# loop counter
idx = 0
# it can be difficult to know when a symbol was listed on Binance so allow start time to be before list date
symbol_existed = False
while True:
# fetch the klines from start_ts up to max 200 entries
temp_tuple = client.Kline.Kline_get(symbol=symbol, interval=timeframe, **{'from':start_ts}, limit=limit).result()
temp_dict = temp_tuple[0]
# handle the case where our start date is before the symbol pair listed on Binance
if not symbol_existed and len(temp_dict):
symbol_existed = True
if symbol_existed:
# extract data and convert to list
temp_data = [list(i.values()) for i in temp_dict['result']]
# append this loops data to our output data
output_data += temp_data
# update our start timestamp using the last value in the array and add the interval timeframe
# NOTE: current implementation does not support inteval of D/W/M/Y
start_ts = temp_data[len(temp_data) - 1][2] + interval*60
else:
# it wasn't listed yet, increment our start date
start_ts += timeframe
idx += 1
# check if we received less than the required limit and exit the loop
if len(temp_data) < limit:
# exit the while loop
break
# sleep after every 3rd call to be kind to the API
if idx % 3 == 0:
time.sleep(0.2)
# convert to data frame
df = pd.DataFrame(output_data, columns=['Symbol', 'Interval', 'TimeStamp', 'Open', 'High', 'Low', 'Close', 'Volume', 'Turnover'])
df['Date'] = [datetime.fromtimestamp(i).strftime('%Y-%m-%d %H:%M:%S.%d')[:-3] for i in df['TimeStamp']]
return df
Thanks,
Hernando