mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-09 05:47:18 +08:00
Merge pull request #1738 from quantopian/slippage-and-commissions-futures
Add preliminary support for Futures slippage models
This commit is contained in:
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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({
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user