How to: change currency in datafeed
I just got started with Backtrader and I want to start by experimenting a bit with arbitrage concepts in the crypto world.
what i'm trying to achieve is to use backtrader to convert data (OHLC) from currency 'X' to 'Y' using historical price data.
using the following data (.csv):
- daily OHLC in 'EUR'
- daily OHLC in 'USD'
- weekly 'EUR' to 'USD' (close only)
I can easily convert all the data files outside of backtrader but I want to try and do it from within using 'simple' backtrader arithmetic.
any ideas on how to make it happen?
@Jacob add another set of data that is daily data for forex for the conversion needed. then do arithmetic with the two datasets
self.datas.closeto give you the needed outputs.
@blonc thanks for your answer.
I played around with your suggested solution and it works well from inside a strategy class.
From my understanding this way on every bar the conversion is taking place on the fly. but it seem a bit limited, as I cannot override the original data (I can only make the calculation)
what i wanted was to change the original data with the new currency before so i can pass the 'new data' to the cerebro instance.
this way I can easily apply indicators, plot...
maybe using 'filters' can be a solution.
as like resampling I can change the data before it enter cerebro.
this would need to work with 2 datafeeds, to create the data with the new currency.
is this a good approach or I'm just bending it completely? (passing the second datafeed in *args or **kwargs).
or maybe loading both datafeed (.csv) first with Pandas, playing around with the data and add it to cerebro directly?
@jacob gonna be a lot easier to just write a simple csv data feed handler and a lot cleaner. most the code is there to do it, just change as needed.. import the two files, do the conversion and pass it along. this will be totally reusable too for any two files you need.
if you are looking for something quick and dirty this bit of code can do literally everything you want. just import the CSV files, do the math in a
np.arrayand then pass it to
iter. you can add params at the top to pass from the strategy side and there you go.
class MyCryptoCSVData(bt.CSVDataBase): params = ( ('filepath1', None), ('filepath2', None), ('anything you want', None), ) def start(self): # read in the CSV values, do your math and put the list into a iter() pass def stop(self): # close your read files def _loadline(self, linetokens): i = itertools.count(0) dttxt = linetokens[next(i)] # Format is YYYY-MM-DD y = int(dttxt[0:4]) m = int(dttxt[5:7]) d = int(dttxt[8:10]) dt = datetime.datetime(y, m, d) dtnum = date2num(dt) self.lines.datetime = dtnum self.lines.open = float(linetokens[next(i)]) self.lines.high = float(linetokens[next(i)]) self.lines.low = float(linetokens[next(i)]) self.lines.close = float(linetokens[next(i)]) self.lines.volume = float(linetokens[next(i)]) self.lines.openinterest = float(linetokens[next(i)]) return True
as I cannot override the original data (I can only make the calculation)
You can add a filter to the target data feed, and the filter may have a reference to the data which holds the conversion rate.
The values in the target data feed for the filter will be overridden with anything you decide to output.
See: Docs - Filters
I really like the idea of using a filter to manipulate the data.
but I not sure the solution I have in mind is doable (I want to use as much as backtrader flexibility)
so I have 2 sets of data feed:
- OHLC of some crypto in X currency
- OHLC of X to Y currency
I import the 2 csv files and apply the conversion filter to the first one (adding also resample to both just to make it interesting :))
now if I understand correctly after cerebro.run() the system will go bar by bar and apply all the filters one bar at a time. so I will have the corresponding bar for the crypto data but not to conversion rate data. meaning if I pass the conversion data as a param for the main data filter I would have to do some date matching on each bar to get the right rate? or is there a cleaver way to make the 2 data feed bars in sync?
Wowwww my mind is completely blown away.
I did a little bit of debugging just to find out that the 2 data feeds are actually in sync!!!
and on top of it the last value is accessible via .
meaning it automatically points to the last conversion rate, just out of the box
I'm really loving this library :)
I did find in the process something that seem a bit buggy with the dates (working with timestamps).
but i need to double check and if so I will open a new post for it.
will also post a short code snippet once i'm done testing it.
maybe someone can find it handy
after testing and debugging there is some limitation that should be noted:
- for both data feed to be properly synced both need to invoke the filter mechanism, otherwise the conversion filter doesn't get any data form the currency rate data. to solve this I resampled the currency rate data feed (which is a filter).
- first raw of main data get called prior to currency data so there is no rate reference. making the data not consistence to the conversion
- trying to delete the raw to solve problem (2) just make it propagate to the next raw causing problem (2) recursively. so a workaround i used is to change all values to 0
in the main code:
data.addfilter(CurrencyConverterFilter, rate = data_rate)
def __init__(self, data, *args, **kwargs): self.currencyRate = None if 'rate' in kwargs: self.currencyRate = kwargs['rate'] def __call__(self, data, *args, **kwargs): bar = [data.lines[i] for i in range(data.size())] if len(self.currencyRate): rate = self.currencyRate # adjust data with new rate bar[data.Open] = bar[data.Open] / rate bar[data.Close] = bar[data.Close] / rate bar[data.High] = bar[data.High] / rate bar[data.Low] = bar[data.Low] / rate # update stream data.backwards(force=True) # remove the copied bar from stream data._add2stack(bar) # bar is ready to be proceesed return False # bar couldn't be converted due to rate data havn't been initialized # can happen if data feed for currency: # 1. was not passed at all # 2. was pass for future date (no rate reference was given yet) # 3. wasn't initialize by the system (can happen in first iteration) #data.backwards() # remove bar from data stack #return True # tell outer data loop to fetch a new bar # unfortunately removing bars cause the sync between data feeds to fail so data need to be removed manualy bar[data.Open] = 0 bar[data.Close] = 0 bar[data.High] = 0 bar[data.Low] = 0 bar[data.Volume] = 0 # update stream data.backwards(force=True) data._add2stack(bar) return False