def test_market_order_prices(self): """ Basic unit tests for the MarketOrder class. """ style = MarketOrder() self.assertEqual(style.get_limit_price(True), None) self.assertEqual(style.get_limit_price(False), None) self.assertEqual(style.get_stop_price(True), None) self.assertEqual(style.get_stop_price(False), None)
def test_blotter_eod_cancellation(self): blotter = SimulationBlotter(cancel_policy=EODCancel()) # Make two orders for the same asset, so we can test that we are not # mutating the orders list as we are cancelling orders blotter.order(self.asset_24, 100, MarketOrder()) blotter.order(self.asset_24, -100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 2) order_ids = [order.id for order in blotter.open_orders[self.asset_24]] self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(SESSION_END) for order_id in order_ids: order = blotter.orders[order_id] self.assertEqual(order.status, ORDER_STATUS.CANCELLED)
def test_blotter_never_cancel(self): blotter = Blotter('minute', cancel_policy=NeverCancel()) blotter.order(self.asset_24, 100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(SESSION_END) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
def test_blotter_never_cancel(self): blotter = SimulationBlotter(cancel_policy=NeverCancel()) blotter.order(self.asset_24, 100, MarketOrder()) assert len(blotter.new_orders) == 1 assert blotter.new_orders[0].status == ORDER_STATUS.OPEN blotter.execute_cancel_policy(BAR) assert blotter.new_orders[0].status == ORDER_STATUS.OPEN blotter.execute_cancel_policy(SESSION_END) assert blotter.new_orders[0].status == ORDER_STATUS.OPEN
def handle_data(self, data): from zipline.api import ( order_percent, order_target, order_target_percent, order_target_value, order_value, ) for style in [ MarketOrder(), LimitOrder(10), StopOrder(10), StopLimitOrder(10, 10) ]: with assert_raises(UnsupportedOrderParameters): order(self.sid, 10, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order(self.sid, 10, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_value(self.sid, 300, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_value(self.sid, 300, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_percent(self.sid, .1, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_percent(self.sid, .1, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target(self.sid, 100, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target(self.sid, 100, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_value(self.sid, 100, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_value(self.sid, 100, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_percent(self.sid, .2, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_percent(self.sid, .2, stop_price=10, style=style)
def test_transactions_created_for_complete_orders(self, symbol_lookup): with patch('zipline.gens.brokers.ib_broker.TWSConnection.connect'): broker = IBBroker("localhost:9999:1111", account_id='TEST-123') broker._tws.nextValidId(0) asset = self.asset_finder.retrieve_asset(1) symbol_lookup.return_value = asset order_count = 0 for amount, order_style in [(-112, StopLimitOrder(limit_price=9, stop_price=1)), (43, LimitOrder(limit_price=10)), (-99, StopOrder(stop_price=8)), (-32, MarketOrder())]: order = broker.order(asset, amount, order_style) broker._tws.orderStatus(order.broker_order_id, 'Filled', filled=int(fabs(amount)), remaining=0, avg_fill_price=111, perm_id=0, parent_id=1, last_fill_price=112, client_id=1111, why_held='') contract = self._create_contract(str(asset.symbol)) (shares, cum_qty, price, avg_price, exec_time, exec_id) = \ (int(fabs(amount)), int(fabs(amount)), 12.3, 12.31, pd.to_datetime('now', utc=True), order_count) exec_detail = self._create_exec_detail(order.broker_order_id, shares, cum_qty, price, avg_price, exec_time, exec_id) broker._tws.execDetails(0, contract, exec_detail) order_count += 1 assert len(broker.transactions) == order_count transactions = [ tx for tx in broker.transactions.values() if tx.order_id == order.id ] assert len(transactions) == 1 assert broker.transactions[exec_id].asset == asset assert broker.transactions[exec_id].amount == order.amount assert (broker.transactions[exec_id].dt - pd.to_datetime('now', utc=True) < pd.Timedelta('10s')) assert broker.transactions[exec_id].price == price assert broker.orders[order.id].commission == 0
def test_factory_some_orders_and_no_fills(self): matcher = mmm_Matcher() fills = {} MID_DATE_0 = pd.Timestamp('2013-01-07 17:00', tz='utc') orders = { 1: { 1: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, 2: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, 3: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, } } assets = {1: a1} all_closed, all_txns, open_orders, unused, all_minutes = mmm_factory( matcher, fills, orders, assets) self.assertEqual(0, len(all_closed)) self.assertEqual(0, len(all_txns)) self.assertEqual(1, len(open_orders)) a1a = matcher.env.asset_finder.retrieve_asset(sid=1) self.assertEqual(3, len(open_orders[a1a]))
def test_blotter_never_cancel(self): blotter = Blotter('minute', self.env.asset_finder, cancel_policy=NeverCancel()) blotter.order(blotter.asset_finder.retrieve_asset(24), 100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(DAY_END) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN)
def test_blotter_eod_cancellation(self): blotter = Blotter('minute', self.env.asset_finder, cancel_policy=EODCancel()) asset_24 = blotter.asset_finder.retrieve_asset(24) # Make two orders for the same sid, so we can test that we are not # mutating the orders list as we are cancelling orders blotter.order(asset_24, 100, MarketOrder()) blotter.order(asset_24, -100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 2) order_ids = [order.id for order in blotter.open_orders[asset_24]] self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(DAY_END) for order_id in order_ids: order = blotter.orders[order_id] self.assertEqual(order.status, ORDER_STATUS.CANCELLED)
def test_cancel(self): blotter = Blotter('daily', self.env.asset_finder) asset_24 = blotter.asset_finder.retrieve_asset(24) asset_25 = blotter.asset_finder.retrieve_asset(25) oid_1 = blotter.order(asset_24, 100, MarketOrder()) oid_2 = blotter.order(asset_24, 200, MarketOrder()) oid_3 = blotter.order(asset_24, 300, MarketOrder()) # Create an order for another asset to verify that we don't remove it # when we do cancel_all on 24. blotter.order(asset_25, 150, MarketOrder()) self.assertEqual(len(blotter.open_orders), 2) self.assertEqual(len(blotter.open_orders[asset_24]), 3) self.assertEqual( [o.amount for o in blotter.open_orders[asset_24]], [100, 200, 300], ) blotter.cancel(oid_2) self.assertEqual(len(blotter.open_orders), 2) self.assertEqual(len(blotter.open_orders[asset_24]), 2) self.assertEqual( [o.amount for o in blotter.open_orders[asset_24]], [100, 300], ) self.assertEqual( [o.id for o in blotter.open_orders[asset_24]], [oid_1, oid_3], ) blotter.cancel_all_orders_for_asset(asset_24) self.assertEqual(len(blotter.open_orders), 1) self.assertEqual(list(blotter.open_orders), [asset_25])
def test_slippage_and_commission_dispatching(self): blotter = Blotter( self.sim_params.data_frequency, self.asset_finder, equity_slippage=FixedSlippage(spread=0.0), future_slippage=FixedSlippage(spread=2.0), equity_commission=PerTrade(cost=1.0), future_commission=PerTrade(cost=2.0), ) blotter.order(self.asset_24, 1, MarketOrder()) blotter.order(self.future_cl, 1, MarketOrder()) bar_data = self.create_bardata( simulation_dt_func=lambda: self.sim_params.sessions[-1], ) txns, commissions, _ = blotter.get_transactions(bar_data) # The equity transaction should have the same price as its current # price because the slippage spread is zero. Its commission should be # $1.00. equity_txn = txns[0] self.assertEqual( equity_txn.price, bar_data.current(equity_txn.sid, 'price'), ) self.assertEqual(commissions[0]['cost'], 1.0) # The future transaction price should be 1.0 more than its current # price because half of the 'future_slippage' spread is added. Its # commission should be $2.00. future_txn = txns[1] self.assertEqual( future_txn.price, bar_data.current(future_txn.sid, 'price') + 1.0, ) self.assertEqual(commissions[1]['cost'], 2.0)
def test_market_order_prices(self): """ Basic unit tests for the MarketOrder class. """ style = MarketOrder() assert_equal(style.get_limit_price(_is_buy=True), None) assert_equal(style.get_limit_price(_is_buy=False), None) assert_equal(style.get_stop_price(_is_buy=True), None) assert_equal(style.get_stop_price(_is_buy=False), None)
def test_prune_orders(self): blotter = Blotter(self.sim_params.data_frequency, self.asset_finder) blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] blotter.prune_orders([]) self.assertEqual(1, len(blotter.open_orders[self.asset_24])) blotter.prune_orders([open_order]) self.assertEqual(0, len(blotter.open_orders[self.asset_24])) # prune an order that isn't in our our open orders list, make sure # nothing blows up other_order = Order(dt=blotter.current_dt, sid=self.asset_25, amount=1) blotter.prune_orders([other_order])
def test_factory_fills_before_orders(self): matcher = mmm_Matcher() # Use same sid as for assets above # NOT Multiplying by 1000 as documented in zipline/data/minute_bars.py#L419 MID_DATE_1 = pd.Timestamp('2013-01-07 17:01', tz='utc') MID_DATE_2 = pd.Timestamp('2013-01-07 17:03', tz='utc') fills = { 1: pd.DataFrame({ "close": [1, 2], "volume": [5, 5], "dt": [MID_DATE_1, MID_DATE_2] }).set_index("dt"), } MID_DATE_0 = pd.Timestamp('2013-01-07 17:02', tz='utc') orders = { 1: { 1: { "dt": MID_DATE_0, "amount": 5, "style": MarketOrder() }, } } assets = {1: a1} all_closed, all_txns, open_orders, unused, all_minutes = mmm_factory( matcher, fills, orders, assets) self.assertEqual(1, len(all_closed)) self.assertEqual(1, len(all_txns)) self.assertEqual(0, len(open_orders)) txn = all_txns[0].to_dict() self.assertEqual( txn["price"], 1 ) # this is 1 if fills are allowed to match with later orders, and 2 if not. The latter is the case ATM a1a = matcher.env.asset_finder.retrieve_asset(sid=1) self.assertEqual(unused, {a1a: [5]})
class BlotterTestCase(TestCase): def setUp(self): setup_logger(self) def tearDown(self): teardown_logger(self) @parameterized.expand([(MarketOrder(), None, None), (LimitOrder(10), 10, None), (StopOrder(10), None, 10), (StopLimitOrder(10, 20), 10, 20)]) def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp): blotter = Blotter() blotter.order(24, 100, style_obj) result = blotter.open_orders[24][0] self.assertEqual(result.limit, expected_lmt) self.assertEqual(result.stop, expected_stp)
def __convert_order_params_for_blotter(limit_price, stop_price, style): """ Helper method for converting deprecated limit_price and stop_price arguments into ExecutionStyle instances. This function assumes that either style == None or (limit_price, stop_price) == (None, None). """ # TODO_SS: DeprecationWarning for usage of limit_price and stop_price. if style: assert (limit_price, stop_price) == (None, None) return style if limit_price and stop_price: return StopLimitOrder(limit_price, stop_price) if limit_price: return LimitOrder(limit_price) if stop_price: return StopOrder(stop_price) else: return MarketOrder()
def test_prune_orders(self): blotter = SimulationBlotter() blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] blotter.prune_orders([]) self.assertEqual(1, len(blotter.open_orders[self.asset_24])) blotter.prune_orders([open_order]) self.assertEqual(0, len(blotter.open_orders[self.asset_24])) # prune an order that isn't in our our open orders list, make sure # nothing blows up other_order = Order(dt=blotter.current_dt, asset=self.asset_25, amount=1) blotter.prune_orders([other_order])
def test_multiple_orders(self, symbol_lookup): with patch('zipline.gens.brokers.ib_broker.TWSConnection.connect'): broker = IBBroker("localhost:9999:1111", account_id='TEST-123') broker._tws.nextValidId(0) asset = self.asset_finder.retrieve_asset(1) symbol_lookup.return_value = asset order_count = 0 for amount, order_style in [(-112, StopLimitOrder(limit_price=9, stop_price=1)), (43, LimitOrder(limit_price=10)), (-99, StopOrder(stop_price=8)), (-32, MarketOrder())]: order = broker.order(asset, amount, order_style) order_count += 1 assert order_count == len(broker.orders) assert broker.orders[order.id] == order is_buy = amount > 0 assert order.stop == order_style.get_stop_price(is_buy) assert order.limit == order_style.get_limit_price(is_buy)
def test_order_rejection(self): blotter = SimulationBlotter(self.sim_params) # Reject a nonexistent order -> no order appears in new_order, # no exceptions raised out blotter.reject(56) self.assertEqual(blotter.new_orders, []) # Basic tests of open order behavior open_order_id = blotter.order(self.asset_24, 100, MarketOrder()) second_order_id = blotter.order(self.asset_24, 50, MarketOrder()) self.assertEqual(len(blotter.open_orders[self.asset_24]), 2) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_order.status, ORDER_STATUS.OPEN) self.assertEqual(open_order.id, open_order_id) self.assertIn(open_order, blotter.new_orders) # Reject that order immediately (same bar, i.e. still in new_orders) blotter.reject(open_order_id) self.assertEqual(len(blotter.new_orders), 2) self.assertEqual(len(blotter.open_orders[self.asset_24]), 1) still_open_order = blotter.new_orders[0] self.assertEqual(still_open_order.id, second_order_id) self.assertEqual(still_open_order.status, ORDER_STATUS.OPEN) rejected_order = blotter.new_orders[1] self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED) self.assertEqual(rejected_order.reason, '') # Do it again, but reject it at a later time (after tradesimulation # pulls it from new_orders) blotter = SimulationBlotter(self.sim_params) new_open_id = blotter.order(self.asset_24, 10, MarketOrder()) new_open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(new_open_id, new_open_order.id) # Pretend that the trade simulation did this. blotter.new_orders = [] rejection_reason = "Not enough cash on hand." blotter.reject(new_open_id, reason=rejection_reason) rejected_order = blotter.new_orders[0] self.assertEqual(rejected_order.id, new_open_id) self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED) self.assertEqual(rejected_order.reason, rejection_reason) # You can't reject a filled order. # Reset for paranoia blotter = SimulationBlotter(self.sim_params) blotter.slippage_models[Equity] = FixedSlippage() filled_id = blotter.order(self.asset_24, 100, MarketOrder()) filled_order = None blotter.current_dt = self.sim_params.sessions[-1] bar_data = self.create_bardata( simulation_dt_func=lambda: self.sim_params.sessions[-1], ) txns, _, closed_orders = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] blotter.prune_orders(closed_orders) self.assertEqual(filled_order.id, filled_id) self.assertIn(filled_order, blotter.new_orders) self.assertEqual(filled_order.status, ORDER_STATUS.FILLED) self.assertNotIn(filled_order, blotter.open_orders[self.asset_24]) blotter.reject(filled_id) updated_order = blotter.orders[filled_id] self.assertEqual(updated_order.status, ORDER_STATUS.FILLED)
def transaction_sim(self, **params): """ This is a utility method that asserts expected results for conversion of orders to transactions given a trade history""" trade_count = params['trade_count'] trade_interval = params['trade_interval'] order_count = params['order_count'] order_amount = params['order_amount'] order_interval = params['order_interval'] expected_txn_count = params['expected_txn_count'] expected_txn_volume = params['expected_txn_volume'] # optional parameters # --------------------- # if present, alternate between long and short sales alternate = params.get('alternate') # if present, expect transaction amounts to match orders exactly. complete_fill = params.get('complete_fill') sid = 1 sim_params = factory.create_simulation_parameters() blotter = Blotter() price = [10.1] * trade_count volume = [100] * trade_count start_date = sim_params.first_open generated_trades = factory.create_trade_history( sid, price, volume, trade_interval, sim_params) if alternate: alternator = -1 else: alternator = 1 order_date = start_date for i in range(order_count): blotter.set_date(order_date) blotter.order(sid, order_amount * alternator**i, MarketOrder()) order_date = order_date + order_interval # move after market orders to just after market next # market open. if order_date.hour >= 21: if order_date.minute >= 00: order_date = order_date + timedelta(days=1) order_date = order_date.replace(hour=14, minute=30) # there should now be one open order list stored under the sid oo = blotter.open_orders self.assertEqual(len(oo), 1) self.assertTrue(sid in oo) order_list = oo[sid][:] # make copy self.assertEqual(order_count, len(order_list)) for i in range(order_count): order = order_list[i] self.assertEqual(order.sid, sid) self.assertEqual(order.amount, order_amount * alternator**i) tracker = PerformanceTracker(sim_params) benchmark_returns = [ Event({ 'dt': dt, 'returns': ret, 'type': zipline.protocol.DATASOURCE_TYPE.BENCHMARK, 'source_id': 'benchmarks' }) for dt, ret in trading.environment.benchmark_returns.iteritems() if dt.date() >= sim_params.period_start.date() and dt.date() <= sim_params.period_end.date() ] generated_events = date_sorted_sources(generated_trades, benchmark_returns) # this approximates the loop inside TradingSimulationClient transactions = [] for dt, events in itertools.groupby(generated_events, operator.attrgetter('dt')): for event in events: if event.type == DATASOURCE_TYPE.TRADE: for txn, order in blotter.process_trade(event): transactions.append(txn) tracker.process_transaction(txn) elif event.type == DATASOURCE_TYPE.BENCHMARK: tracker.process_benchmark(event) elif event.type == DATASOURCE_TYPE.TRADE: tracker.process_trade(event) if complete_fill: self.assertEqual(len(transactions), len(order_list)) total_volume = 0 for i in range(len(transactions)): txn = transactions[i] total_volume += txn.amount if complete_fill: order = order_list[i] self.assertEqual(order.amount, txn.amount) self.assertEqual(total_volume, expected_txn_volume) self.assertEqual(len(transactions), expected_txn_count) cumulative_pos = tracker.cumulative_performance.positions[sid] self.assertEqual(total_volume, cumulative_pos.amount) # the open orders should not contain sid. oo = blotter.open_orders self.assertNotIn(sid, oo, "Entry is removed when no open orders")
def transaction_sim(self, **params): """ This is a utility method that asserts expected results for conversion of orders to transactions given a trade history""" tempdir = TempDirectory() try: trade_count = params['trade_count'] trade_interval = params['trade_interval'] order_count = params['order_count'] order_amount = params['order_amount'] order_interval = params['order_interval'] expected_txn_count = params['expected_txn_count'] expected_txn_volume = params['expected_txn_volume'] # optional parameters # --------------------- # if present, alternate between long and short sales alternate = params.get('alternate') # if present, expect transaction amounts to match orders exactly. complete_fill = params.get('complete_fill') env = TradingEnvironment() sid = 1 if trade_interval < timedelta(days=1): sim_params = factory.create_simulation_parameters( data_frequency="minute") minutes = env.market_minute_window( sim_params.first_open, int((trade_interval.total_seconds() / 60) * trade_count) + 100) price_data = np.array([10.1] * len(minutes)) assets = { sid: pd.DataFrame({ "open": price_data, "high": price_data, "low": price_data, "close": price_data, "volume": np.array([100] * len(minutes)), "dt": minutes }).set_index("dt") } write_bcolz_minute_data( env, env.days_in_range(minutes[0], minutes[-1]), tempdir.path, assets) equity_minute_reader = BcolzMinuteBarReader(tempdir.path) data_portal = DataPortal( env, equity_minute_reader=equity_minute_reader, ) else: sim_params = factory.create_simulation_parameters( data_frequency="daily") days = sim_params.trading_days assets = { 1: pd.DataFrame( { "open": [10.1] * len(days), "high": [10.1] * len(days), "low": [10.1] * len(days), "close": [10.1] * len(days), "volume": [100] * len(days), "day": [day.value for day in days] }, index=days) } path = os.path.join(tempdir.path, "testdata.bcolz") DailyBarWriterFromDataFrames(assets).write(path, days, assets) equity_daily_reader = BcolzDailyBarReader(path) data_portal = DataPortal( env, equity_daily_reader=equity_daily_reader, ) if "default_slippage" not in params or \ not params["default_slippage"]: slippage_func = FixedSlippage() else: slippage_func = None blotter = Blotter(sim_params.data_frequency, self.env.asset_finder, slippage_func) env.write_data( equities_data={ sid: { "start_date": sim_params.trading_days[0], "end_date": sim_params.trading_days[-1] } }) start_date = sim_params.first_open if alternate: alternator = -1 else: alternator = 1 tracker = PerformanceTracker(sim_params, self.env) # replicate what tradesim does by going through every minute or day # of the simulation and processing open orders each time if sim_params.data_frequency == "minute": ticks = minutes else: ticks = days transactions = [] order_list = [] order_date = start_date for tick in ticks: blotter.current_dt = tick if tick >= order_date and len(order_list) < order_count: # place an order direction = alternator**len(order_list) order_id = blotter.order( blotter.asset_finder.retrieve_asset(sid), order_amount * direction, MarketOrder()) order_list.append(blotter.orders[order_id]) order_date = order_date + order_interval # move after market orders to just after market next # market open. if order_date.hour >= 21: if order_date.minute >= 00: order_date = order_date + timedelta(days=1) order_date = order_date.replace(hour=14, minute=30) else: bar_data = BarData(data_portal, lambda: tick, sim_params.data_frequency) txns, _ = blotter.get_transactions(bar_data) for txn in txns: tracker.process_transaction(txn) transactions.append(txn) for i in range(order_count): order = order_list[i] self.assertEqual(order.sid, sid) self.assertEqual(order.amount, order_amount * alternator**i) if complete_fill: self.assertEqual(len(transactions), len(order_list)) total_volume = 0 for i in range(len(transactions)): txn = transactions[i] total_volume += txn.amount if complete_fill: order = order_list[i] self.assertEqual(order.amount, txn.amount) self.assertEqual(total_volume, expected_txn_volume) self.assertEqual(len(transactions), expected_txn_count) cumulative_pos = tracker.position_tracker.positions[sid] if total_volume == 0: self.assertIsNone(cumulative_pos) else: self.assertEqual(total_volume, cumulative_pos.amount) # the open orders should not contain sid. oo = blotter.open_orders self.assertNotIn(sid, oo, "Entry is removed when no open orders") finally: tempdir.cleanup()
def test_order(self, tradeapi, symbol_lookup): api = tradeapi.REST() asset = self.asset_finder.retrieve_asset(1) symbol_lookup.return_value = asset broker = ALPACABroker('') amount = 10 submitted_orders = [] def submit_order(symbol, qty, side, type, time_in_force, limit_price, stop_price, client_order_id): o = apca.Order({ 'symbol': symbol, 'qty': str(qty), 'side': side, 'type': type, 'time_in_force': time_in_force, 'limit_price': limit_price, 'stop_price': stop_price, 'client_order_id': client_order_id, 'submitted_at': '2017-06-01T10:30:00-0400', 'filled_at': None, 'filled_qty': None, 'canceled_at': None, 'failed_at': None, }) submitted_orders.append(o) return o api.submit_order = submit_order order = broker.order(asset, amount, MarketOrder()) assert order.limit is None assert submitted_orders[-1].side == 'buy' assert submitted_orders[-1].type == 'market' order = broker.order(asset, -amount, LimitOrder(210.00)) assert order.limit == 210.00 assert order.amount == -amount assert submitted_orders[-1].side == 'sell' assert submitted_orders[-1].type == 'limit' assert submitted_orders[-1].limit_price is not None order = broker.order(asset, amount, StopOrder(211)) assert order.stop == 211.00 assert submitted_orders[-1].side == 'buy' assert submitted_orders[-1].type == 'stop' assert submitted_orders[-1].stop_price is not None order = broker.order(asset, -amount, StopLimitOrder(210, 211)) assert order.limit == 210.00 assert order.stop == 211.00 assert submitted_orders[-1].side == 'sell' assert submitted_orders[-1].type == 'stop_limit' assert submitted_orders[-1].limit_price is not None assert submitted_orders[-1].stop_price is not None api.get_order_by_client_order_id.return_value = submitted_orders[-1] def cancel_order(self, order_id): assert order.id == order_id broker.cancel_order(order.id)
def close_positions(context, data): """ Closes all positions. """ for asset, position in context.portfolio.positions.items(): algo.order(asset, -position.amount, style=MarketOrder())
class BlotterTestCase(WithLogger, WithDataPortal, WithSimParams, ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-05', tz='utc') END_DATE = pd.Timestamp('2006-01-06', tz='utc') ASSET_FINDER_EQUITY_SIDS = 24, 25 @classmethod def make_daily_bar_data(cls): yield 24, pd.DataFrame( { 'open': [50, 50], 'high': [50, 50], 'low': [50, 50], 'close': [50, 50], 'volume': [100, 400], }, index=cls.sim_params.trading_days, ) yield 25, pd.DataFrame( { 'open': [50, 50], 'high': [50, 50], 'low': [50, 50], 'close': [50, 50], 'volume': [100, 400], }, index=cls.sim_params.trading_days, ) @parameterized.expand([(MarketOrder(), None, None), (LimitOrder(10), 10, None), (StopOrder(10), None, 10), (StopLimitOrder(10, 20), 10, 20)]) def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp): blotter = Blotter('daily', self.env.asset_finder) asset_24 = blotter.asset_finder.retrieve_asset(24) blotter.order(asset_24, 100, style_obj) result = blotter.open_orders[asset_24][0] self.assertEqual(result.limit, expected_lmt) self.assertEqual(result.stop, expected_stp) def test_cancel(self): blotter = Blotter('daily', self.env.asset_finder) asset_24 = blotter.asset_finder.retrieve_asset(24) asset_25 = blotter.asset_finder.retrieve_asset(25) oid_1 = blotter.order(asset_24, 100, MarketOrder()) oid_2 = blotter.order(asset_24, 200, MarketOrder()) oid_3 = blotter.order(asset_24, 300, MarketOrder()) # Create an order for another asset to verify that we don't remove it # when we do cancel_all on 24. blotter.order(asset_25, 150, MarketOrder()) self.assertEqual(len(blotter.open_orders), 2) self.assertEqual(len(blotter.open_orders[asset_24]), 3) self.assertEqual( [o.amount for o in blotter.open_orders[asset_24]], [100, 200, 300], ) blotter.cancel(oid_2) self.assertEqual(len(blotter.open_orders), 2) self.assertEqual(len(blotter.open_orders[asset_24]), 2) self.assertEqual( [o.amount for o in blotter.open_orders[asset_24]], [100, 300], ) self.assertEqual( [o.id for o in blotter.open_orders[asset_24]], [oid_1, oid_3], ) blotter.cancel_all_orders_for_asset(asset_24) self.assertEqual(len(blotter.open_orders), 1) self.assertEqual(list(blotter.open_orders), [asset_25]) def test_blotter_eod_cancellation(self): blotter = Blotter('minute', self.env.asset_finder, cancel_policy=EODCancel()) asset_24 = blotter.asset_finder.retrieve_asset(24) # Make two orders for the same sid, so we can test that we are not # mutating the orders list as we are cancelling orders blotter.order(asset_24, 100, MarketOrder()) blotter.order(asset_24, -100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 2) order_ids = [order.id for order in blotter.open_orders[asset_24]] self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(DAY_END) for order_id in order_ids: order = blotter.orders[order_id] self.assertEqual(order.status, ORDER_STATUS.CANCELLED) def test_blotter_never_cancel(self): blotter = Blotter('minute', self.env.asset_finder, cancel_policy=NeverCancel()) blotter.order(blotter.asset_finder.retrieve_asset(24), 100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(DAY_END) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) def test_order_rejection(self): blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) asset_24 = blotter.asset_finder.retrieve_asset(24) # Reject a nonexistent order -> no order appears in new_order, # no exceptions raised out blotter.reject(56) self.assertEqual(blotter.new_orders, []) # Basic tests of open order behavior open_order_id = blotter.order(asset_24, 100, MarketOrder()) second_order_id = blotter.order(asset_24, 50, MarketOrder()) self.assertEqual(len(blotter.open_orders[asset_24]), 2) open_order = blotter.open_orders[asset_24][0] self.assertEqual(open_order.status, ORDER_STATUS.OPEN) self.assertEqual(open_order.id, open_order_id) self.assertIn(open_order, blotter.new_orders) # Reject that order immediately (same bar, i.e. still in new_orders) blotter.reject(open_order_id) self.assertEqual(len(blotter.new_orders), 2) self.assertEqual(len(blotter.open_orders[asset_24]), 1) still_open_order = blotter.new_orders[0] self.assertEqual(still_open_order.id, second_order_id) self.assertEqual(still_open_order.status, ORDER_STATUS.OPEN) rejected_order = blotter.new_orders[1] self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED) self.assertEqual(rejected_order.reason, '') # Do it again, but reject it at a later time (after tradesimulation # pulls it from new_orders) blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) new_open_id = blotter.order(asset_24, 10, MarketOrder()) new_open_order = blotter.open_orders[asset_24][0] self.assertEqual(new_open_id, new_open_order.id) # Pretend that the trade simulation did this. blotter.new_orders = [] rejection_reason = "Not enough cash on hand." blotter.reject(new_open_id, reason=rejection_reason) rejected_order = blotter.new_orders[0] self.assertEqual(rejected_order.id, new_open_id) self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED) self.assertEqual(rejected_order.reason, rejection_reason) # You can't reject a filled order. # Reset for paranoia blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) blotter.slippage_func = FixedSlippage() filled_id = blotter.order(asset_24, 100, MarketOrder()) filled_order = None blotter.current_dt = self.sim_params.trading_days[-1] bar_data = BarData( self.data_portal, lambda: self.sim_params.trading_days[-1], self.sim_params.data_frequency, ) txns, _ = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] self.assertEqual(filled_order.id, filled_id) self.assertIn(filled_order, blotter.new_orders) self.assertEqual(filled_order.status, ORDER_STATUS.FILLED) self.assertNotIn(filled_order, blotter.open_orders[asset_24]) blotter.reject(filled_id) updated_order = blotter.orders[filled_id] self.assertEqual(updated_order.status, ORDER_STATUS.FILLED) def test_order_hold(self): """ Held orders act almost identically to open orders, except for the status indication. When a fill happens, the order should switch status to OPEN/FILLED as necessary """ blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) # Nothing happens on held of a non-existent order blotter.hold(56) self.assertEqual(blotter.new_orders, []) asset_24 = blotter.asset_finder.retrieve_asset(24) open_id = blotter.order(asset_24, 100, MarketOrder()) open_order = blotter.open_orders[asset_24][0] self.assertEqual(open_order.id, open_id) blotter.hold(open_id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[asset_24]), 1) held_order = blotter.new_orders[0] self.assertEqual(held_order.status, ORDER_STATUS.HELD) self.assertEqual(held_order.reason, '') blotter.cancel(held_order.id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[asset_24]), 0) cancelled_order = blotter.new_orders[0] self.assertEqual(cancelled_order.id, held_order.id) self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED) for data in ([100, self.sim_params.trading_days[0]], [400, self.sim_params.trading_days[1]]): # Verify that incoming fills will change the order status. trade_amt = data[0] dt = data[1] order_size = 100 expected_filled = int(trade_amt * DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT) expected_open = order_size - expected_filled expected_status = ORDER_STATUS.OPEN if expected_open else \ ORDER_STATUS.FILLED blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) open_id = blotter.order(blotter.asset_finder.retrieve_asset(24), order_size, MarketOrder()) open_order = blotter.open_orders[asset_24][0] self.assertEqual(open_id, open_order.id) blotter.hold(open_id) held_order = blotter.new_orders[0] filled_order = None blotter.current_dt = dt bar_data = BarData( self.data_portal, lambda: dt, self.sim_params.data_frequency, ) txns, _ = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] self.assertEqual(filled_order.id, held_order.id) self.assertEqual(filled_order.status, expected_status) self.assertEqual(filled_order.filled, expected_filled) self.assertEqual(filled_order.open_amount, expected_open)
def test_order_hold(self): """ Held orders act almost identically to open orders, except for the status indication. When a fill happens, the order should switch status to OPEN/FILLED as necessary """ blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) # Nothing happens on held of a non-existent order blotter.hold(56) self.assertEqual(blotter.new_orders, []) asset_24 = blotter.asset_finder.retrieve_asset(24) open_id = blotter.order(asset_24, 100, MarketOrder()) open_order = blotter.open_orders[asset_24][0] self.assertEqual(open_order.id, open_id) blotter.hold(open_id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[asset_24]), 1) held_order = blotter.new_orders[0] self.assertEqual(held_order.status, ORDER_STATUS.HELD) self.assertEqual(held_order.reason, '') blotter.cancel(held_order.id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[asset_24]), 0) cancelled_order = blotter.new_orders[0] self.assertEqual(cancelled_order.id, held_order.id) self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED) for data in ([100, self.sim_params.trading_days[0]], [400, self.sim_params.trading_days[1]]): # Verify that incoming fills will change the order status. trade_amt = data[0] dt = data[1] order_size = 100 expected_filled = int(trade_amt * DEFAULT_VOLUME_SLIPPAGE_BAR_LIMIT) expected_open = order_size - expected_filled expected_status = ORDER_STATUS.OPEN if expected_open else \ ORDER_STATUS.FILLED blotter = Blotter(self.sim_params.data_frequency, self.env.asset_finder) open_id = blotter.order(blotter.asset_finder.retrieve_asset(24), order_size, MarketOrder()) open_order = blotter.open_orders[asset_24][0] self.assertEqual(open_id, open_order.id) blotter.hold(open_id) held_order = blotter.new_orders[0] filled_order = None blotter.current_dt = dt bar_data = BarData( self.data_portal, lambda: dt, self.sim_params.data_frequency, ) txns, _ = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] self.assertEqual(filled_order.id, held_order.id) self.assertEqual(filled_order.status, expected_status) self.assertEqual(filled_order.filled, expected_filled) self.assertEqual(filled_order.open_amount, expected_open)
def test_factory_orders_with_validity(self): matcher = mmm_Matcher() # Use same sid as for assets above # NOT Multiplying by 1000 as documented in zipline/data/minute_bars.py#L419 MID_DATE_1 = pd.Timestamp('2013-01-07 17:01', tz='utc') MID_DATE_2 = pd.Timestamp('2013-01-07 17:02', tz='utc') MID_DATE_3 = pd.Timestamp('2013-01-07 17:03', tz='utc') # seconds in below timestamp will be floored MID_DATE_4 = pd.Timestamp('2017-02-13 05:13:23', tz='utc') fills = { 1: pd.DataFrame({ "close": [3.5, 4.5, 4, 2.4], "volume": [10, 5, 7, 1], "dt": [MID_DATE_1, MID_DATE_2, MID_DATE_3, MID_DATE_4] }).set_index("dt"), 2: pd.DataFrame({ "close": [3.5], "volume": [1], "dt": [MID_DATE_4] }).set_index("dt") } #print("data: %s" % (fills)) MID_DATE_0 = pd.Timestamp('2013-01-07 17:00', tz='utc') orders = { 1: { 1: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder(), "validity": None }, 2: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder(), "validity": { "type": ORDER_VALIDITY.GTC, "date": None } }, 3: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder(), "validity": { "type": ORDER_VALIDITY.GTD, "date": MID_DATE_3 } }, }, 2: { 4: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder(), "validity": { "type": ORDER_VALIDITY.DAY, "date": None } }, } } assets = {1: a1, 2: a2} all_closed, all_txns, open_orders, unused, all_minutes = mmm_factory( matcher, fills, orders, assets) self.assertEqual(2, len(all_closed)) self.assertEqual(4, len(all_txns)) self.assertEqual(0, len(open_orders)) # remaining order on asset 1 gets filled on MID_DATE_3 but not on MID_DATE_4 due to validity sub = [txn['amount'] for txn in all_txns if txn['order_id'] == 3] self.assertEqual(1, len(sub)) self.assertEqual(2, sub[0]) # remaining order on asset 2 does not get filled at all due to validity sub = [txn['amount'] for txn in all_txns if txn['order_id'] == 4] self.assertEqual(0, len(sub))
def test_factory_some_orders_and_some_fills(self): matcher = mmm_Matcher() # Use same sid as for assets above # NOT Multiplying by 1000 as documented in zipline/data/minute_bars.py#L419 MID_DATE_1 = pd.Timestamp('2013-01-07 17:01', tz='utc') MID_DATE_2 = pd.Timestamp('2013-01-07 17:02', tz='utc') MID_DATE_3 = pd.Timestamp('2013-01-07 17:03', tz='utc') # seconds in below timestamp will be floored MID_DATE_4 = pd.Timestamp('2017-02-13 05:13:23', tz='utc') fills = { 1: pd.DataFrame({ "close": [3.5, 4.5, 4, 2.4], "volume": [10, 5, 7, 1], "dt": [MID_DATE_1, MID_DATE_2, MID_DATE_3, MID_DATE_4] }).set_index("dt"), 2: pd.DataFrame({ "close": [3.5], "volume": [1], "dt": [MID_DATE_4] }).set_index("dt") } #print("data: %s" % (fills)) MID_DATE_0 = pd.Timestamp('2013-01-07 17:00', tz='utc') orders = { 1: { 1: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, 2: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, 3: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, }, 2: { 4: { "dt": MID_DATE_0, "amount": 10, "style": MarketOrder() }, } } assets = {1: a1, 2: a2} all_closed, all_txns, open_orders, unused, all_minutes = mmm_factory( matcher, fills, orders, assets) self.assertEqual(2, len(all_closed)) self.assertEqual(6, len(all_txns)) self.assertEqual(2, len(open_orders)) a1a = matcher.env.asset_finder.retrieve_asset(sid=1) self.assertEqual(1, len(open_orders[a1a]))
def test_order_hold(self): """ Held orders act almost identically to open orders, except for the status indication. When a fill happens, the order should switch status to OPEN/FILLED as necessary """ blotter = SimulationBlotter(self.sim_params, equity_slippage=VolumeShareSlippage()) # Nothing happens on held of a non-existent order blotter.hold(56) self.assertEqual(blotter.new_orders, []) open_id = blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_order.id, open_id) blotter.hold(open_id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[self.asset_24]), 1) held_order = blotter.new_orders[0] self.assertEqual(held_order.status, ORDER_STATUS.HELD) self.assertEqual(held_order.reason, '') blotter.cancel(held_order.id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[self.asset_24]), 0) cancelled_order = blotter.new_orders[0] self.assertEqual(cancelled_order.id, held_order.id) self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED) for data in ([100, self.sim_params.sessions[0]], [400, self.sim_params.sessions[1]]): # Verify that incoming fills will change the order status. trade_amt = data[0] dt = data[1] order_size = 100 expected_filled = int(trade_amt * DEFAULT_EQUITY_VOLUME_SLIPPAGE_BAR_LIMIT) expected_open = order_size - expected_filled expected_status = ORDER_STATUS.OPEN if expected_open else \ ORDER_STATUS.FILLED blotter = SimulationBlotter(self.sim_params, equity_slippage=VolumeShareSlippage()) open_id = blotter.order(self.asset_24, order_size, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_id, open_order.id) blotter.hold(open_id) held_order = blotter.new_orders[0] filled_order = None blotter.current_dt = dt bar_data = self.create_bardata(simulation_dt_func=lambda: dt, ) txns, _, _ = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] self.assertEqual(filled_order.id, held_order.id) self.assertEqual(filled_order.status, expected_status) self.assertEqual(filled_order.filled, expected_filled) self.assertEqual(filled_order.open_amount, expected_open)
class BlotterTestCase(WithCreateBarData, WithDataPortal, WithSimParams, ZiplineTestCase): START_DATE = pd.Timestamp('2006-01-05', tz='utc') END_DATE = pd.Timestamp('2006-01-06', tz='utc') ASSET_FINDER_EQUITY_SIDS = 24, 25 @classmethod def init_class_fixtures(cls): super(BlotterTestCase, cls).init_class_fixtures() cls.asset_24 = cls.asset_finder.retrieve_asset(24) cls.asset_25 = cls.asset_finder.retrieve_asset(25) cls.future_cl = cls.asset_finder.retrieve_asset(1000) @classmethod def make_equity_daily_bar_data(cls, country_code, sids): yield 24, pd.DataFrame( { 'open': [50, 50], 'high': [50, 50], 'low': [50, 50], 'close': [50, 50], 'volume': [100, 400], }, index=cls.sim_params.sessions, ) yield 25, pd.DataFrame( { 'open': [50, 50], 'high': [50, 50], 'low': [50, 50], 'close': [50, 50], 'volume': [100, 400], }, index=cls.sim_params.sessions, ) @classmethod def make_futures_info(cls): return pd.DataFrame.from_dict( { 1000: { 'symbol': 'CLF06', 'root_symbol': 'CL', 'real_sid': '1000', 'currency': 'USD', 'start_date': cls.START_DATE, 'end_date': cls.END_DATE, 'expiration_date': cls.END_DATE, 'auto_close_date': cls.END_DATE, 'exchange': 'CMES', }, }, orient='index', ) @classproperty def CREATE_BARDATA_DATA_FREQUENCY(cls): return cls.sim_params.data_frequency @parameterized.expand([(MarketOrder(), None, None), (LimitOrder(10), 10, None), (StopOrder(10), None, 10), (StopLimitOrder(10, 20), 10, 20)]) def test_blotter_order_types(self, style_obj, expected_lmt, expected_stp): style_obj.asset = self.asset_24 blotter = SimulationBlotter(self.sim_params) blotter.order(self.asset_24, 100, style_obj) result = blotter.open_orders[self.asset_24][0] self.assertEqual(result.limit, expected_lmt) self.assertEqual(result.stop, expected_stp) def test_cancel(self): blotter = SimulationBlotter(self.sim_params) oid_1 = blotter.order(self.asset_24, 100, MarketOrder()) oid_2 = blotter.order(self.asset_24, 200, MarketOrder()) oid_3 = blotter.order(self.asset_24, 300, MarketOrder()) # Create an order for another asset to verify that we don't remove it # when we do cancel_all on 24. blotter.order(self.asset_25, 150, MarketOrder()) self.assertEqual(len(blotter.open_orders), 2) self.assertEqual(len(blotter.open_orders[self.asset_24]), 3) self.assertEqual( [o.amount for o in blotter.open_orders[self.asset_24]], [100, 200, 300], ) blotter.cancel(oid_2) self.assertEqual(len(blotter.open_orders), 2) self.assertEqual(len(blotter.open_orders[self.asset_24]), 2) self.assertEqual( [o.amount for o in blotter.open_orders[self.asset_24]], [100, 300], ) self.assertEqual( [o.id for o in blotter.open_orders[self.asset_24]], [oid_1, oid_3], ) blotter.cancel_all_orders_for_asset(self.asset_24) self.assertEqual(len(blotter.open_orders), 1) self.assertEqual(list(blotter.open_orders), [self.asset_25]) def test_blotter_eod_cancellation(self): blotter = SimulationBlotter(self.sim_params, cancel_policy=EODCancel()) # Make two orders for the same asset, so we can test that we are not # mutating the orders list as we are cancelling orders blotter.order(self.asset_24, 100, MarketOrder()) blotter.order(self.asset_24, -100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 2) order_ids = [order.id for order in blotter.open_orders[self.asset_24]] self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) self.assertEqual(blotter.new_orders[1].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(SESSION_END) for order_id in order_ids: order = blotter.orders[order_id] self.assertEqual(order.status, ORDER_STATUS.CANCELLED) def test_blotter_never_cancel(self): blotter = SimulationBlotter(self.sim_params, cancel_policy=NeverCancel()) blotter.order(self.asset_24, 100, MarketOrder()) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(BAR) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) blotter.execute_cancel_policy(SESSION_END) self.assertEqual(blotter.new_orders[0].status, ORDER_STATUS.OPEN) def test_order_rejection(self): blotter = SimulationBlotter(self.sim_params) # Reject a nonexistent order -> no order appears in new_order, # no exceptions raised out blotter.reject(56) self.assertEqual(blotter.new_orders, []) # Basic tests of open order behavior open_order_id = blotter.order(self.asset_24, 100, MarketOrder()) second_order_id = blotter.order(self.asset_24, 50, MarketOrder()) self.assertEqual(len(blotter.open_orders[self.asset_24]), 2) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_order.status, ORDER_STATUS.OPEN) self.assertEqual(open_order.id, open_order_id) self.assertIn(open_order, blotter.new_orders) # Reject that order immediately (same bar, i.e. still in new_orders) blotter.reject(open_order_id) self.assertEqual(len(blotter.new_orders), 2) self.assertEqual(len(blotter.open_orders[self.asset_24]), 1) still_open_order = blotter.new_orders[0] self.assertEqual(still_open_order.id, second_order_id) self.assertEqual(still_open_order.status, ORDER_STATUS.OPEN) rejected_order = blotter.new_orders[1] self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED) self.assertEqual(rejected_order.reason, '') # Do it again, but reject it at a later time (after tradesimulation # pulls it from new_orders) blotter = SimulationBlotter(self.sim_params) new_open_id = blotter.order(self.asset_24, 10, MarketOrder()) new_open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(new_open_id, new_open_order.id) # Pretend that the trade simulation did this. blotter.new_orders = [] rejection_reason = "Not enough cash on hand." blotter.reject(new_open_id, reason=rejection_reason) rejected_order = blotter.new_orders[0] self.assertEqual(rejected_order.id, new_open_id) self.assertEqual(rejected_order.status, ORDER_STATUS.REJECTED) self.assertEqual(rejected_order.reason, rejection_reason) # You can't reject a filled order. # Reset for paranoia blotter = SimulationBlotter(self.sim_params) blotter.slippage_models[Equity] = FixedSlippage() filled_id = blotter.order(self.asset_24, 100, MarketOrder()) filled_order = None blotter.current_dt = self.sim_params.sessions[-1] bar_data = self.create_bardata( simulation_dt_func=lambda: self.sim_params.sessions[-1], ) txns, _, closed_orders = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] blotter.prune_orders(closed_orders) self.assertEqual(filled_order.id, filled_id) self.assertIn(filled_order, blotter.new_orders) self.assertEqual(filled_order.status, ORDER_STATUS.FILLED) self.assertNotIn(filled_order, blotter.open_orders[self.asset_24]) blotter.reject(filled_id) updated_order = blotter.orders[filled_id] self.assertEqual(updated_order.status, ORDER_STATUS.FILLED) def test_order_hold(self): """ Held orders act almost identically to open orders, except for the status indication. When a fill happens, the order should switch status to OPEN/FILLED as necessary """ blotter = SimulationBlotter(self.sim_params, equity_slippage=VolumeShareSlippage()) # Nothing happens on held of a non-existent order blotter.hold(56) self.assertEqual(blotter.new_orders, []) open_id = blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_order.id, open_id) blotter.hold(open_id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[self.asset_24]), 1) held_order = blotter.new_orders[0] self.assertEqual(held_order.status, ORDER_STATUS.HELD) self.assertEqual(held_order.reason, '') blotter.cancel(held_order.id) self.assertEqual(len(blotter.new_orders), 1) self.assertEqual(len(blotter.open_orders[self.asset_24]), 0) cancelled_order = blotter.new_orders[0] self.assertEqual(cancelled_order.id, held_order.id) self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED) for data in ([100, self.sim_params.sessions[0]], [400, self.sim_params.sessions[1]]): # Verify that incoming fills will change the order status. trade_amt = data[0] dt = data[1] order_size = 100 expected_filled = int(trade_amt * DEFAULT_EQUITY_VOLUME_SLIPPAGE_BAR_LIMIT) expected_open = order_size - expected_filled expected_status = ORDER_STATUS.OPEN if expected_open else \ ORDER_STATUS.FILLED blotter = SimulationBlotter(self.sim_params, equity_slippage=VolumeShareSlippage()) open_id = blotter.order(self.asset_24, order_size, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] self.assertEqual(open_id, open_order.id) blotter.hold(open_id) held_order = blotter.new_orders[0] filled_order = None blotter.current_dt = dt bar_data = self.create_bardata(simulation_dt_func=lambda: dt, ) txns, _, _ = blotter.get_transactions(bar_data) for txn in txns: filled_order = blotter.orders[txn.order_id] self.assertEqual(filled_order.id, held_order.id) self.assertEqual(filled_order.status, expected_status) self.assertEqual(filled_order.filled, expected_filled) self.assertEqual(filled_order.open_amount, expected_open) def test_prune_orders(self): blotter = SimulationBlotter(self.sim_params) blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] blotter.prune_orders([]) self.assertEqual(1, len(blotter.open_orders[self.asset_24])) blotter.prune_orders([open_order]) self.assertEqual(0, len(blotter.open_orders[self.asset_24])) # prune an order that isn't in our our open orders list, make sure # nothing blows up other_order = Order(dt=blotter.current_dt, asset=self.asset_25, amount=1) blotter.prune_orders([other_order]) def test_batch_order_matches_multiple_orders(self): """ Ensure the effect of order_batch is the same as multiple calls to order. """ blotter1 = SimulationBlotter(self.sim_params) blotter2 = SimulationBlotter(self.sim_params) for i in range(1, 4): order_arg_lists = [ (self.asset_24, i * 100, MarketOrder()), (self.asset_25, i * 100, LimitOrder(i * 100 + 1)), ] order_batch_ids = blotter1.batch_order(order_arg_lists) order_ids = [] for order_args in order_arg_lists: order_ids.append(blotter2.order(*order_args)) self.assertEqual(len(order_batch_ids), len(order_ids)) self.assertEqual(len(blotter1.open_orders), len(blotter2.open_orders)) for (asset, _, _), order_batch_id, order_id in zip(order_arg_lists, order_batch_ids, order_ids): self.assertEqual(len(blotter1.open_orders[asset]), len(blotter2.open_orders[asset])) self.assertEqual(order_batch_id, blotter1.open_orders[asset][i - 1].id) self.assertEqual(order_id, blotter2.open_orders[asset][i - 1].id) def test_slippage_and_commission_dispatching(self): blotter = SimulationBlotter( self.sim_params, equity_slippage=FixedSlippage(spread=0.0), future_slippage=FixedSlippage(spread=2.0), equity_commission=PerTrade(cost=1.0), future_commission=PerTrade(cost=2.0), ) blotter.order(self.asset_24, 1, MarketOrder()) blotter.order(self.future_cl, 1, MarketOrder()) bar_data = self.create_bardata( simulation_dt_func=lambda: self.sim_params.sessions[-1], ) txns, commissions, _ = blotter.get_transactions(bar_data) # The equity transaction should have the same price as its current # price because the slippage spread is zero. Its commission should be # $1.00. equity_txn = txns[0] self.assertEqual( equity_txn.price, bar_data.current(equity_txn.asset, 'price'), ) self.assertEqual(commissions[0]['cost'], 1.0) # The future transaction price should be 1.0 more than its current # price because half of the 'future_slippage' spread is added. Its # commission should be $2.00. future_txn = txns[1] self.assertEqual( future_txn.price, bar_data.current(future_txn.asset, 'price') + 1.0, ) self.assertEqual(commissions[1]['cost'], 2.0)
def test_filterBySign(self): MID_DATE_1 = pd.Timestamp('2013-01-07 17:01', tz='utc') fills_all = { 1: pd.DataFrame({ "close": [3.5, 3.5], "volume": [-3, 4], "dt": [MID_DATE_1, MID_DATE_1] }).set_index("dt"), } MID_DATE_0 = pd.Timestamp('2013-01-07 17:00', tz='utc') orders_all = { 1: { 1: { "dt": MID_DATE_0, "amount": -10, "style": MarketOrder() }, 2: { "dt": MID_DATE_0, "amount": 15, "style": MarketOrder() }, }, } act_fills_pos, act_orders_pos = mmm_Matcher.filterBySign( +1, fills_all, orders_all) act_fills_neg, act_orders_neg = mmm_Matcher.filterBySign( -1, fills_all, orders_all) exp_fills_pos = { 1: pd.DataFrame({ "close": [3.5], "volume": [4], "dt": [MID_DATE_1] }).set_index("dt"), } exp_fills_neg = { 1: pd.DataFrame({ "close": [3.5], "volume": [-3], "dt": [MID_DATE_1] }).set_index("dt"), } exp_orders_pos = { 1: { 2: { "dt": MID_DATE_0, "amount": 15, "style": orders_all[1][2]['style'] }, }, } exp_orders_neg = { 1: { 1: { "dt": MID_DATE_0, "amount": -10, "style": orders_all[1][1]['style'] }, }, } self.assertEqual(act_fills_pos[1].equals(exp_fills_pos[1]), True) self.assertEqual(act_fills_neg[1].equals(exp_fills_neg[1]), True) self.assertEqual(act_orders_pos, exp_orders_pos) self.assertEqual(act_orders_neg, exp_orders_neg)