Merge pull request #1738 from quantopian/slippage-and-commissions-futures

Add preliminary support for Futures slippage models
This commit is contained in:
David Michalowicz
2017-04-10 15:05:28 -04:00
committed by GitHub
7 changed files with 105 additions and 37 deletions
@@ -16,22 +16,22 @@ from nose_parameterized import parameterized
import pandas as pd
from zipline.assets import Equity
from zipline.finance.blotter import Blotter
from zipline.finance.order import ORDER_STATUS, Order
from zipline.finance.cancel_policy import EODCancel, NeverCancel
from zipline.finance.commission import PerTrade
from zipline.finance.execution import (
LimitOrder,
MarketOrder,
StopLimitOrder,
StopOrder,
)
from zipline.gens.sim_engine import SESSION_END, BAR
from zipline.finance.cancel_policy import EODCancel, NeverCancel
from zipline.finance.order import ORDER_STATUS, Order
from zipline.finance.slippage import (
DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT,
FixedSlippage,
)
from zipline.utils.classproperty import classproperty
from zipline.gens.sim_engine import BAR, SESSION_END
from zipline.testing.fixtures import (
WithCreateBarData,
WithDataPortal,
@@ -39,6 +39,7 @@ from zipline.testing.fixtures import (
WithSimParams,
ZiplineTestCase,
)
from zipline.utils.classproperty import classproperty
class BlotterTestCase(WithCreateBarData,
@@ -55,6 +56,7 @@ class BlotterTestCase(WithCreateBarData,
super(BlotterTestCase, cls).init_class_fixtures()
cls.asset_24 = cls.asset_finder.retrieve_asset(24)
cls.asset_25 = cls.asset_finder.retrieve_asset(25)
cls.future_cl = cls.asset_finder.retrieve_asset(1000)
@classmethod
def make_equity_daily_bar_data(cls):
@@ -79,6 +81,23 @@ class BlotterTestCase(WithCreateBarData,
index=cls.sim_params.sessions,
)
@classmethod
def make_futures_info(cls):
return pd.DataFrame.from_dict(
{
1000: {
'symbol': 'CLF06',
'root_symbol': 'CL',
'start_date': cls.START_DATE,
'end_date': cls.END_DATE,
'expiration_date': cls.END_DATE,
'auto_close_date': cls.END_DATE,
'exchange': 'CME',
},
},
orient='index',
)
@classproperty
def CREATE_BARDATA_DATA_FREQUENCY(cls):
return cls.sim_params.data_frequency
@@ -220,7 +239,7 @@ class BlotterTestCase(WithCreateBarData,
# Reset for paranoia
blotter = Blotter(self.sim_params.data_frequency,
self.asset_finder)
blotter.slippage_func = FixedSlippage()
blotter.slippage_models[Equity] = FixedSlippage()
filled_id = blotter.order(self.asset_24, 100, MarketOrder())
filled_order = None
blotter.current_dt = self.sim_params.sessions[-1]
@@ -363,3 +382,40 @@ class BlotterTestCase(WithCreateBarData,
blotter1.open_orders[asset][i-1].id)
self.assertEqual(order_id,
blotter2.open_orders[asset][i-1].id)
def test_slippage_and_commission_dispatching(self):
blotter = Blotter(
self.sim_params.data_frequency,
self.asset_finder,
equity_slippage=FixedSlippage(spread=0.0),
future_slippage=FixedSlippage(spread=2.0),
equity_commission=PerTrade(cost=1.0),
future_commission=PerTrade(cost=2.0),
)
blotter.order(self.asset_24, 1, MarketOrder())
blotter.order(self.future_cl, 1, MarketOrder())
bar_data = self.create_bardata(
simulation_dt_func=lambda: self.sim_params.sessions[-1],
)
txns, commissions, _ = blotter.get_transactions(bar_data)
# The equity transaction should have the same price as its current
# price because the slippage spread is zero. Its commission should be
# $1.00.
equity_txn = txns[0]
self.assertEqual(
equity_txn.price,
bar_data.current(equity_txn.sid, 'price'),
)
self.assertEqual(commissions[0]['cost'], 1.0)
# The future transaction price should be 1.0 more than its current
# price because half of the 'future_slippage' spread is added. Its
# commission should be $2.00.
future_txn = txns[1]
self.assertEqual(
future_txn.price,
bar_data.current(future_txn.sid, 'price') + 1.0,
)
self.assertEqual(commissions[1]['cost'], 2.0)
+1 -1
View File
@@ -363,7 +363,7 @@ class FinanceTestCase(WithLogger,
def test_blotter_processes_splits(self):
blotter = Blotter('daily', self.env.asset_finder,
slippage_func=FixedSlippage())
equity_slippage=FixedSlippage())
# set up two open limit orders with very low limit prices,
# one for sid 1 and one for sid 2
+14 -15
View File
@@ -67,7 +67,7 @@ from zipline.errors import (
)
from zipline.finance.trading import TradingEnvironment
from zipline.finance.blotter import Blotter
from zipline.finance.commission import PerShare, CommissionModel
from zipline.finance.commission import CommissionModel
from zipline.finance.controls import (
LongOnly,
MaxOrderCount,
@@ -84,17 +84,14 @@ from zipline.finance.execution import (
)
from zipline.finance.performance import PerformanceTracker
from zipline.finance.asset_restrictions import Restrictions
from zipline.finance.slippage import (
VolumeShareSlippage,
SlippageModel
)
from zipline.finance.slippage import SlippageModel
from zipline.finance.cancel_policy import NeverCancel, CancelPolicy
from zipline.finance.asset_restrictions import (
NoRestrictions,
StaticRestrictions,
SecurityListRestrictions,
)
from zipline.assets import Asset, Future
from zipline.assets import Asset, Equity, Future
from zipline.gens.tradesimulation import AlgorithmSimulator
from zipline.pipeline import Pipeline
from zipline.pipeline.engine import (
@@ -324,10 +321,8 @@ class TradingAlgorithm(object):
self.blotter = Blotter(
data_frequency=self.data_frequency,
asset_finder=self.asset_finder,
slippage_func=VolumeShareSlippage(),
commission=PerShare(),
# Default to NeverCancel in zipline
cancel_policy=self.cancel_policy
cancel_policy=self.cancel_policy,
)
# The symbol lookup date specifies the date to use when resolving
@@ -493,16 +488,16 @@ class TradingAlgorithm(object):
capital_base={capital_base}
sim_params={sim_params},
initialized={initialized},
slippage={slippage},
commission={commission},
slippage_models={slippage_models},
commission_models={commission_models},
blotter={blotter},
recorded_vars={recorded_vars})
""".strip().format(class_name=self.__class__.__name__,
capital_base=self.sim_params.capital_base,
sim_params=repr(self.sim_params),
initialized=self.initialized,
slippage=repr(self.blotter.slippage_func),
commission=repr(self.blotter.commission),
slippage_models=repr(self.blotter.slippage_models),
commission_models=repr(self.blotter.commission_models),
blotter=repr(self.blotter),
recorded_vars=repr(self.recorded_vars))
@@ -1662,7 +1657,9 @@ class TradingAlgorithm(object):
raise UnsupportedSlippageModel()
if self.initialized:
raise SetSlippagePostInit()
self.blotter.slippage_func = slippage
# TODO: Create separate API methods for setting Equity and Future
# slippage models.
self.blotter.slippage_models[Equity] = slippage
@api_method
def set_commission(self, commission):
@@ -1685,7 +1682,9 @@ class TradingAlgorithm(object):
if self.initialized:
raise SetCommissionPostInit()
self.blotter.commission = commission
# TODO: Create separate API methods for setting Equity and Future
# commission models.
self.blotter.commission_models[Equity] = commission
@api_method
def set_cancel_policy(self, cancel_policy):
+27 -12
View File
@@ -18,9 +18,14 @@ from copy import copy
from six import iteritems
from zipline.assets import Equity, Future
from zipline.finance.order import Order
from zipline.finance.slippage import VolumeShareSlippage
from zipline.finance.commission import PerShare
from zipline.finance.commission import (
DEFAULT_FUTURE_COST_PER_TRADE,
PerShare,
PerTrade,
)
from zipline.finance.cancel_policy import NeverCancel
log = Logger('Blotter')
@@ -28,8 +33,9 @@ warning_logger = Logger('AlgoWarning')
class Blotter(object):
def __init__(self, data_frequency, asset_finder, slippage_func=None,
commission=None, cancel_policy=None):
def __init__(self, data_frequency, asset_finder, equity_slippage=None,
future_slippage=None, equity_commission=None,
future_commission=None, cancel_policy=None):
# these orders are aggregated by sid
self.open_orders = defaultdict(list)
@@ -47,8 +53,16 @@ class Blotter(object):
self.max_shares = int(1e+11)
self.slippage_func = slippage_func or VolumeShareSlippage()
self.commission = commission or PerShare()
self.slippage_models = {
Equity: equity_slippage or VolumeShareSlippage(),
Future: future_slippage or VolumeShareSlippage(),
}
self.commission_models = {
Equity: equity_commission or PerShare(),
Future: future_commission or PerTrade(
cost=DEFAULT_FUTURE_COST_PER_TRADE,
),
}
self.data_frequency = data_frequency
@@ -57,15 +71,15 @@ class Blotter(object):
def __repr__(self):
return """
{class_name}(
slippage={slippage_func},
commission={commission},
slippage_models={slippage_models},
commission_models={commission_models},
open_orders={open_orders},
orders={orders},
new_orders={new_orders},
current_dt={current_dt})
""".strip().format(class_name=self.__class__.__name__,
slippage_func=self.slippage_func,
commission=self.commission,
slippage_models=self.slippage_models,
commission_models=self.commission_models,
open_orders=self.open_orders,
orders=self.orders,
new_orders=self.new_orders,
@@ -347,11 +361,12 @@ class Blotter(object):
for sid, asset_orders in iteritems(self.open_orders):
asset = asset_dict[sid]
slippage = self.slippage_models[type(asset)]
for order, txn in \
self.slippage_func(bar_data, asset, asset_orders):
additional_commission = \
self.commission.calculate(order, txn)
slippage.simulate(bar_data, asset, asset_orders):
commission = self.commission_models[type(asset)]
additional_commission = commission.calculate(order, txn)
if additional_commission > 0:
commissions.append({
+1
View File
@@ -19,6 +19,7 @@ from six import with_metaclass
DEFAULT_PER_SHARE_COST = 0.0075 # 0.75 cents per share
DEFAULT_MINIMUM_COST_PER_TRADE = 1.0 # $1 per trade
DEFAULT_FUTURE_COST_PER_TRADE = 2.35
class CommissionModel(with_metaclass(abc.ABCMeta)):
-3
View File
@@ -149,9 +149,6 @@ class SlippageModel(with_metaclass(abc.ABCMeta)):
self._volume_for_bar += abs(txn.amount)
yield order, txn
def __call__(self, bar_data, asset, current_orders):
return self.simulate(bar_data, asset, current_orders)
def __eq__(self, other):
return self.asdict() == other.asdict()