scrtlabs / catalyst

An Algorithmic Trading Library for Crypto-Assets in Python
http://enigma.co
Apache License 2.0
2.49k stars 725 forks source link

Running Backtest with custom ohlcv data #244

Open tdrobbin opened 6 years ago

tdrobbin commented 6 years ago

Dear Catalyst Maintainers,

Thank you for your work on an amazing platform!

Before I tell you about my issue, let me describe my environment:

Environment

I'm running mac osx, and installed everything with combination of pip and conda in a custom conda environment. catalyst was installe with pip. here's what my environment looks like

name: sdac channels:

Now that you know a little about me, let me tell you about the issue I am having:

Description of Issue

screen shot 2018-02-22 at 8 59 36 pm

I tried running the backtest using this data by passing it to run_algorithm via the data param as follows:

# %%catalyst --start 2016-1-1 --end 2018-1-1 --capital-base 100000 -x bitfinex -c usd
from catalyst import run_algorithm

from catalyst.finance.slippage import FixedSlippage
from catalyst.finance.commission import PerDollar, PerTrade

from catalyst.api import (
    order_target_value,
    order_target_percent,
    symbol,
    record,
    cancel_order,
    get_open_orders,
    order,
    set_max_leverage,
    set_long_only,
    history,
    get_datetime,
    set_commission, 
    set_slippage,
    set_cancel_policy,
    EODCancel
)

def initialize(context):
    set_max_leverage(1)
    set_long_only()
    set_cancel_policy(EODCancel())

    set_commission(PerTrade(cost=0))
    set_slippage(spread=0.00)

#    context.asset = symbol('btc_usd')
    context.asset = symbol('BTC')

    context.i = 0

def handle_data(context, data):
    if context.i == 0:
        order_target_percent(context.asset, .99)

    record(btc = data.current(context.asset, 'price'))

    context.i += 1

data = sda.zipline.get_zipline_ready_bar_data()

bt_options = dict(
    capital_base=10000, 
    start=pd.Timestamp('2016-01-01', tz='UTC'), 
    end=pd.Timestamp('2018-01-01', tz='UTC'),
    handle_data=handle_data,
    initialize=initialize,
    data=data,
#     exchange_name='bitfinex',
#     base_currency='usd'
)

df = run_algorithm(**bt_options)

I recieved an error that i must pass an exchange_name, so uncommented out the portion above. so my bt_options looks as follows:

bt_options = dict(
    capital_base=10000, 
    start=pd.Timestamp('2016-01-01', tz='UTC'), 
    end=pd.Timestamp('2018-01-01', tz='UTC'),
    handle_data=handle_data,
    initialize=initialize,
    data=data,
    exchange_name='bitfinex',
    base_currency='usd'
)

df = run_algorithm(**bt_options)

I recieved the following traceback:

[2018-02-23 02:03:55.081223] WARNING: run_algo: Catalyst is currently in ALPHA. It is going through rapid development and it is subject to errors. Please use carefully. We encourage you to report any issue on GitHub: https://github.com/enigmampc/catalyst/issues
[2018-02-23 02:03:58.086530] INFO: run_algo: running algo in backtest mode
[2018-02-23 02:03:58.234683] WARNING: Loader: Refusing to download new treasury data because a download succeeded at 2018-02-21 04:59:27+00:00.
[2018-02-23 02:03:58.237140] INFO: exchange_algorithm: initialized trading algorithm in backtest mode
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-26-aabf0a13351d> in <module>()
     60 )
     61 
---> 62 df = run_algorithm(**bt_options)

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/utils/run_algo.py in run_algorithm(initialize, capital_base, start, end, handle_data, before_trading_start, analyze, data_frequency, data, bundle, bundle_timestamp, default_extension, extensions, strict_extensions, environ, live, exchange_name, base_currency, algo_namespace, live_graph, analyze_live, simulate_orders, auth_aliases, stats_output, output)
    547         simulate_orders=simulate_orders,
    548         auth_aliases=auth_aliases,
--> 549         stats_output=stats_output
    550     )

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/utils/run_algo.py in _run(handle_data, initialize, before_trading_start, analyze, algofile, algotext, defines, data_frequency, capital_base, data, bundle, bundle_timestamp, start, end, output, print_algo, local_namespace, environ, live, exchange, algo_namespace, base_currency, live_graph, analyze_live, simulate_orders, auth_aliases, stats_output)
    329     ).run(
    330         data,
--> 331         overwrite_sim_params=False,
    332     )
    333 

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/exchange_algorithm.py in run(self, data, overwrite_sim_params)
    350     def run(self, data=None, overwrite_sim_params=True):
    351         perf = super(ExchangeTradingAlgorithmBacktest, self).run(
--> 352             data, overwrite_sim_params
    353         )
    354         # Rebuilding the stats to support minute data

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/exchange_algorithm.py in run(self, data, overwrite_sim_params)
    307         data.attempts = self.attempts
    308         return super(ExchangeTradingAlgorithmBase, self).run(
--> 309             data, overwrite_sim_params
    310         )
    311 

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/algorithm.py in run(self, data, overwrite_sim_params)
    722         try:
    723             perfs = []
--> 724             for perf in self.get_generator():
    725                 perfs.append(perf)
    726 

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/algorithm.py in get_generator(self)
    620         method to get a standard construction generator.
    621         """
--> 622         return self._create_generator(self.sim_params)
    623 
    624     def run(self, data=None, overwrite_sim_params=True):

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/algorithm.py in _create_generator(self, sim_params)
    591 
    592         if not self.initialized:
--> 593             self.initialize(*self.initialize_args, **self.initialize_kwargs)
    594             self.initialized = True
    595 

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/algorithm.py in initialize(self, *args, **kwargs)
    455         """
    456         with ZiplineAPI(self):
--> 457             self._initialize(self, *args, **kwargs)
    458 
    459     def before_trading_start(self, data):

<ipython-input-26-aabf0a13351d> in initialize(context)
     33 
     34     context.asset = symbol('btc_usd')
---> 35     context.asset = symbol('BTC')
     36 
     37     context.i = 0

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/utils/api_support.py in wrapped(*args, **kwargs)
     55                 % f.__name__
     56             )
---> 57         return getattr(algo_instance, f.__name__)(*args, **kwargs)
     58     # Add functor to catalyst.api
     59     setattr(catalyst.api, f.__name__, wrapped)

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/exchange_algorithm.py in symbol(self, symbol_str, exchange_name)
    183     @preprocess(symbol_str=ensure_upper_case)
    184     def symbol(self, symbol_str, exchange_name=None):
--> 185         """Lookup an Equity by its ticker symbol.
    186 
    187         Parameters

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/exchange_algorithm.py in symbol(self, symbol_str, exchange_name)
    225             exchange=exchange,
    226             data_frequency=data_frequency,
--> 227             as_of_date=_lookup_date
    228         )
    229 

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/exchange_asset_finder.py in lookup_symbol(self, symbol, exchange, data_frequency, as_of_date, fuzzy)
    114         log.debug('looking up symbol: {} {}'.format(symbol, exchange.name))
    115 
--> 116         return exchange.get_asset(symbol, data_frequency)
    117 
    118     def lifetimes(self, dates, include_start_date):

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/exchange.py in get_asset(self, symbol, data_frequency, is_exchange_symbol, is_local)
    238         # TODO: temp mapping, fix to use a single symbol convention
    239         og_symbol = symbol
--> 240         symbol = self.get_symbol(symbol) if not is_exchange_symbol else symbol
    241         log.debug(
    242             'searching assets for: {} {}'.format(

~/anaconda/envs/sdac/lib/python3.6/site-packages/catalyst/exchange/ccxt/ccxt_exchange.py in get_symbol(self, asset_or_symbol, source)
    277             )
    278             quote_currency = self.substitute_currency_code(
--> 279                 parts[1], source
    280             )
    281             return '{}/{}'.format(base_currency, quote_currency)

IndexError: list index out of range

I believe there may be an issue with how I'm accessing the asset in the following:

context.asset = symbol('BTC')

Nearly identical code, however, works when using zipline.

Here is how you can reproduce this issue on your machine:

Reproduction Steps

If it would be helpful I can upload a binary of the data panel, as a pickle or zipped hdf5 or something else that would be useful.

I have also already ingested the bitfinex daily exchange data hence the btc_usd symbol lookup succeeds (when I uncomment it out).

...

What steps have you taken to resolve this already?

Looking through the stack trace and catalyst's documentation, it seems that symbol expects a target currency as well a base currency in the string with an underscore separating them. So I tried the following:

    context.asset = symbol('BTC_usd')

everything ran successfully but the resulting data (returns) were exactly the same, out to the 4th decimal place as when I used btc_usd so perhaps it is not case sensitive

...

Anything else?

I'm looking to test some idea on a large cross section using custom ohlcv data. I can get it to work with zipline, but the calendar is a bit messed up (not designed for cryptos like catalyst is!). But catalyst is designed for cryptos and in the future it would be nice to ues the live feature as well, so ideally I can do it all using catalyst.

Again, thanks a lot! ...

Sincerely, tyler

hnipps commented 6 years ago

I've managed to get custom OHLCV data into Catalyst by loading data from a CSV in the initialize function like so:

df = pandas.read_csv('minute-price-test.csv', parse_dates=['last_traded'], index_col=['last_traded'])
context.imported_prices = df

Then using the price from context.imported_prices in handle_data instead of the data that's piped in by Catalyst:

price = context.imported_prices.loc[context.datetime]['close']

You could probably skip the CSV reading part and just pass in your dataframe.

I'm looking at enhancing the exchange_bundle module to allow us to ingest data directly from a Pandas DataFrame into an exchange.

Hope the above workaround helps in the meantime!