diff --git a/catalyst/examples/buy_the_dip_live.py b/catalyst/examples/buy_the_dip_live.py index e3469079..7ea58bf9 100644 --- a/catalyst/examples/buy_the_dip_live.py +++ b/catalyst/examples/buy_the_dip_live.py @@ -47,7 +47,7 @@ def _handle_data(context, data): buy_increment = 50 elif rsi <= 40: buy_increment = 20 - elif rsi <= 70: + elif rsi <= 90: buy_increment = 5 else: buy_increment = None @@ -72,6 +72,14 @@ def _handle_data(context, data): cost_basis = None if context.asset in context.portfolio.positions: position = context.portfolio.positions[context.asset] + # TODO: temp test + if position.amount > 0: + order_target_percent( + asset=context.asset, + target=0, + limit_price=price * (1 - context.SLIPPAGE_ALLOWED), + ) + return cost_basis = position.cost_basis log.info( diff --git a/catalyst/exchange/algorithm_exchange.py b/catalyst/exchange/algorithm_exchange.py index 265f3e0d..c9515b5c 100644 --- a/catalyst/exchange/algorithm_exchange.py +++ b/catalyst/exchange/algorithm_exchange.py @@ -397,9 +397,11 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): style) order_id = self._order(asset, amount, limit_price, stop_price, style) - order = self.portfolio.open_orders[order_id] - self.perf_tracker.process_order(order) + if order_id is not None: + order = self.portfolio.open_orders[order_id] + self.perf_tracker.process_order(order) + return order def round_order(self, amount): diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py index 07e56df0..a012fc07 100644 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ b/catalyst/exchange/bitfinex/bitfinex.py @@ -133,6 +133,10 @@ class Bitfinex(Exchange): amount = float(order_status['original_amount']) filled = float(order_status['executed_amount']) + if order_status['side'] == 'sell': + amount = -amount + filled = -filled + price = float(order_status['price']) order_type = order_status['type'] diff --git a/docs/live-trading-blueprint.md b/docs/live-trading-blueprint.md new file mode 100644 index 00000000..d26638f2 --- /dev/null +++ b/docs/live-trading-blueprint.md @@ -0,0 +1,207 @@ +

Live Trading Blueprint

+The purpose of this document is to allow project contributors navigate +through the ongoing live trading implementation. + +

Components

+At a high level, the following components have been implemented to coerce +zipline into live trading. + +

Exchange

+ +*catalyst/exchange* + +Exchange is a new package introducing cryptocurrency +exchanges to zipline. The package contains mostly new implementations +of existing components, adapted to characteristics of exchanges. + +Here are some key characteristics which make cryptocurrency exchanges +exchanges different compared to equity brokers. +* They trade around the clock. +* Currency symbols are inconsistent across exchanges. +* They trade currency pairs (i.e. the base currency is not always be USD). +This is a paradigm shift in context of zipline. Additional +business logic will be required to manage the portfolio data and orders. +* The price of a single asset might vary across exchanges. This means +arbitrage opportunities. Consequently, to extract maximum alpha, the +platform should not only support multiple exchanges, but also multiple +exchanges per algorithm. +* The fee model is usually more complex than that of an equity broker. +It can vary drastically between exchanges. +* There are no splits, mergers, etc. to worry about. +* A complete order book is usually available, the platform should +offer access to it order to help traders reduce slippage. + +

New Components

+These components of the exchange package were added to the zipline +sources. + +

Exchange

+ +*catalyst/exchange/exchange.py* + +Abstract class which acts as an interface for the implementation of +various exchanges. It also contains logic common to all exchanges. + +

Bitfinex

+ +*catalyst/exchange/bitfinex.py* + +The Bitfinex exchange implementation. It extends the Exchange class. + +

DataPortalExchange

+ +*catalyst/exchange/data_portal_exchange.py* + +Extends the zipline DataPortal to route spot data to the exchange. +This is critical because it allows the algoritm to request data in +real-time. + +For example, `data.current(asset, 'price')` retrieves the current price +of the asset, not the price at the time of yielding the bar this +is critical to minimize slippage. + +At the time of writing, it only supports spot data but I believe that +it should be extended to historical data as well. Some exchanges +have better historical data APIs than others. This will need to +be considered during each individual implementation. + +

ExchangeClock

+ +*catalyst/exchange/exchange_clock.py* + +An implementation to the zipline Clock which runs 24/7. It yields a +bar every minute. + +

AssetFinderExchange

+ +*catalyst/exchange/asset_finder_exchange.py* + +An alternate implementation of AssetFinder which locates each asset +against the exchanges instead of bundle databases. + +For example, `symbol('eth_usd')` should return an Ethereum/USD asset +regardless of currency notation of the target exchange. + +To acheive this, I have created a dictionary of currencies for the +Bitfinex exchange. Here is what it looks like. +* Each key represents the exchange specific symbol. +* The symbol attribute represents the abstract symbol common across +all exchanges for the given currency. +* The start_date attribute should correspond to its first trading day +on the exchange. + +```json +{ + "btcusd": { + "symbol": "btc_usd", + "start_date": "2010-01-01" + }, + "ltcusd": { + "symbol": "ltc_usd", + "start_date": "2010-01-01" + }, + "ltcbtc": { + "symbol": "ltc_btc", + "start_date": "2010-01-01" + }, + "ethusd": { + "symbol": "eth_usd", + "start_date": "2010-01-01" + }, + "ethbtc": { + "symbol": "eth_btc", + "start_date": "2010-01-01" + } +} +``` + +

ExchangeTradingAlgorithm

+ +*catalyst/exchange/algorithm_exchange.py* + +Extends the TradingAlgorithm class which orchestrates the api +operations. This class brings together most of the components +described above. + +

Modified Components

+ +The following components have been modified to include conditional +business logic to enable live trading. + +

run_algorithm

+ +*catalyst/utils/run_algo.py* + +The run_algorithm interface is an entry point to execute an +algorithm in zipline. This component was already modified for +the catalyst concurrency bundles. I added conditional logic +which should not interfere with backtesting. + +In a nutshell, the run_algorithm method now contains three additional +parameters: +* live: If True, zipline will attempt to trade live. If False or not +specified, it will run a backtest as normal. +* algo_namespace: An arbitrary namespace for the current algorithm. +It will be used to persist data between runs. +* exchange_conn: A dictionary containing the attributes required +to instantiate an exchange. Here is an example for Bitfinex: + +```python +exchange_conn = dict( + name='bitfinex', + key='', + secret=b'', + base_currency='usd' +) +``` + +The following sample algorithm uses the run_algorithm interface: + +*catalyst/examples/buy_and_hold_live.py* + +

Portfolio Management

+ +Zipline has a Portfolio class containing key metrics used by zipline +for, but not only, these reasons: + +* Placing orders: When placing orders (e.g. order_target_percent), +zipline queries the portfolio to assess the size of current positions, +cash available, etc. +* Measuring performance: The portfolio contains attributes like +cost basis of each asset, p&l, etc. which zipline uses to compute all +of its performance criteria. + +When backtesting, zipline automatically updates the Portfolio object +of its corresponding algorithm. When live trading, these updates should +be the responsibility of the exchange as it holds the truth for: + +* Executed price of each order (including fees and slippage) +* Partial / failed orders +* Cash (i.e. base currency) available +* Cost basis of each position + +If each exchange account had a one-to-one relationship with an +algorithm, portfolio metrics could be retrieved directly from the +exchange without persisting any data to the algorithm. However, +doing this would have at least the following drawbacks: + +* It may not be reasonable to ask users to dedicate an +exchange account to a single algorithm. Exchanges are not easy +to partition. +* If an exchange account contains existing positions, the calculated +cost basis would correspond to all positions, not just those +initiated by the algorithm. +* It would not be possible impose trading limits on algorithms. + +It follows that Portfolio metrics should be calculated using a strategic +combination of the exchange data and algorithm activity. While tracking +the activity of an algorithm works well in backtesting, it is more +challenging during live trading. A live algorithm might run over +several months. It might have to stop and start for many reasons. +This means that the platform should have the ability to persist +algorithm activity in order to be reliable. + +In the interest of time, I will start by persisting algorithm +activity in memory. Data will be lost when the algorithm execution stops. +The intent it to offer a simple basis from which to implement data +persistence strategies in the future. \ No newline at end of file