Merge pull request #1757 from quantopian/futures-commissions

Assets everywhere ... and a futures fix.
This commit is contained in:
Jean Bredeche
2017-04-24 16:31:57 -04:00
committed by GitHub
20 changed files with 430 additions and 475 deletions
+16 -27
View File
@@ -108,7 +108,7 @@ class BlotterTestCase(WithCreateBarData,
(StopLimitOrder(10, 20), 10, 20)]) (StopLimitOrder(10, 20), 10, 20)])
def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp): def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp):
blotter = Blotter('daily', self.asset_finder) blotter = Blotter('daily')
blotter.order(self.asset_24, 100, style_obj) blotter.order(self.asset_24, 100, style_obj)
result = blotter.open_orders[self.asset_24][0] result = blotter.open_orders[self.asset_24][0]
@@ -117,7 +117,7 @@ class BlotterTestCase(WithCreateBarData,
self.assertEqual(result.stop, expected_stp) self.assertEqual(result.stop, expected_stp)
def test_cancel(self): def test_cancel(self):
blotter = Blotter('daily', self.asset_finder) blotter = Blotter('daily')
oid_1 = blotter.order(self.asset_24, 100, MarketOrder()) oid_1 = blotter.order(self.asset_24, 100, MarketOrder())
oid_2 = blotter.order(self.asset_24, 200, MarketOrder()) oid_2 = blotter.order(self.asset_24, 200, MarketOrder())
@@ -151,10 +151,9 @@ class BlotterTestCase(WithCreateBarData,
self.assertEqual(list(blotter.open_orders), [self.asset_25]) self.assertEqual(list(blotter.open_orders), [self.asset_25])
def test_blotter_eod_cancellation(self): def test_blotter_eod_cancellation(self):
blotter = Blotter('minute', self.asset_finder, blotter = Blotter('minute', cancel_policy=EODCancel())
cancel_policy=EODCancel())
# Make two orders for the same sid, so we can test that we are not # Make two orders for the same asset, so we can test that we are not
# mutating the orders list as we are cancelling orders # mutating the orders list as we are cancelling orders
blotter.order(self.asset_24, 100, MarketOrder()) blotter.order(self.asset_24, 100, MarketOrder())
blotter.order(self.asset_24, -100, MarketOrder()) blotter.order(self.asset_24, -100, MarketOrder())
@@ -175,8 +174,7 @@ class BlotterTestCase(WithCreateBarData,
self.assertEqual(order.status, ORDER_STATUS.CANCELLED) self.assertEqual(order.status, ORDER_STATUS.CANCELLED)
def test_blotter_never_cancel(self): def test_blotter_never_cancel(self):
blotter = Blotter('minute', self.asset_finder, blotter = Blotter('minute', cancel_policy=NeverCancel())
cancel_policy=NeverCancel())
blotter.order(self.asset_24, 100, MarketOrder()) blotter.order(self.asset_24, 100, MarketOrder())
@@ -190,8 +188,7 @@ class BlotterTestCase(WithCreateBarData,
self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
def test_order_rejection(self): def test_order_rejection(self):
blotter = Blotter(self.sim_params.data_frequency, blotter = Blotter(self.sim_params.data_frequency)
self.asset_finder)
# Reject a nonexistent order -> no order appears in new_order, # Reject a nonexistent order -> no order appears in new_order,
# no exceptions raised out # no exceptions raised out
@@ -220,8 +217,7 @@ class BlotterTestCase(WithCreateBarData,
# Do it again, but reject it at a later time (after tradesimulation # Do it again, but reject it at a later time (after tradesimulation
# pulls it from new_orders) # pulls it from new_orders)
blotter = Blotter(self.sim_params.data_frequency, blotter = Blotter(self.sim_params.data_frequency)
self.asset_finder)
new_open_id = blotter.order(self.asset_24, 10, MarketOrder()) new_open_id = blotter.order(self.asset_24, 10, MarketOrder())
new_open_order = blotter.open_orders[self.asset_24][0] new_open_order = blotter.open_orders[self.asset_24][0]
self.assertEqual(new_open_id, new_open_order.id) self.assertEqual(new_open_id, new_open_order.id)
@@ -237,8 +233,7 @@ class BlotterTestCase(WithCreateBarData,
# You can't reject a filled order. # You can't reject a filled order.
# Reset for paranoia # Reset for paranoia
blotter = Blotter(self.sim_params.data_frequency, blotter = Blotter(self.sim_params.data_frequency)
self.asset_finder)
blotter.slippage_models[Equity] = FixedSlippage() blotter.slippage_models[Equity] = FixedSlippage()
filled_id = blotter.order(self.asset_24, 100, MarketOrder()) filled_id = blotter.order(self.asset_24, 100, MarketOrder())
filled_order = None filled_order = None
@@ -266,8 +261,7 @@ class BlotterTestCase(WithCreateBarData,
status indication. When a fill happens, the order should switch status indication. When a fill happens, the order should switch
status to OPEN/FILLED as necessary status to OPEN/FILLED as necessary
""" """
blotter = Blotter(self.sim_params.data_frequency, blotter = Blotter(self.sim_params.data_frequency)
self.asset_finder)
# Nothing happens on held of a non-existent order # Nothing happens on held of a non-existent order
blotter.hold(56) blotter.hold(56)
self.assertEqual(blotter.new_orders, []) self.assertEqual(blotter.new_orders, [])
@@ -303,8 +297,7 @@ class BlotterTestCase(WithCreateBarData,
expected_status = ORDER_STATUS.OPEN if expected_open else \ expected_status = ORDER_STATUS.OPEN if expected_open else \
ORDER_STATUS.FILLED ORDER_STATUS.FILLED
blotter = Blotter(self.sim_params.data_frequency, blotter = Blotter(self.sim_params.data_frequency)
self.asset_finder)
open_id = blotter.order(self.asset_24, order_size, MarketOrder()) open_id = blotter.order(self.asset_24, order_size, MarketOrder())
open_order = blotter.open_orders[self.asset_24][0] open_order = blotter.open_orders[self.asset_24][0]
self.assertEqual(open_id, open_order.id) self.assertEqual(open_id, open_order.id)
@@ -326,8 +319,7 @@ class BlotterTestCase(WithCreateBarData,
self.assertEqual(filled_order.open_amount, expected_open) self.assertEqual(filled_order.open_amount, expected_open)
def test_prune_orders(self): def test_prune_orders(self):
blotter = Blotter(self.sim_params.data_frequency, blotter = Blotter(self.sim_params.data_frequency)
self.asset_finder)
blotter.order(self.asset_24, 100, MarketOrder()) blotter.order(self.asset_24, 100, MarketOrder())
open_order = blotter.open_orders[self.asset_24][0] open_order = blotter.open_orders[self.asset_24][0]
@@ -343,7 +335,7 @@ class BlotterTestCase(WithCreateBarData,
other_order = Order( other_order = Order(
dt=blotter.current_dt, dt=blotter.current_dt,
sid=self.asset_25, asset=self.asset_25,
amount=1 amount=1
) )
@@ -354,10 +346,8 @@ class BlotterTestCase(WithCreateBarData,
Ensure the effect of order_batch is the same as multiple calls to Ensure the effect of order_batch is the same as multiple calls to
order. order.
""" """
blotter1 = Blotter(self.sim_params.data_frequency, blotter1 = Blotter(self.sim_params.data_frequency)
self.asset_finder) blotter2 = Blotter(self.sim_params.data_frequency)
blotter2 = Blotter(self.sim_params.data_frequency,
self.asset_finder)
for i in range(1, 4): for i in range(1, 4):
order_arg_lists = [ order_arg_lists = [
(self.asset_24, i * 100, MarketOrder()), (self.asset_24, i * 100, MarketOrder()),
@@ -386,7 +376,6 @@ class BlotterTestCase(WithCreateBarData,
def test_slippage_and_commission_dispatching(self): def test_slippage_and_commission_dispatching(self):
blotter = Blotter( blotter = Blotter(
self.sim_params.data_frequency, self.sim_params.data_frequency,
self.asset_finder,
equity_slippage=FixedSlippage(spread=0.0), equity_slippage=FixedSlippage(spread=0.0),
future_slippage=FixedSlippage(spread=2.0), future_slippage=FixedSlippage(spread=2.0),
equity_commission=PerTrade(cost=1.0), equity_commission=PerTrade(cost=1.0),
@@ -406,7 +395,7 @@ class BlotterTestCase(WithCreateBarData,
equity_txn = txns[0] equity_txn = txns[0]
self.assertEqual( self.assertEqual(
equity_txn.price, equity_txn.price,
bar_data.current(equity_txn.sid, 'price'), bar_data.current(equity_txn.asset, 'price'),
) )
self.assertEqual(commissions[0]['cost'], 1.0) self.assertEqual(commissions[0]['cost'], 1.0)
@@ -416,6 +405,6 @@ class BlotterTestCase(WithCreateBarData,
future_txn = txns[1] future_txn = txns[1]
self.assertEqual( self.assertEqual(
future_txn.price, future_txn.price,
bar_data.current(future_txn.sid, 'price') + 1.0, bar_data.current(future_txn.asset, 'price') + 1.0,
) )
self.assertEqual(commissions[1]['cost'], 2.0) self.assertEqual(commissions[1]['cost'], 2.0)
+4 -4
View File
@@ -21,16 +21,16 @@ class CommissionUnitTests(WithAssetFinder, ZiplineTestCase):
asset1 = self.asset_finder.retrieve_asset(1) asset1 = self.asset_finder.retrieve_asset(1)
# one order # one order
order = Order(dt=None, sid=asset1, amount=500) order = Order(dt=None, asset=asset1, amount=500)
# three fills # three fills
txn1 = Transaction(sid=asset1, amount=230, dt=None, txn1 = Transaction(asset=asset1, amount=230, dt=None,
price=100, order_id=order.id) price=100, order_id=order.id)
txn2 = Transaction(sid=asset1, amount=170, dt=None, txn2 = Transaction(asset=asset1, amount=170, dt=None,
price=101, order_id=order.id) price=101, order_id=order.id)
txn3 = Transaction(sid=asset1, amount=100, dt=None, txn3 = Transaction(asset=asset1, amount=100, dt=None,
price=102, order_id=order.id) price=102, order_id=order.id)
return order, [txn1, txn2, txn3] return order, [txn1, txn2, txn3]
+24 -30
View File
@@ -126,7 +126,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'limit': 3.5}) 'limit': 3.5})
] ]
@@ -148,7 +148,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'limit': 3.5}) 'limit': 3.5})
] ]
@@ -170,7 +170,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'limit': 3.6}) 'limit': 3.6})
] ]
@@ -194,7 +194,7 @@ class SlippageTestCase(WithCreateBarData,
# we ordered 100 shares, but default volume slippage only allows # we ordered 100 shares, but default volume slippage only allows
# for 2.5% of the volume. 2.5% * 2000 = 50 shares # for 2.5% of the volume. 2.5% * 2000 = 50 shares
'amount': int(50), 'amount': int(50),
'sid': int(133), 'asset': self.ASSET133,
'order_id': open_orders[0].id 'order_id': open_orders[0].id
} }
@@ -209,7 +209,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'limit': 3.5}) 'limit': 3.5})
] ]
@@ -231,7 +231,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'limit': 3.5}) 'limit': 3.5})
] ]
@@ -253,7 +253,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'limit': 3.4}) 'limit': 3.4})
] ]
@@ -275,7 +275,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime( 'dt': datetime.datetime(
2006, 1, 5, 14, 32, tzinfo=pytz.utc), 2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'amount': int(-50), 'amount': int(-50),
'sid': int(133) 'asset': self.ASSET133,
} }
self.assertIsNotNone(txn) self.assertIsNotNone(txn)
@@ -293,7 +293,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'stop': 4.0, 'stop': 4.0,
'limit': 3.0}) 'limit': 3.0})
] ]
@@ -328,7 +328,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'stop': 4.0, 'stop': 4.0,
'limit': 3.5}) 'limit': 3.5})
] ]
@@ -363,7 +363,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'stop': 4.0, 'stop': 4.0,
'limit': 3.6}) 'limit': 3.6})
] ]
@@ -398,7 +398,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime( 'dt': datetime.datetime(
2006, 1, 5, 14, 34, tzinfo=pytz.utc), 2006, 1, 5, 14, 34, tzinfo=pytz.utc),
'amount': int(50), 'amount': int(50),
'sid': int(133) 'asset': self.ASSET133
} }
for key, value in expected_txn.items(): for key, value in expected_txn.items():
@@ -411,7 +411,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'stop': 3.0, 'stop': 3.0,
'limit': 4.0}) 'limit': 4.0})
] ]
@@ -446,7 +446,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'stop': 3.0, 'stop': 3.0,
'limit': 3.5}) 'limit': 3.5})
] ]
@@ -481,7 +481,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), 'dt': datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': self.ASSET133, 'asset': self.ASSET133,
'stop': 3.0, 'stop': 3.0,
'limit': 3.4}) 'limit': 3.4})
] ]
@@ -516,7 +516,7 @@ class SlippageTestCase(WithCreateBarData,
'dt': datetime.datetime( 'dt': datetime.datetime(
2006, 1, 5, 14, 32, tzinfo=pytz.utc), 2006, 1, 5, 14, 32, tzinfo=pytz.utc),
'amount': int(-50), 'amount': int(-50),
'sid': int(133) 'asset': self.ASSET133,
} }
for key, value in expected_txn.items(): for key, value in expected_txn.items():
@@ -574,7 +574,7 @@ class VolumeShareSlippageTestCase(WithCreateBarData,
dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
amount=100, amount=100,
filled=0, filled=0,
sid=self.ASSET133 asset=self.ASSET133
) )
] ]
@@ -596,7 +596,7 @@ class VolumeShareSlippageTestCase(WithCreateBarData,
'dt': datetime.datetime( 'dt': datetime.datetime(
2006, 1, 5, 14, 31, tzinfo=pytz.utc), 2006, 1, 5, 14, 31, tzinfo=pytz.utc),
'amount': int(5), 'amount': int(5),
'sid': int(133), 'asset': self.ASSET133,
'commission': None, 'commission': None,
'type': DATASOURCE_TYPE.TRANSACTION, 'type': DATASOURCE_TYPE.TRANSACTION,
'order_id': open_orders[0].id 'order_id': open_orders[0].id
@@ -613,7 +613,7 @@ class VolumeShareSlippageTestCase(WithCreateBarData,
dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc), dt=datetime.datetime(2006, 1, 5, 14, 30, tzinfo=pytz.utc),
amount=100, amount=100,
filled=0, filled=0,
sid=self.ASSET133 asset=self.ASSET133
) )
] ]
@@ -684,7 +684,6 @@ class OrdersStopTestCase(WithSimParams,
'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': 133,
'stop': 3.5 'stop': 3.5
}, },
'event': { 'event': {
@@ -693,7 +692,6 @@ class OrdersStopTestCase(WithSimParams,
'price': 4.0, 'price': 4.0,
'high': 3.15, 'high': 3.15,
'low': 2.85, 'low': 2.85,
'sid': 133,
'close': 4.0, 'close': 4.0,
'open': 3.5 'open': 3.5
}, },
@@ -702,7 +700,6 @@ class OrdersStopTestCase(WithSimParams,
'price': 4.00025, 'price': 4.00025,
'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'), 'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
'amount': 50, 'amount': 50,
'sid': 133,
} }
} }
}, },
@@ -711,7 +708,6 @@ class OrdersStopTestCase(WithSimParams,
'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
'amount': 100, 'amount': 100,
'filled': 0, 'filled': 0,
'sid': 133,
'stop': 3.6 'stop': 3.6
}, },
'event': { 'event': {
@@ -720,7 +716,6 @@ class OrdersStopTestCase(WithSimParams,
'price': 3.5, 'price': 3.5,
'high': 3.15, 'high': 3.15,
'low': 2.85, 'low': 2.85,
'sid': 133,
'close': 3.5, 'close': 3.5,
'open': 4.0 'open': 4.0
}, },
@@ -733,7 +728,6 @@ class OrdersStopTestCase(WithSimParams,
'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': 133,
'stop': 3.4 'stop': 3.4
}, },
'event': { 'event': {
@@ -742,7 +736,6 @@ class OrdersStopTestCase(WithSimParams,
'price': 3.5, 'price': 3.5,
'high': 3.15, 'high': 3.15,
'low': 2.85, 'low': 2.85,
'sid': 133,
'close': 3.5, 'close': 3.5,
'open': 3.0 'open': 3.0
}, },
@@ -755,7 +748,6 @@ class OrdersStopTestCase(WithSimParams,
'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'), 'dt': pd.Timestamp('2006-01-05 14:30', tz='UTC'),
'amount': -100, 'amount': -100,
'filled': 0, 'filled': 0,
'sid': 133,
'stop': 3.5 'stop': 3.5
}, },
'event': { 'event': {
@@ -764,7 +756,6 @@ class OrdersStopTestCase(WithSimParams,
'price': 3.0, 'price': 3.0,
'high': 3.15, 'high': 3.15,
'low': 2.85, 'low': 2.85,
'sid': 133,
'close': 3.0, 'close': 3.0,
'open': 3.0 'open': 3.0
}, },
@@ -773,7 +764,6 @@ class OrdersStopTestCase(WithSimParams,
'price': 2.9998125, 'price': 2.9998125,
'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'), 'dt': pd.Timestamp('2006-01-05 14:31', tz='UTC'),
'amount': -50, 'amount': -50,
'sid': 133,
} }
} }
}, },
@@ -785,9 +775,13 @@ class OrdersStopTestCase(WithSimParams,
]) ])
def test_orders_stop(self, name, order_data, event_data, expected): def test_orders_stop(self, name, order_data, event_data, expected):
data = order_data data = order_data
data['sid'] = self.ASSET133 data['asset'] = self.ASSET133
order = Order(**data) order = Order(**data)
if expected['transaction']:
expected['transaction']['asset'] = self.ASSET133
event_data['asset'] = self.ASSET133
assets = ( assets = (
(133, pd.DataFrame( (133, pd.DataFrame(
{ {
+14 -19
View File
@@ -1790,8 +1790,7 @@ def handle_data(context, data):
def test_batch_order_target_percent_matches_multi_order(self): def test_batch_order_target_percent_matches_multi_order(self):
weights = pd.Series([.3, .7]) weights = pd.Series([.3, .7])
multi_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY, multi_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY)
self.asset_finder)
multi_test_algo = TradingAlgorithm( multi_test_algo = TradingAlgorithm(
script=dedent("""\ script=dedent("""\
from collections import OrderedDict from collections import OrderedDict
@@ -1820,8 +1819,7 @@ def handle_data(context, data):
multi_stats = multi_test_algo.run(self.data_portal) multi_stats = multi_test_algo.run(self.data_portal)
self.assertFalse(multi_blotter.order_batch_called) self.assertFalse(multi_blotter.order_batch_called)
batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY, batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY)
self.asset_finder)
batch_test_algo = TradingAlgorithm( batch_test_algo = TradingAlgorithm(
script=dedent("""\ script=dedent("""\
from collections import OrderedDict from collections import OrderedDict
@@ -1864,8 +1862,7 @@ def handle_data(context, data):
def test_batch_order_target_percent_filters_null_orders(self): def test_batch_order_target_percent_filters_null_orders(self):
weights = pd.Series([1, 0]) weights = pd.Series([1, 0])
batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY, batch_blotter = RecordBatchBlotter(self.SIM_PARAMS_DATA_FREQUENCY)
self.asset_finder)
batch_test_algo = TradingAlgorithm( batch_test_algo = TradingAlgorithm(
script=dedent("""\ script=dedent("""\
from collections import OrderedDict from collections import OrderedDict
@@ -3802,7 +3799,7 @@ class TestOrderCancelation(WithDataPortal,
np.copysign(389, direction), np.copysign(389, direction),
daily_positions[0]["amount"], daily_positions[0]["amount"],
) )
self.assertEqual(1, results.positions[0][0]["sid"].sid) self.assertEqual(1, results.positions[0][0]["sid"])
# should be an order on day1, but no more orders afterwards # should be an order on day1, but no more orders afterwards
np.testing.assert_array_equal([1, 0, 0], np.testing.assert_array_equal([1, 0, 0],
@@ -4067,7 +4064,6 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
resources = self.make_data(auto_close_delta, 'daily', capital_base) resources = self.make_data(auto_close_delta, 'daily', capital_base)
assets = resources.assets assets = resources.assets
sids = [asset.sid for asset in assets]
final_prices = resources.final_prices final_prices = resources.final_prices
# Prices at which we expect our orders to be filled. # Prices at which we expect our orders to be filled.
@@ -4174,14 +4170,14 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
self.test_days[1] self.test_days[1]
)[1] )[1]
for sid, txn in zip(sids, initial_fills): for asset, txn in zip(assets, initial_fills):
self.assertDictContainsSubset( self.assertDictContainsSubset(
{ {
'amount': order_size, 'amount': order_size,
'commission': None, 'commission': None,
'dt': last_minute_of_session, 'dt': last_minute_of_session,
'price': initial_fill_prices[sid], 'price': initial_fill_prices[asset],
'sid': sid, 'sid': asset,
}, },
txn, txn,
) )
@@ -4203,7 +4199,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
'commission': 0.0, 'commission': 0.0,
'dt': assets[0].auto_close_date, 'dt': assets[0].auto_close_date,
'price': fp0, 'price': fp0,
'sid': sids[0], 'sid': assets[0],
'order_id': None, # Auto-close txns emit Nones for order_id. 'order_id': None, # Auto-close txns emit Nones for order_id.
}, },
) )
@@ -4218,7 +4214,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
'commission': 0.0, 'commission': 0.0,
'dt': assets[1].auto_close_date, 'dt': assets[1].auto_close_date,
'price': fp1, 'price': fp1,
'sid': sids[1], 'sid': assets[1],
'order_id': None, # Auto-close txns emit Nones for order_id. 'order_id': None, # Auto-close txns emit Nones for order_id.
}, },
) )
@@ -4313,7 +4309,6 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
env = resources.env env = resources.env
assets = resources.assets assets = resources.assets
sids = [a.sid for a in assets]
final_prices = resources.final_prices final_prices = resources.final_prices
backtest_minutes = resources.trade_data_by_sid[0].index.tolist() backtest_minutes = resources.trade_data_by_sid[0].index.tolist()
@@ -4390,14 +4385,14 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
# the backtest, which is still on the first day in minute mode. # the backtest, which is still on the first day in minute mode.
initial_fills = transactions.iloc[0] initial_fills = transactions.iloc[0]
self.assertEqual(len(initial_fills), len(assets)) self.assertEqual(len(initial_fills), len(assets))
for sid, txn in zip(sids, initial_fills): for asset, txn in zip(assets, initial_fills):
self.assertDictContainsSubset( self.assertDictContainsSubset(
{ {
'amount': order_size, 'amount': order_size,
'commission': None, 'commission': None,
'dt': backtest_minutes[1], 'dt': backtest_minutes[1],
'price': initial_fill_prices[sid], 'price': initial_fill_prices[asset],
'sid': sid, 'sid': asset,
}, },
txn, txn,
) )
@@ -4419,7 +4414,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
'commission': 0.0, 'commission': 0.0,
'dt': assets[0].auto_close_date, 'dt': assets[0].auto_close_date,
'price': fp0, 'price': fp0,
'sid': sids[0], 'sid': assets[0],
'order_id': None, # Auto-close txns emit Nones for order_id. 'order_id': None, # Auto-close txns emit Nones for order_id.
}, },
) )
@@ -4434,7 +4429,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingCalendars, ZiplineTestCase):
'commission': 0.0, 'commission': 0.0,
'dt': assets[1].auto_close_date, 'dt': assets[1].auto_close_date,
'price': fp1, 'price': fp1,
'sid': sids[1], 'sid': assets[1],
'order_id': None, # Auto-close txns emit Nones for order_id. 'order_id': None, # Auto-close txns emit Nones for order_id.
}, },
) )
+4
View File
@@ -401,6 +401,10 @@ class DataPortalTestBase(WithDataPortal,
"Asset 10000 had a trade on fourth minute, so should " "Asset 10000 had a trade on fourth minute, so should "
"return that as the last trade on the fifth.") "return that as the last trade on the fifth.")
def test_get_empty_splits(self):
splits = self.data_portal.get_splits([], self.trading_days[2])
self.assertEqual([], splits)
class TestDataPortal(DataPortalTestBase): class TestDataPortal(DataPortalTestBase):
DATA_PORTAL_LAST_AVAILABLE_SESSION = None DATA_PORTAL_LAST_AVAILABLE_SESSION = None
+35 -30
View File
@@ -187,8 +187,8 @@ class FinanceTestCase(WithLogger,
# if present, expect transaction amounts to match orders exactly. # if present, expect transaction amounts to match orders exactly.
complete_fill = params.get('complete_fill') complete_fill = params.get('complete_fill')
sid = 1 asset1 = self.asset_finder.retrieve_asset(1)
metadata = make_simple_equity_info([sid], self.start, self.end) metadata = make_simple_equity_info([asset1.sid], self.start, self.end)
with TempDirectory() as tempdir, \ with TempDirectory() as tempdir, \
tmp_trading_env(equities=metadata) as env: tmp_trading_env(equities=metadata) as env:
@@ -206,7 +206,7 @@ class FinanceTestCase(WithLogger,
price_data = np.array([10.1] * len(minutes)) price_data = np.array([10.1] * len(minutes))
assets = { assets = {
sid: pd.DataFrame({ asset1.sid: pd.DataFrame({
"open": price_data, "open": price_data,
"high": price_data, "high": price_data,
"low": price_data, "low": price_data,
@@ -275,8 +275,7 @@ class FinanceTestCase(WithLogger,
else: else:
slippage_func = None slippage_func = None
blotter = Blotter(sim_params.data_frequency, self.env.asset_finder, blotter = Blotter(sim_params.data_frequency, slippage_func)
slippage_func)
start_date = sim_params.first_open start_date = sim_params.first_open
@@ -305,7 +304,7 @@ class FinanceTestCase(WithLogger,
# place an order # place an order
direction = alternator ** len(order_list) direction = alternator ** len(order_list)
order_id = blotter.order( order_id = blotter.order(
blotter.asset_finder.retrieve_asset(sid), asset1,
order_amount * direction, order_amount * direction,
MarketOrder()) MarketOrder())
order_list.append(blotter.orders[order_id]) order_list.append(blotter.orders[order_id])
@@ -333,7 +332,7 @@ class FinanceTestCase(WithLogger,
for i in range(order_count): for i in range(order_count):
order = order_list[i] order = order_list[i]
self.assertEqual(order.sid, sid) self.assertEqual(order.asset, asset1)
self.assertEqual(order.amount, order_amount * alternator ** i) self.assertEqual(order.amount, order_amount * alternator ** i)
if complete_fill: if complete_fill:
@@ -351,48 +350,54 @@ class FinanceTestCase(WithLogger,
self.assertEqual(len(transactions), expected_txn_count) self.assertEqual(len(transactions), expected_txn_count)
cumulative_pos = tracker.position_tracker.positions[sid] cumulative_pos = tracker.position_tracker.positions[asset1]
if total_volume == 0: if total_volume == 0:
self.assertIsNone(cumulative_pos) self.assertIsNone(cumulative_pos)
else: else:
self.assertEqual(total_volume, cumulative_pos.amount) self.assertEqual(total_volume, cumulative_pos.amount)
# the open orders should not contain sid. # the open orders should not contain the asset.
oo = blotter.open_orders oo = blotter.open_orders
self.assertNotIn(sid, oo, "Entry is removed when no open orders") self.assertNotIn(
asset1,
oo,
"Entry is removed when no open orders"
)
def test_blotter_processes_splits(self): def test_blotter_processes_splits(self):
blotter = Blotter('daily', self.env.asset_finder, blotter = Blotter('daily', equity_slippage=FixedSlippage())
equity_slippage=FixedSlippage())
# set up two open limit orders with very low limit prices, # set up two open limit orders with very low limit prices,
# one for sid 1 and one for sid 2 # one for sid 1 and one for sid 2
blotter.order( asset1 = self.asset_finder.retrieve_asset(1)
blotter.asset_finder.retrieve_asset(1), 100, LimitOrder(10)) asset2 = self.asset_finder.retrieve_asset(2)
blotter.order( asset133 = self.asset_finder.retrieve_asset(133)
blotter.asset_finder.retrieve_asset(2), 100, LimitOrder(10))
# send in a split for sid 2 blotter.order(asset1, 100, LimitOrder(10))
blotter.process_splits([(2, 0.3333)]) blotter.order(asset2, 100, LimitOrder(10))
for sid in [1, 2]: # send in splits for assets 133 and 2. We have no open orders for
order_lists = blotter.open_orders[sid] # asset 133 so it should be ignored.
blotter.process_splits([(asset133, 0.5), (asset2, 0.3333)])
for asset in [asset1, asset2]:
order_lists = blotter.open_orders[asset]
self.assertIsNotNone(order_lists) self.assertIsNotNone(order_lists)
self.assertEqual(1, len(order_lists)) self.assertEqual(1, len(order_lists))
aapl_order = blotter.open_orders[1][0].to_dict() asset1_order = blotter.open_orders[1][0]
fls_order = blotter.open_orders[2][0].to_dict() asset2_order = blotter.open_orders[2][0]
# make sure the aapl order didn't change # make sure the asset1 order didn't change
self.assertEqual(100, aapl_order['amount']) self.assertEqual(100, asset1_order.amount)
self.assertEqual(10, aapl_order['limit']) self.assertEqual(10, asset1_order.limit)
self.assertEqual(1, aapl_order['sid']) self.assertEqual(1, asset1_order.asset)
# make sure the fls order did change # make sure the asset2 order did change
# to 300 shares at 3.33 # to 300 shares at 3.33
self.assertEqual(300, fls_order['amount']) self.assertEqual(300, asset2_order.amount)
self.assertEqual(3.33, fls_order['limit']) self.assertEqual(3.33, asset2_order.limit)
self.assertEqual(2, fls_order['sid']) self.assertEqual(2, asset2_order.asset)
class TradingEnvironmentTestCase(WithLogger, class TradingEnvironmentTestCase(WithLogger,
+142 -110
View File
@@ -276,6 +276,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
def init_class_fixtures(cls): def init_class_fixtures(cls):
super(TestSplitPerformance, cls).init_class_fixtures() super(TestSplitPerformance, cls).init_class_fixtures()
cls.asset1 = cls.env.asset_finder.retrieve_asset(1) cls.asset1 = cls.env.asset_finder.retrieve_asset(1)
cls.asset2 = cls.env.asset_finder.retrieve_asset(2)
def test_multiple_splits(self): def test_multiple_splits(self):
# if multiple positions all have splits at the same time, verify that # if multiple positions all have splits at the same time, verify that
@@ -284,17 +285,14 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
self.trading_calendar, self.trading_calendar,
self.env) self.env)
asset1 = self.asset_finder.retrieve_asset(1)
asset2 = self.asset_finder.retrieve_asset(2)
perf_tracker.position_tracker.positions[1] = \ perf_tracker.position_tracker.positions[1] = \
Position(asset1, amount=10, cost_basis=10, last_sale_price=11) Position(self.asset1, amount=10, cost_basis=10, last_sale_price=11)
perf_tracker.position_tracker.positions[2] = \ perf_tracker.position_tracker.positions[2] = \
Position(asset2, amount=10, cost_basis=10, last_sale_price=11) Position(self.asset2, amount=10, cost_basis=10, last_sale_price=11)
leftover_cash = perf_tracker.position_tracker.handle_splits( leftover_cash = perf_tracker.position_tracker.handle_splits(
[(1, 0.333), (2, 0.333)] [(self.asset1, 0.333), (self.asset2, 0.333)]
) )
# we used to have 10 shares that each cost us $10, total $100 # we used to have 10 shares that each cost us $10, total $100
@@ -329,7 +327,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
# set up a split with ratio 3 occurring at the start of the second # set up a split with ratio 3 occurring at the start of the second
# day. # day.
splits = { splits = {
events[1].dt: [(1, 3)] events[1].dt: [(self.asset1, 3)]
} }
results = calculate_results(self.sim_params, results = calculate_results(self.sim_params,
@@ -347,7 +345,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
# check the last position to make sure it's been updated # check the last position to make sure it's been updated
position = latest_positions[0] position = latest_positions[0]
self.assertEqual(1, position['sid']) self.assertEqual(self.asset1, position['sid'])
self.assertEqual(33, position['amount']) self.assertEqual(33, position['amount'])
self.assertEqual(60, position['cost_basis']) self.assertEqual(60, position['cost_basis'])
self.assertEqual(60, position['last_sale_price']) self.assertEqual(60, position['last_sale_price'])
@@ -1106,10 +1104,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars,
txn1 = create_txn(self.asset1, trades_1[0].dt, 10.0, 100) txn1 = create_txn(self.asset1, trades_1[0].dt, 10.0, 100)
txn2 = create_txn(self.asset2, trades_1[0].dt, 10.0, -100) txn2 = create_txn(self.asset2, trades_1[0].dt, 10.0, -100)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency)
pp.position_tracker = pt pp.position_tracker = pt
pt.execute_transaction(txn1) pt.execute_transaction(txn1)
pp.handle_execution(txn1) pp.handle_execution(txn1)
@@ -1199,10 +1195,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars,
self.sim_params, self.sim_params,
{1: trades}) {1: trades})
txn = create_txn(self.asset1, trades[1].dt, 10.0, 1000) txn = create_txn(self.asset1, trades[1].dt, 10.0, 1000)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency)
pp.position_tracker = pt pp.position_tracker = pt
pt.execute_transaction(txn) pt.execute_transaction(txn)
@@ -1291,9 +1285,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars,
self.sim_params, self.sim_params,
{1: trades}) {1: trades})
txn = create_txn(self.asset1, trades[1].dt, 10.0, 100) txn = create_txn(self.asset1, trades[1].dt, 10.0, 100)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0,
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency, self.sim_params.data_frequency,
period_open=self.sim_params.start_session, period_open=self.sim_params.start_session,
period_close=self.sim_params.end_session) period_close=self.sim_params.end_session)
@@ -1326,8 +1319,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendars,
"should be just one position") "should be just one position")
self.assertEqual( self.assertEqual(
pp.positions[1].sid, pp.positions[1].asset,
txn.sid, txn.asset,
"position should be in security with id 1") "position should be in security with id 1")
self.assertEqual( self.assertEqual(
@@ -1410,11 +1403,8 @@ single short-sale transaction"""
{1: trades}) {1: trades})
txn = create_txn(self.asset1, trades[1].dt, 10.0, -100) txn = create_txn(self.asset1, trades[1].dt, 10.0, -100)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency)
pp = perf.PerformancePeriod(
1000.0, self.env.asset_finder,
self.sim_params.data_frequency)
pp.position_tracker = pt pp.position_tracker = pt
pt.execute_transaction(txn) pt.execute_transaction(txn)
@@ -1437,8 +1427,8 @@ single short-sale transaction"""
"should be just one position") "should be just one position")
self.assertEqual( self.assertEqual(
pp.positions[1].sid, pp.positions[1].asset,
txn.sid, txn.asset,
"position should be in security from the transaction" "position should be in security from the transaction"
) )
@@ -1494,8 +1484,8 @@ single short-sale transaction"""
) )
self.assertEqual( self.assertEqual(
pp.positions[1].sid, pp.positions[1].asset,
txn.sid, txn.asset,
"position should be in security from the transaction" "position should be in security from the transaction"
) )
@@ -1530,10 +1520,10 @@ single short-sale transaction"""
) )
# now run a performance period encompassing the entire trade sample. # now run a performance period encompassing the entire trade sample.
ptTotal = perf.PositionTracker(self.env.asset_finder, ptTotal = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) ppTotal = perf.PerformancePeriod(
ppTotal = perf.PerformancePeriod(1000.0, self.env.asset_finder, 1000.0, self.sim_params.data_frequency
self.sim_params.data_frequency) )
ppTotal.position_tracker = pt ppTotal.position_tracker = pt
ptTotal.execute_transaction(txn) ptTotal.execute_transaction(txn)
@@ -1556,8 +1546,8 @@ cost of sole txn in test"
"should be just one position" "should be just one position"
) )
self.assertEqual( self.assertEqual(
ppTotal.positions[1].sid, ppTotal.positions[1].asset,
txn.sid, txn.asset,
"position should be in security from the transaction" "position should be in security from the transaction"
) )
@@ -1638,10 +1628,8 @@ trade after cover"""
short_txn = create_txn(self.asset1, trades[1].dt, 10.0, -100) short_txn = create_txn(self.asset1, trades[1].dt, 10.0, -100)
cover_txn = create_txn(self.asset1, trades[6].dt, 7.0, 100) cover_txn = create_txn(self.asset1, trades[6].dt, 7.0, 100)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency)
pp.position_tracker = pt pp.position_tracker = pt
pt.execute_transaction(short_txn) pt.execute_transaction(short_txn)
@@ -1725,11 +1713,9 @@ shares in position"
self.sim_params, self.sim_params,
{1: trades}) {1: trades})
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency)
pp = perf.PerformancePeriod( pp = perf.PerformancePeriod(
1000.0, 1000.0,
self.env.asset_finder,
self.sim_params.data_frequency, self.sim_params.data_frequency,
period_open=self.sim_params.start_session, period_open=self.sim_params.start_session,
period_close=self.sim_params.sessions[-1] period_close=self.sim_params.sessions[-1]
@@ -1792,10 +1778,8 @@ shares in position"
self.assertEqual(pp.pnl, -800, "this period goes from +400 to -400") self.assertEqual(pp.pnl, -800, "this period goes from +400 to -400")
pt3 = perf.PositionTracker(self.env.asset_finder, pt3 = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp3 = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency)
pp3 = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency)
pp3.position_tracker = pt3 pp3.position_tracker = pt3
average_cost = 0 average_cost = 0
@@ -1834,7 +1818,7 @@ shares in position"
self.create_environment_stuff(num_days=8) self.create_environment_stuff(num_days=8)
history_args = ( history_args = (
1, self.asset1,
[10, 9, 11, 8, 9, 12, 13, 14], [10, 9, 11, 8, 9, 12, 13, 14],
[200, -100, -100, 100, -300, 100, 500, 400], [200, -100, -100, 100, -300, 100, 500, 400],
oneday, oneday,
@@ -1845,10 +1829,8 @@ shares in position"
transactions = factory.create_txn_history(*history_args) transactions = factory.create_txn_history(*history_args)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0, self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency)
pp.position_tracker = pt pp.position_tracker = pt
for idx, (txn, cb) in enumerate(zip(transactions, cost_bases)): for idx, (txn, cb) in enumerate(zip(transactions, cost_bases)):
@@ -1885,9 +1867,8 @@ shares in position"
self.sim_params, self.sim_params,
{1: trades}) {1: trades})
txn = create_txn(self.asset1, trades[0].dt, 10.0, 100) txn = create_txn(self.asset1, trades[0].dt, 10.0, 100)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0,
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency, self.sim_params.data_frequency,
period_open=self.sim_params.start_session, period_open=self.sim_params.start_session,
period_close=self.sim_params.end_session) period_close=self.sim_params.end_session)
@@ -1929,9 +1910,8 @@ shares in position"
self.sim_params, self.sim_params,
{1: trades}) {1: trades})
txn = create_txn(self.asset1, trades[0].dt, 10.0, 100) txn = create_txn(self.asset1, trades[0].dt, 10.0, 100)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(self.sim_params.data_frequency)
self.sim_params.data_frequency) pp = perf.PerformancePeriod(1000.0,
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency, self.sim_params.data_frequency,
period_open=self.sim_params.start_session, period_open=self.sim_params.start_session,
period_close=self.sim_params.end_session) period_close=self.sim_params.end_session)
@@ -1973,6 +1953,16 @@ class TestPositionTracker(WithTradingEnvironment,
ZiplineTestCase): ZiplineTestCase):
ASSET_FINDER_EQUITY_SIDS = 1, 2 ASSET_FINDER_EQUITY_SIDS = 1, 2
@classmethod
def init_class_fixtures(cls):
super(TestPositionTracker, cls).init_class_fixtures()
cls.EQUITY1 = cls.asset_finder.retrieve_asset(1)
cls.EQUITY2 = cls.asset_finder.retrieve_asset(2)
cls.FUTURE3 = cls.asset_finder.retrieve_asset(3)
cls.FUTURE4 = cls.asset_finder.retrieve_asset(4)
cls.FUTURE5 = cls.asset_finder.retrieve_asset(1032201401)
@classmethod @classmethod
def make_futures_info(cls): def make_futures_info(cls):
return pd.DataFrame.from_dict( return pd.DataFrame.from_dict(
@@ -1993,8 +1983,7 @@ class TestPositionTracker(WithTradingEnvironment,
""" """
sim_params = factory.create_simulation_parameters(num_days=4) sim_params = factory.create_simulation_parameters(num_days=4)
pt = perf.PositionTracker(self.env.asset_finder, pt = perf.PositionTracker(sim_params.data_frequency)
sim_params.data_frequency)
pos_stats = pt.stats() pos_stats = pt.stats()
stats = [ stats = [
@@ -2015,17 +2004,27 @@ class TestPositionTracker(WithTradingEnvironment,
self.assertNotIsInstance(val, (bool, np.bool_)) self.assertNotIsInstance(val, (bool, np.bool_))
def test_position_values_and_exposures(self): def test_position_values_and_exposures(self):
pt = perf.PositionTracker(self.env.asset_finder, None) pt = perf.PositionTracker(None)
dt = pd.Timestamp("1984/03/06 3:00PM") dt = pd.Timestamp("1984/03/06 3:00PM")
pos1 = perf.Position(1, amount=np.float64(10.0), pt.update_position(
last_sale_date=dt, last_sale_price=10) self.EQUITY1, amount=np.float64(10.0),
pos2 = perf.Position(2, amount=np.float64(-20.0), last_sale_date=dt, last_sale_price=10
last_sale_date=dt, last_sale_price=10) )
pos3 = perf.Position(3, amount=np.float64(30.0),
last_sale_date=dt, last_sale_price=10) pt.update_position(
pos4 = perf.Position(4, amount=np.float64(-40.0), self.EQUITY2, amount=np.float64(-20.0),
last_sale_date=dt, last_sale_price=10) last_sale_date=dt, last_sale_price=10
pt.update_positions({1: pos1, 2: pos2, 3: pos3, 4: pos4}) )
pt.update_position(
self.FUTURE3, amount=np.float64(30.0),
last_sale_date=dt, last_sale_price=10
)
pt.update_position(
self.FUTURE4, amount=np.float64(-40.0),
last_sale_date=dt, last_sale_price=10
)
# Test long-only methods # Test long-only methods
pos_stats = pt.stats() pos_stats = pt.stats()
@@ -2046,25 +2045,71 @@ class TestPositionTracker(WithTradingEnvironment,
self.assertEqual(100 + 200 + 300000 + 400000, pos_stats.gross_exposure) self.assertEqual(100 + 200 + 300000 + 400000, pos_stats.gross_exposure)
self.assertEqual(100 - 200 + 300000 - 400000, pos_stats.net_exposure) self.assertEqual(100 - 200 + 300000 - 400000, pos_stats.net_exposure)
def test_update_positions(self): def test_cost_basis(self):
pt = perf.PositionTracker(self.env.asset_finder, None) dt = pd.Timestamp("2015-12-10 15:00", tz='UTC')
dt = pd.Timestamp("2014/01/01 3:00PM")
pos1 = perf.Position(1, amount=np.float64(10.0),
last_sale_date=dt, last_sale_price=10)
pos2 = perf.Position(2, amount=np.float64(-20.0),
last_sale_date=dt, last_sale_price=10)
pos3 = perf.Position(1032201401, amount=np.float64(30.0),
last_sale_date=dt, last_sale_price=100)
# Call update_positions twice. When the second call is made, equity_pos = perf.Position(
# self.positions will already contain data. The order of this data self.EQUITY1,
# needs to be preserved so that it is consistent with the order of the amount=10,
# data stored in the multipliers OrderedDict()'s. If self.positions last_sale_date=dt,
# were to be stored as a dict, then its order could change in arbitrary cost_basis=10,
# ways when the second update_positions call is made. Hence we also last_sale_price=11,
# store it as an OrderedDict. )
pt.update_positions({1: pos1, 1032201401: pos3})
pt.update_positions({2: pos2}) future_pos = perf.Position(
self.FUTURE3,
amount=10,
last_sale_date=dt,
cost_basis=10,
last_sale_price=11,
)
self.assertEqual(10, equity_pos.cost_basis)
# send a $5 commission to the equity position. Spread out over 10
# shares, that bumps the cost basis by $0.50.
equity_pos.adjust_commission_cost_basis(self.EQUITY1, 5)
self.assertEqual(10.5, equity_pos.cost_basis)
self.assertEqual(10, future_pos.cost_basis)
# send a $5k commission to the futures position. since self.FUTURE3
# has a contract size (multipler) of 1000, this should result in a
# $10.5 updated cost basis. (5000 / 1000 = $5, spread out over 10
# contracts, is $0.50 extra per contract).
future_pos.adjust_commission_cost_basis(self.FUTURE3, 5000)
self.assertEqual(10.5, future_pos.cost_basis)
def test_update_positions(self):
pt = perf.PositionTracker(None)
dt = pd.Timestamp("2014/01/01 3:00PM")
# pos1 = perf.Position(self.EQUITY1, amount=np.float64(10.0),
# last_sale_date=dt, last_sale_price=10)
# pos2 = perf.Position(self.EQUITY2, amount=np.float64(-20.0),
# last_sale_date=dt, last_sale_price=10)
# pos3 = perf.Position(self.FUTURE5, amount=np.float64(30.0),
# last_sale_date=dt, last_sale_price=100)
pt.update_position(
self.EQUITY1,
amount=np.float64(10.0),
last_sale_price=10,
last_sale_date=dt
)
pt.update_position(
self.EQUITY2,
amount=np.float64(-20.0),
last_sale_price=10,
last_sale_date=dt
)
pt.update_position(
self.FUTURE5,
amount=np.float64(30.0),
last_sale_price=100,
last_sale_date=dt
)
pos_stats = pt.stats() pos_stats = pt.stats()
# Test long-only methods # Test long-only methods
@@ -2087,33 +2132,20 @@ class TestPositionTracker(WithTradingEnvironment,
self.assertEqual(100 + 150000 - 200, pos_stats.net_exposure) self.assertEqual(100 + 150000 - 200, pos_stats.net_exposure)
def test_close_position(self): def test_close_position(self):
future_sid = 1032201401 pt = perf.PositionTracker(None)
equity_sid = 1
pt = perf.PositionTracker(self.env.asset_finder, None)
dt = pd.Timestamp('2017/01/04 3:00PM') dt = pd.Timestamp('2017/01/04 3:00PM')
pos1 = perf.Position( pt.update_position(
sid=future_sid, asset=self.FUTURE5, amount=np.float64(30.0),
amount=np.float64(30.0), last_sale_date=dt, last_sale_price=100
last_sale_date=dt,
last_sale_price=100,
)
pos2 = perf.Position(
sid=equity_sid,
amount=np.float64(10.0),
last_sale_date=dt,
last_sale_price=10,
) )
# Update the positions dictionary with `future_sid` first. The order pt.update_position(
# matters because it affects the multipliers dictionaries, which are asset=self.EQUITY1, amount=np.float64(10.0),
# OrderedDicts. If `future_sid` is not removed from the multipliers last_sale_date=dt, last_sale_price=10
# dictionaries, equities will hit the incorrect multiplier when )
# computing `pt.stats()`.
pt.update_positions({future_sid: pos1, equity_sid: pos2})
asset_to_close = self.env.asset_finder.retrieve_asset(future_sid) txn = create_txn(self.FUTURE5, dt, 100, -30)
txn = create_txn(asset_to_close, dt, 100, -30)
pt.execute_transaction(txn) pt.execute_transaction(txn)
pos_stats = pt.stats() pos_stats = pt.stats()
-1
View File
@@ -322,7 +322,6 @@ class TradingAlgorithm(object):
if not self.blotter: if not self.blotter:
self.blotter = Blotter( self.blotter = Blotter(
data_frequency=self.data_frequency, data_frequency=self.data_frequency,
asset_finder=self.asset_finder,
# Default to NeverCancel in zipline # Default to NeverCancel in zipline
cancel_policy=self.cancel_policy, cancel_policy=self.cancel_policy,
) )
+10 -8
View File
@@ -1121,25 +1121,25 @@ class DataPortal(object):
self._asset_end_dates[sid] = asset.end_date self._asset_end_dates[sid] = asset.end_date
def get_splits(self, sids, dt): def get_splits(self, assets, dt):
""" """
Returns any splits for the given sids and the given dt. Returns any splits for the given sids and the given dt.
Parameters Parameters
---------- ----------
sids : container assets : container
Sids for which we want splits. Assets for which we want splits.
dt : pd.Timestamp dt : pd.Timestamp
The date for which we are checking for splits. Note: this is The date for which we are checking for splits. Note: this is
expected to be midnight UTC. expected to be midnight UTC.
Returns Returns
------- -------
splits : list[(int, float)] splits : list[(asset, float)]
List of splits, where each split is a (sid, ratio) tuple. List of splits, where each split is a (asset, ratio) tuple.
""" """
if self._adjustment_reader is None or not sids: if self._adjustment_reader is None or not assets:
return {} return []
# convert dt to # of seconds since epoch, because that's what we use # convert dt to # of seconds since epoch, because that's what we use
# in the adjustments db # in the adjustments db
@@ -1149,7 +1149,9 @@ class DataPortal(object):
"SELECT sid, ratio FROM SPLITS WHERE effective_date = ?", "SELECT sid, ratio FROM SPLITS WHERE effective_date = ?",
(seconds,)).fetchall() (seconds,)).fetchall()
splits = [split for split in splits if split[0] in sids] splits = [split for split in splits if split[0] in assets]
splits = [(self.asset_finder.retrieve_asset(split[0]), split[1])
for split in splits]
return splits return splits
-12
View File
@@ -665,18 +665,6 @@ class UnsupportedDatetimeFormat(ZiplineError):
"coercible to a pandas.Timestamp object.") "coercible to a pandas.Timestamp object.")
class PositionTrackerMissingAssetFinder(ZiplineError):
"""
Raised by a PositionTracker if it is asked to update an Asset but does not
have an AssetFinder
"""
msg = (
"PositionTracker attempted to update its Asset information but does "
"not have an AssetFinder. This may be caused by a failure to properly "
"de-serialize a TradingAlgorithm."
)
class AssetDBVersionError(ZiplineError): class AssetDBVersionError(ZiplineError):
""" """
Raised by an AssetDBWriter or AssetFinder if the version number in the Raised by an AssetDBWriter or AssetFinder if the version number in the
+33 -41
View File
@@ -18,7 +18,7 @@ from copy import copy
from six import iteritems from six import iteritems
from zipline.assets import Equity, Future from zipline.assets import Equity, Future, Asset
from zipline.finance.order import Order from zipline.finance.order import Order
from zipline.finance.slippage import VolumeShareSlippage from zipline.finance.slippage import VolumeShareSlippage
from zipline.finance.commission import ( from zipline.finance.commission import (
@@ -27,26 +27,22 @@ from zipline.finance.commission import (
PerTrade, PerTrade,
) )
from zipline.finance.cancel_policy import NeverCancel from zipline.finance.cancel_policy import NeverCancel
from zipline.utils.input_validation import expect_types
log = Logger('Blotter') log = Logger('Blotter')
warning_logger = Logger('AlgoWarning') warning_logger = Logger('AlgoWarning')
class Blotter(object): class Blotter(object):
def __init__(self, data_frequency, asset_finder, equity_slippage=None, def __init__(self, data_frequency, equity_slippage=None,
future_slippage=None, equity_commission=None, future_slippage=None, equity_commission=None,
future_commission=None, cancel_policy=None): future_commission=None, cancel_policy=None):
# these orders are aggregated by sid # these orders are aggregated by asset
self.open_orders = defaultdict(list) self.open_orders = defaultdict(list)
# keep a dict of orders by their own id # keep a dict of orders by their own id
self.orders = {} self.orders = {}
# all our legacy order management code works with integer sids.
# this lets us convert those to assets when needed. ideally, we'd just
# revamp all the legacy code to work with assets.
self.asset_finder = asset_finder
# holding orders that have come in since the last event. # holding orders that have come in since the last event.
self.new_orders = [] self.new_orders = []
self.current_dt = None self.current_dt = None
@@ -88,7 +84,8 @@ class Blotter(object):
def set_date(self, dt): def set_date(self, dt):
self.current_dt = dt self.current_dt = dt
def order(self, sid, amount, style, order_id=None): @expect_types(asset=Asset)
def order(self, asset, amount, style, order_id=None):
"""Place an order. """Place an order.
Parameters Parameters
@@ -114,10 +111,10 @@ class Blotter(object):
----- -----
amount > 0 :: Buy/Cover amount > 0 :: Buy/Cover
amount < 0 :: Sell/Short amount < 0 :: Sell/Short
Market order: order(sid, amount) Market order: order(asset, amount)
Limit order: order(sid, amount, style=LimitOrder(limit_price)) Limit order: order(asset, amount, style=LimitOrder(limit_price))
Stop order: order(sid, amount, style=StopOrder(stop_price)) Stop order: order(asset, amount, style=StopOrder(stop_price))
StopLimit order: order(sid, amount, style=StopLimitOrder(limit_price, StopLimit order: order(asset, amount, style=StopLimitOrder(limit_price,
stop_price)) stop_price))
""" """
# something could be done with amount to further divide # something could be done with amount to further divide
@@ -136,14 +133,14 @@ class Blotter(object):
is_buy = (amount > 0) is_buy = (amount > 0)
order = Order( order = Order(
dt=self.current_dt, dt=self.current_dt,
sid=sid, asset=asset,
amount=amount, amount=amount,
stop=style.get_stop_price(is_buy), stop=style.get_stop_price(is_buy),
limit=style.get_limit_price(is_buy), limit=style.get_limit_price(is_buy),
id=order_id id=order_id
) )
self.open_orders[order.sid].append(order) self.open_orders[order.asset].append(order)
self.orders[order.id] = order self.orders[order.id] = order
self.new_orders.append(order) self.new_orders.append(order)
@@ -177,7 +174,7 @@ class Blotter(object):
cur_order = self.orders[order_id] cur_order = self.orders[order_id]
if cur_order.open: if cur_order.open:
order_list = self.open_orders[cur_order.sid] order_list = self.open_orders[cur_order.asset]
if cur_order in order_list: if cur_order in order_list:
order_list.remove(cur_order) order_list.remove(cur_order)
@@ -217,7 +214,7 @@ class Blotter(object):
'filled by the end of day and ' 'filled by the end of day and '
'were canceled.'.format( 'were canceled.'.format(
order_amt=order.amount, order_amt=order.amount,
order_sym=order.sid.symbol, order_sym=order.asset.symbol,
order_filled=order.filled, order_filled=order.filled,
order_failed=order.amount - order.filled, order_failed=order.amount - order.filled,
) )
@@ -231,7 +228,7 @@ class Blotter(object):
'filled by the end of day and ' 'filled by the end of day and '
'were canceled.'.format( 'were canceled.'.format(
order_amt=order.amount, order_amt=order.amount,
order_sym=order.sid.symbol, order_sym=order.asset.symbol,
order_filled=-1 * order.filled, order_filled=-1 * order.filled,
order_failed=-1 * (order.amount - order.filled), order_failed=-1 * (order.amount - order.filled),
) )
@@ -242,7 +239,7 @@ class Blotter(object):
'{order_sym} failed to fill by the end of day ' '{order_sym} failed to fill by the end of day '
'and was canceled.'.format( 'and was canceled.'.format(
order_amt=order.amount, order_amt=order.amount,
order_sym=order.sid.symbol, order_sym=order.asset.symbol,
) )
) )
@@ -268,7 +265,7 @@ class Blotter(object):
cur_order = self.orders[order_id] cur_order = self.orders[order_id]
order_list = self.open_orders[cur_order.sid] order_list = self.open_orders[cur_order.asset]
if cur_order in order_list: if cur_order in order_list:
order_list.remove(cur_order) order_list.remove(cur_order)
@@ -306,20 +303,19 @@ class Blotter(object):
Parameters Parameters
---------- ----------
splits: list splits: list
A list of splits. Each split is a tuple of (sid, ratio). A list of splits. Each split is a tuple of (asset, ratio).
Returns Returns
------- -------
None None
""" """
for split in splits: for asset, ratio in splits:
sid = split[0] if asset not in self.open_orders:
if sid not in self.open_orders: continue
return
orders_to_modify = self.open_orders[sid] orders_to_modify = self.open_orders[asset]
for order in orders_to_modify: for order in orders_to_modify:
order.handle_split(split[1]) order.handle_split(ratio)
def get_transactions(self, bar_data): def get_transactions(self, bar_data):
""" """
@@ -344,7 +340,7 @@ class Blotter(object):
commissions_list: List commissions_list: List
commissions_list: list of commissions resulting from filling the commissions_list: list of commissions resulting from filling the
open orders. A commission is an object with "sid" and "cost" open orders. A commission is an object with "asset" and "cost"
parameters. parameters.
closed_orders: List closed_orders: List
@@ -356,11 +352,7 @@ class Blotter(object):
commissions = [] commissions = []
if self.open_orders: if self.open_orders:
assets = self.asset_finder.retrieve_all(self.open_orders) for asset, asset_orders in iteritems(self.open_orders):
asset_dict = {asset.sid: asset for asset in assets}
for sid, asset_orders in iteritems(self.open_orders):
asset = asset_dict[sid]
slippage = self.slippage_models[type(asset)] slippage = self.slippage_models[type(asset)]
for order, txn in \ for order, txn in \
@@ -370,7 +362,7 @@ class Blotter(object):
if additional_commission > 0: if additional_commission > 0:
commissions.append({ commissions.append({
"sid": order.sid, "asset": order.asset,
"order": order, "order": order,
"cost": additional_commission "cost": additional_commission
}) })
@@ -401,15 +393,15 @@ class Blotter(object):
""" """
# remove all closed orders from our open_orders dict # remove all closed orders from our open_orders dict
for order in closed_orders: for order in closed_orders:
sid = order.sid asset = order.asset
sid_orders = self.open_orders[sid] asset_orders = self.open_orders[asset]
try: try:
sid_orders.remove(order) asset_orders.remove(order)
except ValueError: except ValueError:
continue continue
# now clear out the sids from our open_orders dict that have # now clear out the assets from our open_orders dict that have
# zero open orders # zero open orders
for sid in list(self.open_orders.keys()): for asset in list(self.open_orders.keys()):
if len(self.open_orders[sid]) == 0: if len(self.open_orders[asset]) == 0:
del self.open_orders[sid] del self.open_orders[asset]
+16 -14
View File
@@ -20,6 +20,7 @@ from six import text_type
import zipline.protocol as zp import zipline.protocol as zp
from zipline.assets import Asset from zipline.assets import Asset
from zipline.utils.enum import enum from zipline.utils.enum import enum
from zipline.utils.input_validation import expect_types
ORDER_STATUS = enum( ORDER_STATUS = enum(
'OPEN', 'OPEN',
@@ -34,35 +35,35 @@ BUY = 1 << 1
STOP = 1 << 2 STOP = 1 << 2
LIMIT = 1 << 3 LIMIT = 1 << 3
ORDER_FIELDS_TO_IGNORE = {'type', 'direction', '_status'} ORDER_FIELDS_TO_IGNORE = {'type', 'direction', '_status', 'asset'}
class Order(object): class Order(object):
# using __slots__ to save on memory usage. Simulations can create many # using __slots__ to save on memory usage. Simulations can create many
# Order objects and we keep them all in memory, so it's worthwhile trying # Order objects and we keep them all in memory, so it's worthwhile trying
# to cut down on the memory footprint of this object. # to cut down on the memory footprint of this object.
__slots__ = ["id", "dt", "reason", "created", "sid", "amount", "filled", __slots__ = ["id", "dt", "reason", "created", "asset", "amount", "filled",
"commission", "_status", "stop", "limit", "stop_reached", "commission", "_status", "stop", "limit", "stop_reached",
"limit_reached", "direction", "type", "broker_order_id"] "limit_reached", "direction", "type", "broker_order_id"]
def __init__(self, dt, sid, amount, stop=None, limit=None, filled=0, @expect_types(asset=Asset)
def __init__(self, dt, asset, amount, stop=None, limit=None, filled=0,
commission=0, id=None): commission=0, id=None):
""" """
@dt - datetime.datetime that the order was placed @dt - datetime.datetime that the order was placed
@sid - asset for the order. called sid for historical reasons. @asset - asset for the order.
@amount - the number of shares to buy/sell @amount - the number of shares to buy/sell
a positive sign indicates a buy a positive sign indicates a buy
a negative sign indicates a sell a negative sign indicates a sell
@filled - how many shares of the order have been filled so far @filled - how many shares of the order have been filled so far
""" """
assert isinstance(sid, Asset)
# get a string representation of the uuid. # get a string representation of the uuid.
self.id = self.make_id() if id is None else id self.id = self.make_id() if id is None else id
self.dt = dt self.dt = dt
self.reason = None self.reason = None
self.created = dt self.created = dt
self.sid = sid self.asset = asset
self.amount = amount self.amount = amount
self.filled = filled self.filled = filled
self.commission = commission self.commission = commission
@@ -86,9 +87,18 @@ class Order(object):
if self.broker_order_id is None: if self.broker_order_id is None:
del dct['broker_order_id'] del dct['broker_order_id']
# Adding 'sid' for backwards compatibility with downstream consumers.
dct['sid'] = self.asset
dct['status'] = self.status dct['status'] = self.status
return dct return dct
@property
def sid(self):
# For backwards compatibility because we pass this object to
# custom slippage models.
return self.asset
def to_api_obj(self): def to_api_obj(self):
pydict = self.to_dict() pydict = self.to_dict()
obj = zp.Order(initial_values=pydict) obj = zp.Order(initial_values=pydict)
@@ -215,14 +225,6 @@ class Order(object):
def open(self): def open(self):
return self.status in [ORDER_STATUS.OPEN, ORDER_STATUS.HELD] return self.status in [ORDER_STATUS.OPEN, ORDER_STATUS.HELD]
@property
def asset(self):
"""
Convenience accessor to hide away a historical API that we'd like to
change at some point.
"""
return self.sid
@property @property
def triggered(self): def triggered(self):
""" """
+6 -22
View File
@@ -139,7 +139,6 @@ class PerformancePeriod(object):
def __init__( def __init__(
self, self,
starting_cash, starting_cash,
asset_finder,
data_frequency, data_frequency,
period_open=None, period_open=None,
period_close=None, period_close=None,
@@ -148,7 +147,6 @@ class PerformancePeriod(object):
serialize_positions=True, serialize_positions=True,
name=None): name=None):
self.asset_finder = asset_finder
self.data_frequency = data_frequency self.data_frequency = data_frequency
# Start and end of the entire period # Start and end of the entire period
@@ -185,10 +183,6 @@ class PerformancePeriod(object):
self._account_store = zp.Account() self._account_store = zp.Account()
self.serialize_positions = serialize_positions self.serialize_positions = serialize_positions
# This dict contains the known cash flow multipliers for sids and is
# keyed on sid
self._execution_cash_flow_multipliers = {}
_position_tracker = None _position_tracker = None
def initialize(self, starting_cash, starting_value, starting_exposure): def initialize(self, starting_cash, starting_value, starting_exposure):
@@ -362,7 +356,7 @@ class PerformancePeriod(object):
def handle_execution(self, txn): def handle_execution(self, txn):
self.cash_flow += self._calculate_execution_cash_flow(txn) self.cash_flow += self._calculate_execution_cash_flow(txn)
asset = self.asset_finder.retrieve_asset(txn.sid) asset = txn.asset
if isinstance(asset, Future): if isinstance(asset, Future):
try: try:
old_price = self._payout_last_sale_prices[asset] old_price = self._payout_last_sale_prices[asset]
@@ -385,25 +379,15 @@ class PerformancePeriod(object):
except KeyError: except KeyError:
self.processed_transactions[txn.dt] = [txn] self.processed_transactions[txn.dt] = [txn]
def _calculate_execution_cash_flow(self, txn): @staticmethod
def _calculate_execution_cash_flow(txn):
""" """
Calculates the cash flow from executing the given transaction Calculates the cash flow from executing the given transaction
""" """
# Check if the multiplier is cached. If it is not, look up the asset if txn.asset is Future:
# and cache the multiplier. return 0.0
try:
multiplier = self._execution_cash_flow_multipliers[txn.sid]
except KeyError:
asset = self.asset_finder.retrieve_asset(txn.sid)
# Futures experience no cash flow on transactions
if isinstance(asset, Future):
multiplier = 0
else:
multiplier = 1
self._execution_cash_flow_multipliers[txn.sid] = multiplier
# Calculate and return the cash flow given the multiplier return -1 * txn.price * txn.amount
return -1 * txn.price * txn.amount * multiplier
# backwards compat. TODO: remove? # backwards compat. TODO: remove?
@property @property
+25 -15
View File
@@ -20,7 +20,7 @@ Position Tracking
+-----------------+----------------------------------------------------+ +-----------------+----------------------------------------------------+
| key | value | | key | value |
+=================+====================================================+ +=================+====================================================+
| sid | the sid for the asset held in this position | | asset | the asset held in this position |
+-----------------+----------------------------------------------------+ +-----------------+----------------------------------------------------+
| amount | whole number of shares in the position | | amount | whole number of shares in the position |
+-----------------+----------------------------------------------------+ +-----------------+----------------------------------------------------+
@@ -37,15 +37,19 @@ from collections import OrderedDict
import numpy as np import numpy as np
import logbook import logbook
from zipline.assets import Future, Asset
from zipline.utils.input_validation import expect_types
log = logbook.Logger('Performance') log = logbook.Logger('Performance')
class Position(object): class Position(object):
def __init__(self, sid, amount=0, cost_basis=0.0, @expect_types(asset=Asset)
def __init__(self, asset, amount=0, cost_basis=0.0,
last_sale_price=0.0, last_sale_date=None): last_sale_price=0.0, last_sale_date=None):
self.sid = sid self.asset = asset
self.amount = amount self.amount = amount
self.cost_basis = cost_basis # per share self.cost_basis = cost_basis # per share
self.last_sale_price = last_sale_price self.last_sale_price = last_sale_price
@@ -72,15 +76,16 @@ class Position(object):
) )
} }
def handle_split(self, sid, ratio): @expect_types(asset=Asset)
def handle_split(self, asset, ratio):
""" """
Update the position by the split ratio, and return the resulting Update the position by the split ratio, and return the resulting
fractional share that will be converted into cash. fractional share that will be converted into cash.
Returns the unused cash. Returns the unused cash.
""" """
if self.sid != sid: if self.asset != asset:
raise Exception("updating split with the wrong sid!") raise Exception("updating split with the wrong asset!")
# adjust the # of shares by the ratio # adjust the # of shares by the ratio
# (if we had 100 shares, and the ratio is 3, # (if we had 100 shares, and the ratio is 3,
@@ -113,9 +118,9 @@ class Position(object):
return return_cash return return_cash
def update(self, txn): def update(self, txn):
if self.sid != txn.sid: if self.asset != txn.asset:
raise Exception('updating position with txn for a ' raise Exception('updating position with txn for a '
'different sid') 'different asset')
total_shares = self.amount + txn.amount total_shares = self.amount + txn.amount
@@ -145,7 +150,8 @@ class Position(object):
self.amount = total_shares self.amount = total_shares
def adjust_commission_cost_basis(self, sid, cost): @expect_types(asset=Asset)
def adjust_commission_cost_basis(self, asset, cost):
""" """
A note about cost-basis in zipline: all positions are considered A note about cost-basis in zipline: all positions are considered
to share a cost basis, even if they were executed in different to share a cost basis, even if they were executed in different
@@ -156,8 +162,8 @@ class Position(object):
all shares in a position. all shares in a position.
""" """
if sid != self.sid: if asset != self.asset:
raise Exception('Updating a commission for a different sid?') raise Exception('Updating a commission for a different asset?')
if cost == 0.0: if cost == 0.0:
return return
@@ -167,14 +173,18 @@ class Position(object):
return return
prev_cost = self.cost_basis * self.amount prev_cost = self.cost_basis * self.amount
new_cost = prev_cost + cost if isinstance(asset, Future):
cost_to_use = cost / asset.multiplier
else:
cost_to_use = cost
new_cost = prev_cost + cost_to_use
self.cost_basis = new_cost / self.amount self.cost_basis = new_cost / self.amount
def __repr__(self): def __repr__(self):
template = "sid: {sid}, amount: {amount}, cost_basis: {cost_basis}, \ template = "asset: {asset}, amount: {amount}, cost_basis: {cost_basis}, \
last_sale_price: {last_sale_price}" last_sale_price: {last_sale_price}"
return template.format( return template.format(
sid=self.sid, asset=self.asset,
amount=self.amount, amount=self.amount,
cost_basis=self.cost_basis, cost_basis=self.cost_basis,
last_sale_price=self.last_sale_price last_sale_price=self.last_sale_price
@@ -186,7 +196,7 @@ last_sale_price: {last_sale_price}"
Returns a dict object of the form: Returns a dict object of the form:
""" """
return { return {
'sid': self.sid, 'sid': self.asset,
'amount': self.amount, 'amount': self.amount,
'cost_basis': self.cost_basis, 'cost_basis': self.cost_basis,
'last_sale_price': self.last_sale_price 'last_sale_price': self.last_sale_price
+61 -110
View File
@@ -19,21 +19,17 @@ import logbook
import numpy as np import numpy as np
from collections import namedtuple from collections import namedtuple
from math import isnan from math import isnan
from zipline.finance.performance.position import Position
from zipline.finance.transaction import Transaction
try:
# optional cython based OrderedDict
from cyordereddict import OrderedDict
except ImportError:
from collections import OrderedDict
from six import iteritems, itervalues from six import iteritems, itervalues
from zipline.finance.performance.position import Position
from zipline.finance.transaction import Transaction
from zipline.utils.input_validation import expect_types
import zipline.protocol as zp import zipline.protocol as zp
from zipline.assets import ( from zipline.assets import (
Equity, Future Future,
Asset
) )
from zipline.errors import PositionTrackerMissingAssetFinder
from . position import positiondict from . position import positiondict
log = logbook.Logger('Performance') log = logbook.Logger('Performance')
@@ -52,18 +48,17 @@ PositionStats = namedtuple('PositionStats',
'net_value']) 'net_value'])
def calc_position_values(amounts, def calc_position_values(positions):
last_sale_prices, values = []
value_multipliers):
iter_amount_price_multiplier = zip( for position in positions:
amounts, if isinstance(position.asset, Future):
last_sale_prices, # Futures don't have an inherent position value.
itervalues(value_multipliers), values.append(0.0)
) else:
return [ values.append(position.last_sale_price * position.amount)
price * amount * multiplier for
price, amount, multiplier in iter_amount_price_multiplier return values
]
def calc_net(values): def calc_net(values):
@@ -71,18 +66,18 @@ def calc_net(values):
return sum(values, np.float64()) return sum(values, np.float64())
def calc_position_exposures(amounts, def calc_position_exposures(positions):
last_sale_prices, exposures = []
exposure_multipliers):
iter_amount_price_multiplier = zip( for position in positions:
amounts, exposure = position.amount * position.last_sale_price
last_sale_prices,
itervalues(exposure_multipliers), if isinstance(position.asset, Future):
) exposure *= position.asset.multiplier
return [
price * amount * multiplier for exposures.append(exposure)
price, amount, multiplier in iter_amount_price_multiplier
] return exposures
def calc_long_value(position_values): def calc_long_value(position_values):
@@ -119,55 +114,26 @@ def calc_gross_value(long_value, short_value):
class PositionTracker(object): class PositionTracker(object):
def __init__(self, asset_finder, data_frequency): def __init__(self, data_frequency):
self.asset_finder = asset_finder # asset => position object
# sid => position object
self.positions = positiondict() self.positions = positiondict()
# Arrays for quick calculations of positions value
self._position_value_multipliers = OrderedDict()
self._position_exposure_multipliers = OrderedDict()
self._unpaid_dividends = {} self._unpaid_dividends = {}
self._unpaid_stock_dividends = {} self._unpaid_stock_dividends = {}
self._positions_store = zp.Positions() self._positions_store = zp.Positions()
self.data_frequency = data_frequency self.data_frequency = data_frequency
def _update_asset(self, sid): @expect_types(asset=Asset)
try: def update_position(self, asset, amount=None, last_sale_price=None,
self._position_value_multipliers[sid]
self._position_exposure_multipliers[sid]
except KeyError:
# Check if there is an AssetFinder
if self.asset_finder is None:
raise PositionTrackerMissingAssetFinder()
# Collect the value multipliers from applicable sids
asset = self.asset_finder.retrieve_asset(sid)
if isinstance(asset, Equity):
self._position_value_multipliers[sid] = 1
self._position_exposure_multipliers[sid] = 1
if isinstance(asset, Future):
self._position_value_multipliers[sid] = 0
self._position_exposure_multipliers[sid] = asset.multiplier
def update_positions(self, positions):
# update positions in batch
self.positions.update(positions)
for sid, pos in iteritems(positions):
self._update_asset(sid)
def update_position(self, sid, amount=None, last_sale_price=None,
last_sale_date=None, cost_basis=None): last_sale_date=None, cost_basis=None):
if sid not in self.positions: if asset not in self.positions:
position = Position(sid) position = Position(asset)
self.positions[sid] = position self.positions[asset] = position
else: else:
position = self.positions[sid] position = self.positions[asset]
if amount is not None: if amount is not None:
position.amount = amount position.amount = amount
self._update_asset(sid=sid)
if last_sale_price is not None: if last_sale_price is not None:
position.last_sale_price = last_sale_price position.last_sale_price = last_sale_price
if last_sale_date is not None: if last_sale_date is not None:
@@ -178,36 +144,31 @@ class PositionTracker(object):
def execute_transaction(self, txn): def execute_transaction(self, txn):
# Update Position # Update Position
# ---------------- # ----------------
sid = txn.sid asset = txn.asset
if sid not in self.positions: if asset not in self.positions:
position = Position(sid) position = Position(asset)
self.positions[sid] = position self.positions[asset] = position
else: else:
position = self.positions[sid] position = self.positions[asset]
position.update(txn) position.update(txn)
if position.amount == 0: if position.amount == 0:
# if this position now has 0 shares, remove it from our internal del self.positions[asset]
# bookkeeping.
del self.positions[sid]
del self._position_value_multipliers[sid]
del self._position_exposure_multipliers[sid]
try: try:
# if this position exists in our user-facing dictionary, # if this position exists in our user-facing dictionary,
# remove it as well. # remove it as well.
del self._positions_store[sid] del self._positions_store[asset]
except KeyError: except KeyError:
pass pass
else:
self._update_asset(sid)
def handle_commission(self, sid, cost): @expect_types(asset=Asset)
def handle_commission(self, asset, cost):
# Adjust the cost basis of the stock if we own it # Adjust the cost basis of the stock if we own it
if sid in self.positions: if asset in self.positions:
self.positions[sid].adjust_commission_cost_basis(sid, cost) self.positions[asset].adjust_commission_cost_basis(asset, cost)
def handle_splits(self, splits): def handle_splits(self, splits):
""" """
@@ -216,7 +177,7 @@ class PositionTracker(object):
Parameters Parameters
---------- ----------
splits: list splits: list
A list of splits. Each split is a tuple of (sid, ratio). A list of splits. Each split is a tuple of (asset, ratio).
Returns Returns
------- -------
@@ -225,14 +186,12 @@ class PositionTracker(object):
""" """
total_leftover_cash = 0 total_leftover_cash = 0
for split in splits: for asset, ratio in splits:
sid = split[0] if asset in self.positions:
if sid in self.positions:
# Make the position object handle the split. It returns the # Make the position object handle the split. It returns the
# leftover cash from a fractional share, if there is any. # leftover cash from a fractional share, if there is any.
position = self.positions[sid] position = self.positions[asset]
leftover_cash = position.handle_split(sid, split[1]) leftover_cash = position.handle_split(asset, ratio)
self._update_asset(split[0])
total_leftover_cash += leftover_cash total_leftover_cash += leftover_cash
return total_leftover_cash return total_leftover_cash
@@ -309,7 +268,6 @@ class PositionTracker(object):
Position(payment_asset) Position(payment_asset)
position.amount += share_count position.amount += share_count
self._update_asset(payment_asset)
return net_cash_payment return net_cash_payment
@@ -326,7 +284,7 @@ class PositionTracker(object):
price = self.positions.get(asset).last_sale_price price = self.positions.get(asset).last_sale_price
txn = Transaction( txn = Transaction(
sid=asset, asset=asset,
amount=(-1 * amount), amount=(-1 * amount),
dt=dt, dt=dt,
price=price, price=price,
@@ -339,20 +297,20 @@ class PositionTracker(object):
positions = self._positions_store positions = self._positions_store
for sid, pos in iteritems(self.positions): for asset, pos in iteritems(self.positions):
if pos.amount == 0: if pos.amount == 0:
# Clear out the position if it has become empty since the last # Clear out the position if it has become empty since the last
# time get_positions was called. Catching the KeyError is # time get_positions was called. Catching the KeyError is
# faster than checking `if sid in positions`, and this can be # faster than checking `if asset in positions`, and this can be
# potentially called in a tight inner loop. # potentially called in a tight inner loop.
try: try:
del positions[sid] del positions[asset]
except KeyError: except KeyError:
pass pass
continue continue
position = zp.Position(sid) position = zp.Position(asset)
position.amount = pos.amount position.amount = pos.amount
position.cost_basis = pos.cost_basis position.cost_basis = pos.cost_basis
position.last_sale_price = pos.last_sale_price position.last_sale_price = pos.last_sale_price
@@ -360,13 +318,13 @@ class PositionTracker(object):
# Adds the new position if we didn't have one before, or overwrite # Adds the new position if we didn't have one before, or overwrite
# one we have currently # one we have currently
positions[sid] = position positions[asset] = position
return positions return positions
def get_positions_list(self): def get_positions_list(self):
positions = [] positions = []
for sid, pos in iteritems(self.positions): for asset, pos in iteritems(self.positions):
if pos.amount != 0: if pos.amount != 0:
positions.append(pos.to_dict()) positions.append(pos.to_dict())
return positions return positions
@@ -401,16 +359,9 @@ class PositionTracker(object):
amounts.append(pos.amount) amounts.append(pos.amount)
last_sale_prices.append(pos.last_sale_price) last_sale_prices.append(pos.last_sale_price)
position_values = calc_position_values( position_values = calc_position_values(itervalues(self.positions))
amounts,
last_sale_prices,
self._position_value_multipliers
)
position_exposures = calc_position_exposures( position_exposures = calc_position_exposures(
amounts, itervalues(self.positions)
last_sale_prices,
self._position_exposure_multipliers
) )
long_value = calc_long_value(position_values) long_value = calc_long_value(position_values)
+2 -5
View File
@@ -98,7 +98,6 @@ class PerformanceTracker(object):
self.emission_rate = sim_params.emission_rate self.emission_rate = sim_params.emission_rate
self.position_tracker = PositionTracker( self.position_tracker = PositionTracker(
asset_finder=env.asset_finder,
data_frequency=self.sim_params.data_frequency data_frequency=self.sim_params.data_frequency
) )
@@ -141,7 +140,6 @@ class PerformanceTracker(object):
keep_orders=False, keep_orders=False,
# don't serialize positions for cumulative period # don't serialize positions for cumulative period
serialize_positions=False, serialize_positions=False,
asset_finder=self.asset_finder,
name="Cumulative" name="Cumulative"
) )
self.cumulative_performance.position_tracker = self.position_tracker self.cumulative_performance.position_tracker = self.position_tracker
@@ -157,7 +155,6 @@ class PerformanceTracker(object):
keep_transactions=True, keep_transactions=True,
keep_orders=True, keep_orders=True,
serialize_positions=True, serialize_positions=True,
asset_finder=self.asset_finder,
name="Daily" name="Daily"
) )
self.todays_performance.position_tracker = self.position_tracker self.todays_performance.position_tracker = self.position_tracker
@@ -275,10 +272,10 @@ class PerformanceTracker(object):
self.todays_performance.record_order(event) self.todays_performance.record_order(event)
def process_commission(self, commission): def process_commission(self, commission):
sid = commission['sid'] asset = commission['asset']
cost = commission['cost'] cost = commission['cost']
self.position_tracker.handle_commission(sid, cost) self.position_tracker.handle_commission(asset, cost)
self.cumulative_performance.handle_commission(cost) self.cumulative_performance.handle_commission(cost)
self.todays_performance.handle_commission(cost) self.todays_performance.handle_commission(cost)
+10 -6
View File
@@ -18,14 +18,13 @@ from copy import copy
from zipline.assets import Asset from zipline.assets import Asset
from zipline.protocol import DATASOURCE_TYPE from zipline.protocol import DATASOURCE_TYPE
from zipline.utils.input_validation import expect_types
class Transaction(object): class Transaction(object):
@expect_types(asset=Asset)
def __init__(self, sid, amount, dt, price, order_id, commission=None): def __init__(self, asset, amount, dt, price, order_id, commission=None):
assert isinstance(sid, Asset) self.asset = asset
self.sid = sid
self.amount = amount self.amount = amount
self.dt = dt self.dt = dt
self.price = price self.price = price
@@ -39,6 +38,11 @@ class Transaction(object):
def to_dict(self): def to_dict(self):
py = copy(self.__dict__) py = copy(self.__dict__)
del py['type'] del py['type']
del py['asset']
# Adding 'sid' for backwards compatibility with downstrean consumers.
py['sid'] = self.asset
return py return py
@@ -53,7 +57,7 @@ def create_transaction(order, dt, price, amount):
raise Exception("Transaction magnitude must be at least 1.") raise Exception("Transaction magnitude must be at least 1.")
transaction = Transaction( transaction = Transaction(
sid=order.sid, asset=order.asset,
amount=int(amount), amount=int(amount),
dt=dt, dt=dt,
price=price, price=price,
+10 -3
View File
@@ -16,6 +16,8 @@ from warnings import warn
import pandas as pd import pandas as pd
from zipline.assets import Asset
from zipline.utils.input_validation import expect_types
from .utils.enum import enum from .utils.enum import enum
from zipline._protocol import BarData # noqa from zipline._protocol import BarData # noqa
@@ -225,14 +227,19 @@ class Account(object):
class Position(object): class Position(object):
@expect_types(asset=Asset)
def __init__(self, sid): def __init__(self, asset):
self.sid = sid self.asset = asset
self.amount = 0 self.amount = 0
self.cost_basis = 0.0 # per share self.cost_basis = 0.0 # per share
self.last_sale_price = 0.0 self.last_sale_price = 0.0
self.last_sale_date = None self.last_sale_date = None
@property
def sid(self):
# for backwards compatibility
return self.asset
def __repr__(self): def __repr__(self):
return "Position({0})".format(self.__dict__) return "Position({0})".format(self.__dict__)
+2 -4
View File
@@ -1512,10 +1512,8 @@ def ensure_doctest(f, name=None):
class RecordBatchBlotter(Blotter): class RecordBatchBlotter(Blotter):
"""Blotter that tracks how its batch_order method was called. """Blotter that tracks how its batch_order method was called.
""" """
def __init__(self, data_frequency, asset_finder): def __init__(self, data_frequency):
super(RecordBatchBlotter, self).__init__( super(RecordBatchBlotter, self).__init__(data_frequency)
data_frequency, asset_finder,
)
self.order_batch_called = [] self.order_batch_called = []
def batch_order(self, *args, **kwargs): def batch_order(self, *args, **kwargs):
+16 -14
View File
@@ -21,6 +21,8 @@ import pandas as pd
import numpy as np import numpy as np
from datetime import timedelta, datetime from datetime import timedelta, datetime
from zipline.assets import Asset
from zipline.finance.transaction import Transaction
from zipline.protocol import Event, DATASOURCE_TYPE from zipline.protocol import Event, DATASOURCE_TYPE
from zipline.sources import SpecificEquityTrades from zipline.sources import SpecificEquityTrades
from zipline.finance.trading import SimulationParameters from zipline.finance.trading import SimulationParameters
@@ -30,7 +32,7 @@ from zipline.data.loader import ( # For backwards compatibility
load_bars_from_yahoo, load_bars_from_yahoo,
) )
from zipline.utils.calendars import get_calendar from zipline.utils.calendars import get_calendar
from zipline.utils.input_validation import expect_types
__all__ = ['load_from_yahoo', 'load_bars_from_yahoo'] __all__ = ['load_from_yahoo', 'load_bars_from_yahoo']
@@ -150,27 +152,27 @@ def create_split(sid, ratio, date):
}) })
def create_txn(sid, price, amount, datetime): @expect_types(asset=Asset)
txn = Event({ def create_txn(asset, price, amount, datetime, order_id):
'sid': sid, return Transaction(
'amount': amount, asset=asset,
'dt': datetime, price=price,
'price': price, amount=amount,
'type': DATASOURCE_TYPE.TRANSACTION, dt=datetime,
'source_id': 'MockTransactionSource' order_id=order_id,
}) )
return txn
def create_txn_history(sid, priceList, amtList, interval, sim_params, @expect_types(asset=Asset)
def create_txn_history(asset, priceList, amtList, interval, sim_params,
trading_calendar): trading_calendar):
txns = [] txns = []
current = sim_params.first_open current = sim_params.first_open
for price, amount in zip(priceList, amtList): for price, amount in zip(priceList, amtList):
current = get_next_trading_dt(current, interval, trading_calendar) dt = get_next_trading_dt(current, interval, trading_calendar)
txns.append(create_txn(sid, price, amount, current)) txns.append(create_txn(asset, price, amount, dt, None))
current = current + interval current = current + interval
return txns return txns