def test_blotter_processes_splits(self): blotter = Blotter('daily', self.env.asset_finder, slippage_func=FixedSlippage()) # set up two open limit orders with very low limit prices, # one for sid 1 and one for sid 2 blotter.order(blotter.asset_finder.retrieve_asset(1), 100, LimitOrder(10)) blotter.order(blotter.asset_finder.retrieve_asset(2), 100, LimitOrder(10)) # send in a split for sid 2 blotter.process_splits([(2, 0.3333)]) for sid in [1, 2]: order_lists = blotter.open_orders[sid] self.assertIsNotNone(order_lists) self.assertEqual(1, len(order_lists)) aapl_order = blotter.open_orders[1][0].to_dict() fls_order = blotter.open_orders[2][0].to_dict() # make sure the aapl order didn't change self.assertEqual(100, aapl_order['amount']) self.assertEqual(10, aapl_order['limit']) self.assertEqual(1, aapl_order['sid']) # make sure the fls order did change # to 300 shares at 3.33 self.assertEqual(300, fls_order['amount']) self.assertEqual(3.33, fls_order['limit']) self.assertEqual(2, fls_order['sid'])
def test_blotter_processes_splits(self): blotter = Blotter('daily', equity_slippage=FixedSlippage()) # set up two open limit orders with very low limit prices, # one for sid 1 and one for sid 2 asset1 = self.asset_finder.retrieve_asset(1) asset2 = self.asset_finder.retrieve_asset(2) asset133 = self.asset_finder.retrieve_asset(133) blotter.order(asset1, 100, LimitOrder(10)) blotter.order(asset2, 100, LimitOrder(10)) # send in splits for assets 133 and 2. We have no open orders for # 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.assertEqual(1, len(order_lists)) asset1_order = blotter.open_orders[1][0] asset2_order = blotter.open_orders[2][0] # make sure the asset1 order didn't change self.assertEqual(100, asset1_order.amount) self.assertEqual(10, asset1_order.limit) self.assertEqual(1, asset1_order.asset) # make sure the asset2 order did change # to 300 shares at 3.33 self.assertEqual(300, asset2_order.amount) self.assertEqual(3.33, asset2_order.limit) self.assertEqual(2, asset2_order.asset)
def test_blotter_processes_splits(self): blotter = SimulationBlotter(equity_slippage=FixedSlippage()) # set up two open limit orders with very low limit prices, # one for sid 1 and one for sid 2 asset1 = self.asset_finder.retrieve_asset(1) asset2 = self.asset_finder.retrieve_asset(2) asset133 = self.asset_finder.retrieve_asset(133) blotter.order(asset1, 100, LimitOrder(10, asset=asset1)) blotter.order(asset2, 100, LimitOrder(10, asset=asset2)) # send in splits for assets 133 and 2. We have no open orders for # 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] assert order_lists is not None assert 1 == len(order_lists) asset1_order = blotter.open_orders[1][0] asset2_order = blotter.open_orders[2][0] # make sure the asset1 order didn't change assert 100 == asset1_order.amount assert 10 == asset1_order.limit assert 1 == asset1_order.asset # make sure the asset2 order did change # to 300 shares at 3.33 assert 300 == asset2_order.amount assert 3.33 == asset2_order.limit assert 2 == asset2_order.asset
def test_blotter_processes_splits(self): sim_params = factory.create_simulation_parameters() blotter = Blotter() blotter.set_date(sim_params.period_start) # set up two open limit orders with very low limit prices, # one for sid 1 and one for sid 2 blotter.order(1, 100, LimitOrder(10)) blotter.order(2, 100, LimitOrder(10)) # send in a split for sid 2 split_event = factory.create_split( 2, 0.33333, sim_params.period_start + timedelta(days=1)) blotter.process_split(split_event) for sid in [1, 2]: order_lists = blotter.open_orders[sid] self.assertIsNotNone(order_lists) self.assertEqual(1, len(order_lists)) aapl_order = blotter.open_orders[1][0].to_dict() fls_order = blotter.open_orders[2][0].to_dict() # make sure the aapl order didn't change self.assertEqual(100, aapl_order['amount']) self.assertEqual(10, aapl_order['limit']) self.assertEqual(1, aapl_order['sid']) # make sure the fls order did change # to 300 shares at 3.33 self.assertEqual(300, fls_order['amount']) self.assertEqual(3.33, fls_order['limit']) self.assertEqual(2, fls_order['sid'])
def handle_data(self, data): sid, toptid, tid = self.sids[0], self.tops[self.tids[0]], self.tids[0] date = data[sid].datetime try: self.window.append( (getattr(data[sid], "top%d_buyvolume" % (toptid)), getattr(data[sid], "top%d_sellvolume" % (toptid)), getattr(data[sid], "top%d_avgbuyprice" % (toptid)), getattr(data[sid], "top%d_avgsellprice" % (toptid)), getattr(data[sid], "top%d_ratio" % (toptid)), data[sid].open, data[sid].high, data[sid].low, data[sid].close, data[sid].volume)) except: if self._debug: print "%s: traderid(%s) not found in stockid(%s)" % (date, tid, sid) pass if len(self.window) == self._cfg['buf_win']: buyvolume, sellvolume, avgbuyprice, avgsellprice, ratio, open, high, low, close, volume = [ np.array(i) for i in zip(*self.window) ] self.buy = False self.sell = False if buyvolume[-1]: self.order_target_percent(sid, buyvolume[-1], style=LimitOrder(avgbuyprice[-1])) self.buy = True if sellvolume[-1]: self.order_target_percent(sid, -sellvolume[-1], style=LimitOrder(avgsellprice[-1])) self.sell = True sideband = { "top%d_%s_buyvolume" % (toptid, tid): buyvolume[-1], "top%d_%s_sellvolume" % (toptid, tid): sellvolume[-1], "top%d_%s_avgbuyprice" % (toptid, tid): avgbuyprice[-1], "top%d_%s_avgsellprice" % (toptid, tid): avgsellprice[-1] } # save to recorder signals = { 'open': open[-1], 'high': high[-1], 'low': low[-1], 'close': close[-1], 'volume': volume[-1], 'buy': self.buy, 'sell': self.sell, } signals.update(sideband) self.record(**signals)
def add_order(order): if order.order_status != OPEN: logger.debug("Not adding non-open order %s" % order) # check if already added before and status changed ZlModel.delete_order(order, True) return ZlModel.add_asset(order.asset) logger.debug("Add order %s" % order) if order.asset.id not in ZlModel.orders: ZlModel.orders[order.asset.id] = {} style = None if order.order_type == LIMIT: style = LimitOrder(order.limit_price) else: if order.order_type == MARKET: style = MarketOrder() else: raise ValueError('Unsupported order type: %s' % order.order_type) ZlModel.orders[order.asset.id][order.id] = { "dt": order.pub_date, "asset": order.asset.id, "amount": order.order_qty_signed(), "style": style, "validity": { "type": ZlModel.validity_django2zipline(order.order_validity), "date": order.validity_date } }
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.asset, 10, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order(self.asset, 10, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_value(self.asset, 300, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_value(self.asset, 300, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_percent(self.asset, .1, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_percent(self.asset, .1, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target(self.asset, 100, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target(self.asset, 100, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_value(self.asset, 100, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_value(self.asset, 100, stop_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_percent(self.asset, .2, limit_price=10, style=style) with assert_raises(UnsupportedOrderParameters): order_target_percent(self.asset, .2, stop_price=10, style=style)
def handle_data(context, data): # print today's summary with orders placed before BackTestUtility.print_daily_summary(context) # analysis based today's market and place orders to execute tomorrow stock_list = list(context.screen_assets.keys()) # place or adjust sell orders for current position for position in context.portfolio.positions.values(): place_sell_orders(context, data, position) stock_list.remove(position.asset.symbol) # check if it is in the ramp down stage if len(stock_list) > 0 and not BackTestUtility.is_ramp_down(context) and \ context.portfolio.cash > context.fixed_buying_amount: available_cash = context.portfolio.cash # new buyings buyings = context.market.run_strategies( stock_list=stock_list, target_date=context.datetime.replace(tzinfo=None).date()) if 'OverReactStrategy' in buyings.keys(): for stock in buyings['OverReactStrategy'].itertuples(): asset = context.screen_assets[stock.symbol] if data.can_trade(asset): BackTestUtility.cleanup_open_orders(asset) order_target_value(asset, context.fixed_buying_amount, style=LimitOrder( data.current(asset, 'close'))) available_cash -= context.fixed_buying_amount if available_cash < context.fixed_buying_amount: break
def test_limit_order_prices(self, price, expected_limit_buy_or_stop_sell, expected_limit_sell_or_stop_buy): """ Test price getters for the LimitOrder class. """ style = LimitOrder(price) self.assertEqual(expected_limit_buy_or_stop_sell, style.get_limit_price(True)) self.assertEqual(expected_limit_sell_or_stop_buy, style.get_limit_price(False)) self.assertEqual(None, style.get_stop_price(True)) self.assertEqual(None, style.get_stop_price(False))
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 handle_data(algo, data): if algo.minute == 0: # Should be filled by the next minute algo.order(1, 1) # Won't be filled because the price is too low. algo.order(2, 1, style=LimitOrder(0.01)) algo.order(2, 1, style=LimitOrder(0.01)) algo.order(2, 1, style=LimitOrder(0.01)) all_orders = algo.get_open_orders() self.assertEqual(list(all_orders.keys()), [1, 2]) self.assertEqual(all_orders[1], algo.get_open_orders(1)) self.assertEqual(len(all_orders[1]), 1) self.assertEqual(all_orders[2], algo.get_open_orders(2)) self.assertEqual(len(all_orders[2]), 3) if algo.minute == 1: # First order should have filled. # Second order should still be open. all_orders = algo.get_open_orders() self.assertEqual(list(all_orders.keys()), [2]) self.assertEqual([], algo.get_open_orders(1)) orders_2 = algo.get_open_orders(2) self.assertEqual(all_orders[2], orders_2) self.assertEqual(len(all_orders[2]), 3) for order in orders_2: algo.cancel_order(order) all_orders = algo.get_open_orders() self.assertEqual(all_orders, {}) algo.minute += 1
def test_invalid_prices(self, price): """ Test that execution styles throw appropriate exceptions upon receipt of an invalid price field. """ with self.assertRaises(BadOrderParameters): LimitOrder(price) with self.assertRaises(BadOrderParameters): StopOrder(price) for lmt, stp in [(price, 1), (1, price), (price, price)]: with self.assertRaises(BadOrderParameters): StopLimitOrder(lmt, stp)
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 __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()
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 test_limit_order_prices(self, price, expected_limit_buy_or_stop_sell, expected_limit_sell_or_stop_buy, asset): """ Test price getters for the LimitOrder class. """ style = LimitOrder(price, asset=self.asset_finder.retrieve_asset(asset)) assert_equal(expected_limit_buy_or_stop_sell, style.get_limit_price(is_buy=True)) assert_equal(expected_limit_sell_or_stop_buy, style.get_limit_price(is_buy=False)) assert_equal(None, style.get_stop_price(_is_buy=True)) assert_equal(None, style.get_stop_price(_is_buy=False))
def open(self): """ Open long positions """ securities = self.securities.difference(self.context.portfolio.positions.keys()) securities = self.filter_securities(securities, self.data) if securities: log.debug("Up-trend stocks: " + ", ".join([security_.symbol for security_ in securities])) for security in securities: if security in self.context.portfolio.positions.keys(): log.debug("Already have position in " + str(security)) continue price = self.data.history(security, "low", intraday_trend_fast, intraday_frequency).as_matrix().min() + cents_to_market if price * position_amount > self.context.portfolio.cash: log.debug("Not enough cash to open position in " + str(security)) continue order_target(security, position_amount, style=LimitOrder(price)) log.info("Buying " + str(security.symbol) + ", price =" + str(price))
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)
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)
class BlotterTestCase(TestCase): @classmethod def setUpClass(cls): cls.env = trading.TradingEnvironment() cls.env.write_data(equities_identifiers=[24]) @classmethod def tearDownClass(cls): del cls.env def setUp(self, env=None): 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 test_order_rejection(self): blotter = Blotter() # 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(24, 100, MarketOrder()) second_order_id = blotter.order(24, 50, MarketOrder()) self.assertEqual(len(blotter.open_orders[24]), 2) open_order = blotter.open_orders[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[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() new_open_id = blotter.order(24, 10, MarketOrder()) new_open_order = blotter.open_orders[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. blotter = Blotter() # Reset for paranoia blotter.current_dt = datetime.datetime.now() filled_id = blotter.order(24, 100, MarketOrder()) aapl_trade = create_trade(24, 50.0, 400, datetime.datetime.now()) filled_order = None for txn, updated_order in blotter.process_trade(aapl_trade): filled_order = updated_order 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[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() # Nothing happens on held of a non-existent order blotter.hold(56) self.assertEqual(blotter.new_orders, []) open_id = blotter.order(24, 100, MarketOrder()) open_order = blotter.open_orders[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[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[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 trade_amt in (100, 400): # Verify that incoming fills will change the order status. order_size = 100 expected_filled = trade_amt * 0.25 expected_open = order_size - expected_filled expected_status = ORDER_STATUS.OPEN if expected_open else \ ORDER_STATUS.FILLED blotter = Blotter() blotter.current_dt = datetime.datetime.now() open_id = blotter.order(24, order_size, MarketOrder()) open_order = blotter.open_orders[24][0] self.assertEqual(open_id, open_order.id) blotter.hold(open_id) held_order = blotter.new_orders[0] aapl_trade = create_trade(24, 50.0, trade_amt, datetime.datetime.now()) filled_order = None for txn, updated_order in blotter.process_trade(aapl_trade): filled_order = updated_order 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(TestCase): @classmethod def setUpClass(cls): setup_logger(cls) cls.env = trading.TradingEnvironment() cls.sim_params = factory.create_simulation_parameters( start=pd.Timestamp("2006-01-05", tz='UTC'), end=pd.Timestamp("2006-01-06", tz='UTC')) cls.env.write_data( equities_data={ 24: { 'start_date': cls.sim_params.trading_days[0], 'end_date': cls.env.next_trading_day(cls.sim_params.trading_days[-1]) }, 25: { 'start_date': cls.sim_params.trading_days[0], 'end_date': cls.env.next_trading_day(cls.sim_params.trading_days[-1]) } }) cls.tempdir = TempDirectory() assets = { 24: pd.DataFrame({ "open": [50, 50], "high": [50, 50], "low": [50, 50], "close": [50, 50], "volume": [100, 400], "day": [day.value for day in cls.sim_params.trading_days] }), 25: pd.DataFrame({ "open": [50, 50], "high": [50, 50], "low": [50, 50], "close": [50, 50], "volume": [100, 400], "day": [day.value for day in cls.sim_params.trading_days] }) } path = os.path.join(cls.tempdir.path, "tempdata.bcolz") DailyBarWriterFromDataFrames(assets).write(path, cls.sim_params.trading_days, assets) equity_daily_reader = BcolzDailyBarReader(path) cls.data_portal = DataPortal( cls.env, equity_daily_reader=equity_daily_reader, ) @classmethod def tearDownClass(cls): del cls.env cls.tempdir.cleanup() teardown_logger(cls) @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)
amount=order.amount, filled=order.filled, stop=None, limit=order.price, # TODO 市价单和限价单 id=zp_order_id, ) od.broker_order_id = order.order_id od.status = zp_status return od if __name__ == "__main__": cats_trader = CatsTrade() broker = TdxCatseBroker(cats_client=cats_trader) print(broker.orders) print(broker.positions) print(broker.portfolio) Asset = namedtuple("Assert",["symbol"]) broker.order(Asset(symbol = "000002"), 2100, LimitOrder(limit_price=38)) print(broker.orders) print(broker.positions) broker.order(Asset(symbol = "000002"), 2500, LimitOrder(limit_price=35)) print(broker.orders) print(broker.positions) orders = broker.orders for k, v in orders.items(): broker.cancel_order(v.order_id) print(broker.orders)
class BlotterTestCase(WithCreateBarData, 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 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", "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() blotter.order(self.asset_24, 100, style_obj) result = blotter.open_orders[self.asset_24][0] assert result.limit == expected_lmt assert result.stop == expected_stp def test_cancel(self): blotter = SimulationBlotter() 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()) assert len(blotter.open_orders) == 2 assert len(blotter.open_orders[self.asset_24]) == 3 assert [o.amount for o in blotter.open_orders[self.asset_24] ] == [100, 200, 300] blotter.cancel(oid_2) assert len(blotter.open_orders) == 2 assert len(blotter.open_orders[self.asset_24]) == 2 assert [o.amount for o in blotter.open_orders[self.asset_24]] == [100, 300] assert [o.id for o in blotter.open_orders[self.asset_24]] == [oid_1, oid_3] blotter.cancel_all_orders_for_asset(self.asset_24) assert len(blotter.open_orders) == 1 assert list(blotter.open_orders) == [self.asset_25] 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()) assert len(blotter.new_orders) == 2 order_ids = [order.id for order in blotter.open_orders[self.asset_24]] assert blotter.new_orders[0].status == ORDER_STATUS.OPEN assert blotter.new_orders[1].status == ORDER_STATUS.OPEN blotter.execute_cancel_policy(BAR) assert blotter.new_orders[0].status == ORDER_STATUS.OPEN assert 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] assert order.status == ORDER_STATUS.CANCELLED 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 test_order_rejection(self): blotter = SimulationBlotter() # Reject a nonexistent order -> no order appears in new_order, # no exceptions raised out blotter.reject(56) assert 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()) assert len(blotter.open_orders[self.asset_24]) == 2 open_order = blotter.open_orders[self.asset_24][0] assert open_order.status == ORDER_STATUS.OPEN assert open_order.id == open_order_id assert open_order in blotter.new_orders # Reject that order immediately (same bar, i.e. still in new_orders) blotter.reject(open_order_id) assert len(blotter.new_orders) == 2 assert len(blotter.open_orders[self.asset_24]) == 1 still_open_order = blotter.new_orders[0] assert still_open_order.id == second_order_id assert still_open_order.status == ORDER_STATUS.OPEN rejected_order = blotter.new_orders[1] assert rejected_order.status == ORDER_STATUS.REJECTED assert rejected_order.reason == "" # Do it again, but reject it at a later time (after tradesimulation # pulls it from new_orders) blotter = SimulationBlotter() new_open_id = blotter.order(self.asset_24, 10, MarketOrder()) new_open_order = blotter.open_orders[self.asset_24][0] assert 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] assert rejected_order.id == new_open_id assert rejected_order.status == ORDER_STATUS.REJECTED assert rejected_order.reason == rejection_reason # You can't reject a filled order. # Reset for paranoia blotter = SimulationBlotter() 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) assert filled_order.id == filled_id assert filled_order in blotter.new_orders assert filled_order.status == ORDER_STATUS.FILLED assert filled_order not in blotter.open_orders[self.asset_24] blotter.reject(filled_id) updated_order = blotter.orders[filled_id] assert 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(equity_slippage=VolumeShareSlippage()) # Nothing happens on held of a non-existent order blotter.hold(56) assert blotter.new_orders == [] open_id = blotter.order(self.asset_24, 100, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] assert open_order.id == open_id blotter.hold(open_id) assert len(blotter.new_orders) == 1 assert len(blotter.open_orders[self.asset_24]) == 1 held_order = blotter.new_orders[0] assert held_order.status == ORDER_STATUS.HELD assert held_order.reason == "" blotter.cancel(held_order.id) assert len(blotter.new_orders) == 1 assert len(blotter.open_orders[self.asset_24]) == 0 cancelled_order = blotter.new_orders[0] assert cancelled_order.id == held_order.id assert 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(equity_slippage=VolumeShareSlippage()) open_id = blotter.order(self.asset_24, order_size, MarketOrder()) open_order = blotter.open_orders[self.asset_24][0] assert 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] assert filled_order.id == held_order.id assert filled_order.status == expected_status assert filled_order.filled == expected_filled assert filled_order.open_amount == expected_open 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([]) assert 1 == len(blotter.open_orders[self.asset_24]) blotter.prune_orders([open_order]) assert 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() blotter2 = SimulationBlotter() 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)) assert len(order_batch_ids) == len(order_ids) assert 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): assert len(blotter1.open_orders[asset]) == len( blotter2.open_orders[asset]) assert order_batch_id == blotter1.open_orders[asset][i - 1].id assert order_id == blotter2.open_orders[asset][i - 1].id def test_slippage_and_commission_dispatching(self): blotter = SimulationBlotter( 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] assert equity_txn.price == bar_data.current(equity_txn.asset, "price") assert 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] assert future_txn.price == bar_data.current(future_txn.asset, "price") + 1.0 assert commissions[1]["cost"] == 2.0
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)
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)