API: Add slippage and commission models for futures

This commit is contained in:
dmichalowicz
2017-04-25 17:29:41 -04:00
parent 0da8a59f4c
commit dd21346eca
12 changed files with 1390 additions and 139 deletions
+2 -2
View File
@@ -28,7 +28,7 @@ from zipline.finance.execution import (
)
from zipline.finance.order import ORDER_STATUS, Order
from zipline.finance.slippage import (
DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT,
DEFAULT_EQUITY_VOLUME_SLIPPAGE_BAR_LIMIT,
FixedSlippage,
)
from zipline.gens.sim_engine import BAR, SESSION_END
@@ -292,7 +292,7 @@ class BlotterTestCase(WithCreateBarData,
order_size = 100
expected_filled = int(trade_amt *
DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT)
DEFAULT_EQUITY_VOLUME_SLIPPAGE_BAR_LIMIT)
expected_open = order_size - expected_filled
expected_status = ORDER_STATUS.OPEN if expected_open else \
ORDER_STATUS.FILLED
+261 -39
View File
@@ -1,8 +1,18 @@
from datetime import timedelta
from textwrap import dedent
from nose_parameterized import parameterized
from pandas import DataFrame
from zipline import TradingAlgorithm
from zipline.finance.commission import PerTrade, PerShare, PerDollar
from zipline.errors import IncompatibleCommissionModel
from zipline.finance.commission import (
PerContract,
PerDollar,
PerFutureTrade,
PerShare,
PerTrade,
)
from zipline.finance.order import Order
from zipline.finance.transaction import Transaction
from zipline.testing import ZiplineTestCase, trades_by_sid_to_dfs
@@ -17,83 +27,199 @@ from zipline.utils import factory
class CommissionUnitTests(WithAssetFinder, ZiplineTestCase):
ASSET_FINDER_EQUITY_SIDS = 1, 2
def generate_order_and_txns(self):
asset1 = self.asset_finder.retrieve_asset(1)
@classmethod
def make_futures_info(cls):
return DataFrame({
'sid': [1000, 1001],
'root_symbol': ['CL', 'FV'],
'symbol': ['CLF07', 'FVF07'],
'start_date': [cls.START_DATE, cls.START_DATE],
'end_date': [cls.END_DATE, cls.END_DATE],
'notice_date': [cls.END_DATE, cls.END_DATE],
'expiration_date': [cls.END_DATE, cls.END_DATE],
'multiplier': [500, 500],
'exchange': ['CME', 'CME'],
})
def generate_order_and_txns(self, sid, order_amount, fill_amounts):
asset1 = self.asset_finder.retrieve_asset(sid)
# one order
order = Order(dt=None, asset=asset1, amount=500)
order = Order(dt=None, asset=asset1, amount=order_amount)
# three fills
txn1 = Transaction(asset=asset1, amount=230, dt=None,
txn1 = Transaction(asset=asset1, amount=fill_amounts[0], dt=None,
price=100, order_id=order.id)
txn2 = Transaction(asset=asset1, amount=170, dt=None,
txn2 = Transaction(asset=asset1, amount=fill_amounts[1], dt=None,
price=101, order_id=order.id)
txn3 = Transaction(asset=asset1, amount=100, dt=None,
txn3 = Transaction(asset=asset1, amount=fill_amounts[2], dt=None,
price=102, order_id=order.id)
return order, [txn1, txn2, txn3]
def test_per_trade(self):
model = PerTrade(cost=10)
def verify_per_trade_commissions(self,
model,
expected_commission,
sid,
order_amount=None,
fill_amounts=None):
fill_amounts = fill_amounts or [230, 170, 100]
order_amount = order_amount or sum(fill_amounts)
order, txns = self.generate_order_and_txns()
order, txns = self.generate_order_and_txns(
sid, order_amount, fill_amounts,
)
self.assertEqual(10, model.calculate(order, txns[0]))
self.assertEqual(expected_commission, model.calculate(order, txns[0]))
order.commission = 10
order.commission = expected_commission
self.assertEqual(0, model.calculate(order, txns[1]))
self.assertEqual(0, model.calculate(order, txns[2]))
def test_per_trade(self):
# Test per trade model for equities.
model = PerTrade(cost=10)
self.verify_per_trade_commissions(model, expected_commission=10, sid=1)
# Test per trade model for futures.
model = PerFutureTrade(cost=10)
self.verify_per_trade_commissions(
model, expected_commission=10, sid=1000,
)
# Test per trade model with custom costs per future symbol.
model = PerFutureTrade(cost={'CL': 5, 'FV': 10})
self.verify_per_trade_commissions(
model, expected_commission=5, sid=1000,
)
self.verify_per_trade_commissions(
model, expected_commission=10, sid=1001,
)
def test_per_share_no_minimum(self):
model = PerShare(cost=0.0075, min_trade_cost=None)
order, txns = self.generate_order_and_txns()
order, txns = self.generate_order_and_txns(
sid=1, order_amount=500, fill_amounts=[230, 170, 100],
)
# make sure each commission is pro-rated
self.assertAlmostEqual(1.725, model.calculate(order, txns[0]))
self.assertAlmostEqual(1.275, model.calculate(order, txns[1]))
self.assertAlmostEqual(0.75, model.calculate(order, txns[2]))
def verify_per_share_commissions(self, model, commission_totals):
order, txns = self.generate_order_and_txns()
def verify_per_unit_commissions(self,
model,
commission_totals,
sid,
order_amount=None,
fill_amounts=None):
fill_amounts = fill_amounts or [230, 170, 100]
order_amount = order_amount or sum(fill_amounts)
order, txns = self.generate_order_and_txns(
sid, order_amount, fill_amounts,
)
for i, commission_total in enumerate(commission_totals):
order.commission += model.calculate(order, txns[i])
self.assertAlmostEqual(commission_total, order.commission)
order.filled += txns[i].amount
def test_per_contract_no_minimum(self):
# Note that the exchange fee is a one-time cost that is only applied to
# the first fill of an order.
#
# The commission on the first fill is (230 * 0.01) + 0.3 = 2.6
# The commission on the second fill is 170 * 0.01 = 1.7
# The total after the second fill is 2.6 + 1.7 = 4.3
# The commission on the third fill is 100 * 0.01 = 1.0
# The total after the third fill is 5.3
model = PerContract(cost=0.01, exchange_fee=0.3, min_trade_cost=None)
self.verify_per_unit_commissions(
model=model,
commission_totals=[2.6, 4.3, 5.3],
sid=1000,
order_amount=500,
fill_amounts=[230, 170, 100],
)
# Test using custom costs and fees.
model = PerContract(
cost={'CL': 0.01, 'FV': 0.0075},
exchange_fee={'CL': 0.3, 'FV': 0.5},
min_trade_cost=None,
)
self.verify_per_unit_commissions(model, [2.6, 4.3, 5.3], sid=1000)
self.verify_per_unit_commissions(model, [2.225, 3.5, 4.25], sid=1001)
def test_per_share_with_minimum(self):
# minimum is met by the first trade
self.verify_per_share_commissions(
self.verify_per_unit_commissions(
PerShare(cost=0.0075, min_trade_cost=1),
[1.725, 3, 3.75]
commission_totals=[1.725, 3, 3.75],
sid=1,
)
# minimum is met by the second trade
self.verify_per_share_commissions(
self.verify_per_unit_commissions(
PerShare(cost=0.0075, min_trade_cost=2.5),
[2.5, 3, 3.75]
commission_totals=[2.5, 3, 3.75],
sid=1,
)
# minimum is met by the third trade
self.verify_per_share_commissions(
self.verify_per_unit_commissions(
PerShare(cost=0.0075, min_trade_cost=3.5),
[3.5, 3.5, 3.75]
commission_totals=[3.5, 3.5, 3.75],
sid=1,
)
# minimum is not met by any of the trades
self.verify_per_share_commissions(
self.verify_per_unit_commissions(
PerShare(cost=0.0075, min_trade_cost=5.5),
[5.5, 5.5, 5.5]
commission_totals=[5.5, 5.5, 5.5],
sid=1,
)
def test_per_contract_with_minimum(self):
# Minimum is met by the first trade.
self.verify_per_unit_commissions(
PerContract(cost=.01, exchange_fee=0.3, min_trade_cost=1),
commission_totals=[2.6, 4.3, 5.3],
sid=1000,
)
# Minimum is met by the second trade.
self.verify_per_unit_commissions(
PerContract(cost=.01, exchange_fee=0.3, min_trade_cost=3),
commission_totals=[3.0, 4.3, 5.3],
sid=1000,
)
# Minimum is met by the third trade.
self.verify_per_unit_commissions(
PerContract(cost=.01, exchange_fee=0.3, min_trade_cost=5),
commission_totals=[5.0, 5.0, 5.3],
sid=1000,
)
# Minimum is not met by any of the trades.
self.verify_per_unit_commissions(
PerContract(cost=.01, exchange_fee=0.3, min_trade_cost=7),
commission_totals=[7.0, 7.0, 7.0],
sid=1000,
)
def test_per_dollar(self):
model = PerDollar(cost=0.0015)
order, txns = self.generate_order_and_txns()
order, txns = self.generate_order_and_txns(
sid=1, order_amount=500, fill_amounts=[230, 170, 100],
)
# make sure each commission is pro-rated
self.assertAlmostEqual(34.5, model.calculate(order, txns[0]))
@@ -116,19 +242,36 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
def initialize(context):
# for these tests, let us take out the entire bar with no price
# impact
set_slippage(slippage.VolumeShareSlippage(1.0, 0))
set_slippage(
us_equities=slippage.VolumeShareSlippage(1.0, 0),
us_futures=slippage.VolumeShareSlippage(1.0, 0),
)
{0}
{commission}
context.ordered = False
def handle_data(context, data):
if not context.ordered:
order(sid(133), {1})
order(sid({sid}), {amount})
context.ordered = True
""",
)
@classmethod
def make_futures_info(cls):
return DataFrame({
'sid': [1000, 1001],
'root_symbol': ['CL', 'FV'],
'symbol': ['CLF07', 'FVF07'],
'start_date': [cls.START_DATE, cls.START_DATE],
'end_date': [cls.END_DATE, cls.END_DATE],
'notice_date': [cls.END_DATE, cls.END_DATE],
'expiration_date': [cls.END_DATE, cls.END_DATE],
'multiplier': [500, 500],
'exchange': ['CME', 'CME'],
})
@classmethod
def make_equity_daily_bar_data(cls):
num_days = len(cls.sim_params.sessions)
@@ -158,7 +301,11 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
def test_per_trade(self):
results = self.get_results(
self.code.format("set_commission(commission.PerTrade(1))", 300)
self.code.format(
commission="set_commission(commission.PerTrade(1))",
sid=133,
amount=300,
)
)
# should be 3 fills at 100 shares apiece
@@ -169,10 +316,30 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
self.verify_capital_used(results, [-1001, -1000, -1000])
def test_futures_per_trade(self):
results = self.get_results(
self.code.format(
commission=(
'set_commission(us_futures=commission.PerFutureTrade(1))'
),
sid=1000,
amount=10,
)
)
# The capital used is only -1.0 (the commission cost) because no
# capital is actually spent to enter into a long position on a futures
# contract.
self.assertEqual(results.orders[1][0]['commission'], 1.0)
self.assertEqual(results.capital_used[1], -1.0)
def test_per_share_no_minimum(self):
results = self.get_results(
self.code.format("set_commission(commission.PerShare(0.05, None))",
300)
self.code.format(
commission="set_commission(commission.PerShare(0.05, None))",
sid=133,
amount=300,
)
)
# should be 3 fills at 100 shares apiece
@@ -186,8 +353,11 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
def test_per_share_with_minimum(self):
# minimum hit by first trade
results = self.get_results(
self.code.format("set_commission(commission.PerShare(0.05, 3))",
300)
self.code.format(
commission="set_commission(commission.PerShare(0.05, 3))",
sid=133,
amount=300,
)
)
# commissions should be 5, 10, 15
@@ -198,8 +368,11 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
# minimum hit by second trade
results = self.get_results(
self.code.format("set_commission(commission.PerShare(0.05, 8))",
300)
self.code.format(
commission="set_commission(commission.PerShare(0.05, 8))",
sid=133,
amount=300,
)
)
# commissions should be 8, 10, 15
@@ -211,8 +384,11 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
# minimum hit by third trade
results = self.get_results(
self.code.format("set_commission(commission.PerShare(0.05, 12))",
300)
self.code.format(
commission="set_commission(commission.PerShare(0.05, 12))",
sid=133,
amount=300,
)
)
# commissions should be 12, 12, 15
@@ -224,8 +400,11 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
# minimum never hit
results = self.get_results(
self.code.format("set_commission(commission.PerShare(0.05, 18))",
300)
self.code.format(
commission="set_commission(commission.PerShare(0.05, 18))",
sid=133,
amount=300,
)
)
# commissions should be 18, 18, 18
@@ -235,9 +414,40 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
self.verify_capital_used(results, [-1018, -1000, -1000])
@parameterized.expand([
# The commission is (10 * 0.05) + 1.3 = 1.8, and the capital used is
# the same as the commission cost because no capital is actually spent
# to enter into a long position on a futures contract.
(None, 1.8),
# Minimum hit by first trade.
(1, 1.8),
# Minimum not hit by first trade, so use the minimum.
(3, 3.0),
])
def test_per_contract(self, min_trade_cost, expected_commission):
results = self.get_results(
self.code.format(
commission=(
'set_commission(us_futures=commission.PerContract('
'cost=0.05, exchange_fee=1.3, min_trade_cost={}))'
).format(min_trade_cost),
sid=1000,
amount=10,
),
)
self.assertEqual(
results.orders[1][0]['commission'], expected_commission,
)
self.assertEqual(results.capital_used[1], -expected_commission)
def test_per_dollar(self):
results = self.get_results(
self.code.format("set_commission(commission.PerDollar(0.01))", 300)
self.code.format(
commission="set_commission(commission.PerDollar(0.01))",
sid=133,
amount=300,
)
)
# should be 3 fills at 100 shares apiece, each fill is worth $1k, so
@@ -249,6 +459,18 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
self.verify_capital_used(results, [-1010, -1010, -1010])
def test_incorrectly_set_futures_model(self):
with self.assertRaises(IncompatibleCommissionModel):
# Passing a futures commission model as the first argument, which
# is for setting equity models, should fail.
self.get_results(
self.code.format(
commission='set_commission(commission.PerContract(0, 0))',
sid=1000,
amount=10,
)
)
def verify_capital_used(self, results, values):
self.assertEqual(values[0], results.capital_used[1])
self.assertEqual(values[1], results.capital_used[2])
+275 -12
View File
@@ -16,24 +16,32 @@
'''
Unit tests for finance.slippage
'''
import datetime
from collections import namedtuple
import pytz
import datetime
from math import sqrt
from nose_parameterized import parameterized
import pandas as pd
from pandas.tslib import normalize_date
import numpy as np
import pandas as pd
import pytz
from zipline.finance.slippage import VolumeShareSlippage, \
fill_price_worse_than_limit_price
from zipline.protocol import DATASOURCE_TYPE, BarData
from zipline.finance.blotter import Order
from zipline.finance.asset_restrictions import NoRestrictions
from zipline.assets import Equity
from zipline.data.data_portal import DataPortal
from zipline.testing import tmp_bcolz_equity_minute_bar_reader
from zipline.finance.asset_restrictions import NoRestrictions
from zipline.finance.order import Order
from zipline.finance.slippage import (
fill_price_worse_than_limit_price,
MarketImpactBase,
NO_DATA_VOLATILITY_SLIPPAGE_IMPACT,
VolatilityVolumeShare,
VolumeShareSlippage,
)
from zipline.protocol import DATASOURCE_TYPE, BarData
from zipline.testing import (
create_minute_bar_data,
tmp_bcolz_equity_minute_bar_reader,
)
from zipline.testing.fixtures import (
WithCreateBarData,
WithDataPortal,
@@ -560,10 +568,36 @@ class VolumeShareSlippageTestCase(WithCreateBarData,
index=[cls.minutes[0]],
)
@classmethod
def make_futures_info(cls):
return pd.DataFrame({
'sid': [1000],
'root_symbol': ['CL'],
'symbol': ['CLF06'],
'start_date': [cls.ASSET_FINDER_EQUITY_START_DATE],
'end_date': [cls.ASSET_FINDER_EQUITY_END_DATE],
'multiplier': [500],
'exchange': ['CME'],
})
@classmethod
def make_future_minute_bar_data(cls):
yield 1000, pd.DataFrame(
{
'open': [5.00],
'high': [5.15],
'low': [4.85],
'close': [5.00],
'volume': [100],
},
index=[cls.minutes[0]],
)
@classmethod
def init_class_fixtures(cls):
super(VolumeShareSlippageTestCase, cls).init_class_fixtures()
cls.ASSET133 = cls.env.asset_finder.retrieve_asset(133)
cls.ASSET1000 = cls.env.asset_finder.retrieve_asset(1000)
def test_volume_share_slippage(self):
@@ -631,6 +665,235 @@ class VolumeShareSlippageTestCase(WithCreateBarData,
self.assertEquals(len(orders_txns), 0)
def test_volume_share_slippage_with_future(self):
slippage_model = VolumeShareSlippage(volume_limit=1, price_impact=0.3)
open_orders = [
Order(
dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
amount=10,
filled=0,
asset=self.ASSET1000,
),
]
bar_data = self.create_bardata(
simulation_dt_func=lambda: self.minutes[0],
)
orders_txns = list(
slippage_model.simulate(bar_data, self.ASSET1000, open_orders)
)
self.assertEquals(len(orders_txns), 1)
_, txn = orders_txns[0]
# We expect to fill the order for all 10 contracts. The volume for the
# futures contract in this bar is 100, so our volume share is:
# 10.0 / 100 = 0.1
# The current price is 5.0 and the price impact is 0.3, so the expected
# impacted price is:
# 5.0 + (5.0 * (0.1 ** 2) * 0.3) = 5.015
expected_txn = {
'price': 5.015,
'dt': datetime.datetime(2006, 1, 5, 14, 31, tzinfo=pytz.utc),
'amount': 10,
'asset': self.ASSET1000,
'commission': None,
'type': DATASOURCE_TYPE.TRANSACTION,
'order_id': open_orders[0].id,
}
self.assertIsNotNone(txn)
self.assertEquals(expected_txn, txn.__dict__)
class VolatilityVolumeShareTestCase(WithCreateBarData,
WithSimParams,
WithDataPortal,
ZiplineTestCase):
ASSET_START_DATE = pd.Timestamp('2006-02-10')
TRADING_CALENDAR_STRS = ('NYSE', 'us_futures')
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
@classmethod
def init_class_fixtures(cls):
super(VolatilityVolumeShareTestCase, cls).init_class_fixtures()
cls.ASSET = cls.asset_finder.retrieve_asset(1000)
@classmethod
def make_futures_info(cls):
return pd.DataFrame({
'sid': [1000],
'root_symbol': ['CL'],
'symbol': ['CLF07'],
'start_date': [cls.ASSET_START_DATE],
'end_date': [cls.END_DATE],
'multiplier': [500],
'exchange': ['CME'],
})
@classmethod
def make_future_minute_bar_data(cls):
data = list(
super(
VolatilityVolumeShareTestCase, cls,
).make_future_minute_bar_data()
)
# Make the first month's worth of data NaN to simulate cases where a
# futures contract does not exist yet.
data[0][1].loc[:cls.ASSET_START_DATE] = np.NaN
return data
def test_calculate_impact_buy(self):
answer_key = [
# We ordered 10 contracts, but are capped at 100 * 0.05 = 5
(91485.500085168125, 5),
(91486.500085169057, 5),
(None, None),
]
order = Order(
dt=pd.Timestamp.now(tz='utc').round('min'),
asset=self.ASSET,
amount=10,
)
self._calculate_impact(order, answer_key)
def test_calculate_impact_sell(self):
answer_key = [
# We ordered -10 contracts, but are capped at -(100 * 0.05) = -5
(91485.499914831875, -5),
(91486.499914830943, -5),
(None, None),
]
order = Order(
dt=pd.Timestamp.now(tz='utc').round('min'),
asset=self.ASSET,
amount=-10,
)
self._calculate_impact(order, answer_key)
def _calculate_impact(self, test_order, answer_key):
model = VolatilityVolumeShare(volume_limit=0.05)
first_minute = pd.Timestamp('2006-03-31 11:35AM', tz='UTC')
next_3_minutes = self.trading_calendar.minutes_window(first_minute, 3)
remaining_shares = test_order.open_amount
for i, minute in enumerate(next_3_minutes):
data = self.create_bardata(simulation_dt_func=lambda: minute)
new_order = Order(
dt=data.current_dt, asset=self.ASSET, amount=remaining_shares,
)
price, amount = model.process_order(data, new_order)
self.assertEqual(price, answer_key[i][0])
self.assertEqual(amount, answer_key[i][1])
amount = amount or 0
if remaining_shares < 0:
remaining_shares = min(0, remaining_shares - amount)
else:
remaining_shares = max(0, remaining_shares - amount)
def test_calculate_impact_without_history(self):
model = VolatilityVolumeShare(volume_limit=1)
minutes = [
# Start day of the futures contract; no history yet.
pd.Timestamp('2006-02-10 11:35AM', tz='UTC'),
# Only a week's worth of history data.
pd.Timestamp('2006-02-17 11:35AM', tz='UTC'),
]
for minute in minutes:
data = self.create_bardata(simulation_dt_func=lambda: minute)
order = Order(dt=data.current_dt, asset=self.ASSET, amount=10)
price, amount = model.process_order(data, order)
avg_price = (
data.current(self.ASSET, 'high') +
data.current(self.ASSET, 'low')
) / 2
expected_price = \
avg_price + (avg_price * NO_DATA_VOLATILITY_SLIPPAGE_IMPACT)
self.assertEqual(price, expected_price)
self.assertEqual(amount, 10)
def test_impacted_price_worse_than_limit(self):
model = VolatilityVolumeShare(volume_limit=0.05)
# Use all the same numbers from the 'calculate_impact' tests. Since the
# impacted price is 59805.5, which is worse than the limit price of
# 59800, the model should return None.
minute = pd.Timestamp('2006-03-01 11:35AM', tz='UTC')
data = self.create_bardata(simulation_dt_func=lambda: minute)
order = Order(
dt=data.current_dt, asset=self.ASSET, amount=10, limit=59800,
)
price, amount = model.process_order(data, order)
self.assertIsNone(price)
self.assertIsNone(amount)
class MarketImpactTestCase(WithCreateBarData, ZiplineTestCase):
ASSET_FINDER_EQUITY_SIDS = (1,)
@classmethod
def make_equity_minute_bar_data(cls):
trading_calendar = cls.trading_calendars[Equity]
return create_minute_bar_data(
trading_calendar.minutes_for_sessions_in_range(
cls.equity_minute_bar_days[0],
cls.equity_minute_bar_days[-1],
),
cls.asset_finder.equities_sids,
)
def test_window_data(self):
session = pd.Timestamp('2006-03-01')
minute = self.trading_calendar.minutes_for_session(session)[1]
data = self.create_bardata(simulation_dt_func=lambda: minute)
asset = self.asset_finder.retrieve_asset(1)
mean_volume, volatility = MarketImpactBase()._get_window_data(
data, asset, window_length=20,
)
# close volume
# 2006-01-31 00:00:00+00:00 29.0 119.0
# 2006-02-01 00:00:00+00:00 30.0 120.0
# 2006-02-02 00:00:00+00:00 31.0 121.0
# 2006-02-03 00:00:00+00:00 32.0 122.0
# 2006-02-06 00:00:00+00:00 33.0 123.0
# 2006-02-07 00:00:00+00:00 34.0 124.0
# 2006-02-08 00:00:00+00:00 35.0 125.0
# 2006-02-09 00:00:00+00:00 36.0 126.0
# 2006-02-10 00:00:00+00:00 37.0 127.0
# 2006-02-13 00:00:00+00:00 38.0 128.0
# 2006-02-14 00:00:00+00:00 39.0 129.0
# 2006-02-15 00:00:00+00:00 40.0 130.0
# 2006-02-16 00:00:00+00:00 41.0 131.0
# 2006-02-17 00:00:00+00:00 42.0 132.0
# 2006-02-21 00:00:00+00:00 43.0 133.0
# 2006-02-22 00:00:00+00:00 44.0 134.0
# 2006-02-23 00:00:00+00:00 45.0 135.0
# 2006-02-24 00:00:00+00:00 46.0 136.0
# 2006-02-27 00:00:00+00:00 47.0 137.0
# 2006-02-28 00:00:00+00:00 48.0 138.0
# Mean volume is (119 + 138) / 2 = 128.5
self.assertEqual(mean_volume, 128.5)
# Volatility is closes.pct_change().std() * sqrt(252)
reference_vol = pd.Series(range(29, 49)).pct_change().std() * sqrt(252)
self.assertEqual(volatility, reference_vol)
class OrdersStopTestCase(WithSimParams,
WithTradingEnvironment,
+115
View File
@@ -56,6 +56,7 @@ from zipline.data.us_equity_pricing import (
from zipline.errors import (
AccountControlViolation,
CannotOrderDelistedAsset,
IncompatibleSlippageModel,
OrderDuringInitialize,
OrderInBeforeTradingStart,
RegisterTradingControlPostInit,
@@ -1738,6 +1739,27 @@ def handle_data(context, data):
finally:
tempdir.cleanup()
def test_incorrectly_set_futures_slippage_model(self):
code = dedent(
"""
from zipline.api import set_slippage, slippage
class MySlippage(slippage.FutureSlippageModel):
def process_order(self, data, order):
return data.current(order.asset, 'price'), order.amount
def initialize(context):
set_slippage(MySlippage())
"""
)
test_algo = TradingAlgorithm(
script=code, sim_params=self.sim_params, env=self.env,
)
with self.assertRaises(IncompatibleSlippageModel):
# Passing a futures slippage model as the first argument, which is
# for setting equity models, should fail.
test_algo.run(self.data_portal)
def test_algo_record_vars(self):
test_algo = TradingAlgorithm(
script=record_variables,
@@ -3655,6 +3677,99 @@ class TestFuturesAlgo(WithDataPortal, WithSimParams, ZiplineTestCase):
algo.history_values[1].values, list(map(float, range(3636, 3641))),
)
@staticmethod
def algo_with_slippage(slippage_model):
return dedent(
"""
from zipline.api import (
commission,
order,
set_commission,
set_slippage,
sid,
slippage,
get_datetime,
)
def initialize(context):
commission_model = commission.PerFutureTrade(0)
set_commission(us_futures=commission_model)
slippage_model = slippage.{model}
set_slippage(us_futures=slippage_model)
context.ordered = False
def handle_data(context, data):
if not context.ordered:
order(sid(1), 10)
context.ordered = True
context.order_price = data.current(sid(1), 'price')
"""
).format(model=slippage_model)
def test_fixed_future_slippage(self):
algo_code = self.algo_with_slippage('FixedSlippage(spread=0.10)')
algo = TradingAlgorithm(
script=algo_code,
sim_params=self.sim_params,
env=self.env,
trading_calendar=get_calendar('us_futures'),
)
results = algo.run(self.data_portal)
# Flatten the list of transactions.
all_txns = [
val for sublist in results['transactions'].tolist()
for val in sublist
]
self.assertEqual(len(all_txns), 1)
txn = all_txns[0]
# Add 1 to the expected price because the order does not fill until the
# bar after the price is recorded.
expected_spread = 0.05
expected_price = (algo.order_price + 1) + expected_spread
# Capital used should be 0 because there is no commission, and the cost
# to enter into a long position on a futures contract is 0.
self.assertEqual(txn['price'], expected_price)
self.assertEqual(results['orders'][0][0]['commission'], 0.0)
self.assertEqual(results.capital_used[0], 0.0)
def test_volume_contract_slippage(self):
algo_code = self.algo_with_slippage(
'VolumeShareSlippage(volume_limit=0.05, price_impact=0.1)',
)
algo = TradingAlgorithm(
script=algo_code,
sim_params=self.sim_params,
env=self.env,
trading_calendar=get_calendar('us_futures'),
)
results = algo.run(self.data_portal)
# There should be no commissions.
self.assertEqual(results['orders'][0][0]['commission'], 0.0)
# Flatten the list of transactions.
all_txns = [
val for sublist in results['transactions'].tolist()
for val in sublist
]
# With a volume limit of 0.05, and a total volume of 100 contracts
# traded per minute, we should require 2 transactions to order 10
# contracts.
self.assertEqual(len(all_txns), 2)
for i, txn in enumerate(all_txns):
# Add 1 to the order price because the order does not fill until
# the bar after the price is recorded.
order_price = algo.order_price + i + 1
expected_impact = order_price * 0.1 * (0.05 ** 2)
expected_price = order_price + expected_impact
self.assertEqual(txn['price'], expected_price)
class TestTradingAlgorithm(ZiplineTestCase):
def test_analyze_called(self):