From 552f4260b4a4b290545ab14c33c49f1b8c2376e3 Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Mon, 11 Dec 2017 22:34:46 -0500 Subject: [PATCH] BLD: for issue #87, added configurable slippage and commission --- catalyst/examples/mean_reversion_simple.py | 3 ++ catalyst/exchange/exchange_algorithm.py | 16 +++++++ catalyst/exchange/exchange_blotter.py | 54 ++++++++++++---------- 3 files changed, 48 insertions(+), 25 deletions(-) diff --git a/catalyst/examples/mean_reversion_simple.py b/catalyst/examples/mean_reversion_simple.py index b3179cdd..b3fb7934 100644 --- a/catalyst/examples/mean_reversion_simple.py +++ b/catalyst/examples/mean_reversion_simple.py @@ -44,6 +44,9 @@ def initialize(context): context.start_time = time.time() + # context.set_commission(maker=0.1, taker=0.2) + context.set_slippage(spread=0.0001) + def handle_data(context, data): # This handle_data function is where the real work is done. Our data is diff --git a/catalyst/exchange/exchange_algorithm.py b/catalyst/exchange/exchange_algorithm.py index 2fb70cc8..3505accf 100644 --- a/catalyst/exchange/exchange_algorithm.py +++ b/catalyst/exchange/exchange_algorithm.py @@ -23,6 +23,7 @@ import pandas as pd import catalyst.protocol as zp from catalyst.algorithm import TradingAlgorithm +from catalyst.assets._assets import TradingPair from catalyst.constants import LOG_LEVEL from catalyst.exchange.exchange_blotter import ExchangeBlotter from catalyst.exchange.exchange_errors import ( @@ -113,6 +114,21 @@ class ExchangeTradingAlgorithmBase(TradingAlgorithm): else: return MarketOrder() + @api_method + def set_commission(self, maker=None, taker=None): + key = self.blotter.commission_models.keys()[0] + if maker is not None: + self.blotter.commission_models[key].maker = maker + + if taker is not None: + self.blotter.commission_models[key].taker = taker + + @api_method + def set_slippage(self, spread=None): + key = self.blotter.slippage_models.keys()[0] + if spread is not None: + self.blotter.slippage_models[key].spread = spread + def _calculate_order(self, asset, amount, limit_price=None, stop_price=None, style=None): # Raises a ZiplineError if invalid parameters are detected. diff --git a/catalyst/exchange/exchange_blotter.py b/catalyst/exchange/exchange_blotter.py index 7b89f9b9..ab8056d2 100644 --- a/catalyst/exchange/exchange_blotter.py +++ b/catalyst/exchange/exchange_blotter.py @@ -9,20 +9,13 @@ from catalyst.exchange.exchange_errors import ExchangeRequestError, \ ExchangePortfolioDataError, ExchangeTransactionError from catalyst.finance.blotter import Blotter from catalyst.finance.commission import CommissionModel -from catalyst.finance.order import ORDER_STATUS +from catalyst.finance.order import ORDER_STATUS, Order from catalyst.finance.slippage import SlippageModel from catalyst.finance.transaction import create_transaction, Transaction from catalyst.utils.input_validation import expect_types log = Logger('exchange_blotter', level=LOG_LEVEL) -# It seems like we need to accept greater slippage risk in cryptos -# Orders won't often close at Equity levels. -# TODO: should work with set_commission and set_slippage -DEFAULT_SLIPPAGE_SPREAD = 0.0001 -DEFAULT_MAKER_FEE = 0.0015 -DEFAULT_TAKER_FEE = 0.0025 - class TradingPairFeeSchedule(CommissionModel): """ @@ -30,23 +23,24 @@ class TradingPairFeeSchedule(CommissionModel): Parameters ---------- - fee : float, optional - The percentage fee. + maker : float, optional + The percentage maker fee. + + taker: float, optional + The percentage taker fee. """ - def __init__(self, - maker_fee=DEFAULT_MAKER_FEE, - taker_fee=DEFAULT_TAKER_FEE): - self.maker_fee = maker_fee - self.taker_fee = taker_fee + def __init__(self, maker=None, taker=None): + self.maker = maker + self.taker = taker def __repr__(self): return ( - '{class_name}(maker_fee={maker_fee}, ' - 'taker_fee={taker_fee})'.format( + '{class_name}(maker={maker}, ' + 'taker={taker})'.format( class_name=self.__class__.__name__, - maker_fee=self.maker_fee, - taker_fee=self.taker_fee, + maker=self.maker, + taker=self.taker, ) ) @@ -54,16 +48,25 @@ class TradingPairFeeSchedule(CommissionModel): """ Calculate the final fee based on the order parameters. - :param order: - :param transaction: + :param order: Order + :param transaction: Transaction :return float: The total commission. """ cost = abs(transaction.amount) * transaction.price + asset = order.asset + maker = self.maker if self.maker is not None else asset.maker + taker = self.taker if self.taker is not None else asset.taker + + multiplier = maker \ + if ((order.amount > 0 and order.limit < transaction.price) + or (order.amount < 0 and order.limit > transaction.price)) \ + and order.limit_reached else taker + # Assuming just the taker fee for now - fee = cost * self.taker_fee + fee = cost * multiplier return fee @@ -77,7 +80,7 @@ class TradingPairFixedSlippage(SlippageModel): spread / 2 will be added to buys and subtracted from sells. """ - def __init__(self, spread=DEFAULT_SLIPPAGE_SPREAD): + def __init__(self, spread=0.0001): super(TradingPairFixedSlippage, self).__init__() self.spread = spread @@ -132,8 +135,9 @@ class ExchangeBlotter(Blotter): self.exchanges = kwargs.pop('exchanges', None) if not self.exchanges: - raise ValueError('ExchangeBlotter must have an `exchanges` ' - 'attribute.') + raise ValueError( + 'ExchangeBlotter must have an `exchanges` attribute.' + ) super(ExchangeBlotter, self).__init__(*args, **kwargs)