def _match_order(self, order: OrderData, buy_cross_price, sell_cross_price, buy_best_price, sell_best_price): direction = order.direction buy_cross = (direction == EnumOrderDirection.BUY and order.price >= buy_cross_price) sell_cross = (direction == EnumOrderDirection.SELL and order.price <= sell_cross_price) if buy_cross or sell_cross or order.order_type == EnumOrderType.MARKET: order.status = EnumOrderStatus.FILLED order.executed_volume = order.volume order.order_id = generate_id() trade = TradeData.from_order(order) trade.trade_id = generate_id() if direction == EnumOrderDirection.BUY: trade.price = min(order.price, buy_best_price) else: trade.price = max(order.price, sell_best_price) trade.commission = trade.price * trade.volume * self.fee_rate trade.commission_asset = trade.contract.asset_quote logger.debug('trade matched\n%s', trade.pretty_string()) self.callback(trade) self.callback(order) return order.client_order_id return ''
def match_order(self, order: OrderData): logger.debug(f'{self} got order {order}') if EnumOrderStatus.NEW == order.status: order.status = EnumOrderStatus.FILLED order.executed_volume = order.volume order.order_id = generate_id() trade = TradeData.from_order(order) trade.trade_id = generate_id() if trade.direction == EnumOrderDirection.BUY: trade.price = trade.price * (1 + self.slippage) else: trade.price = trade.price * (1 - self.slippage) trade.commission_asset = order.contract.asset_quote trade.commission = trade.price * trade.volume * self.fee_rate self.callback(trade) self.callback(order) elif EnumOrderStatus.CANCELLING == order.status: order.status = EnumOrderStatus.CANCELLED self.callback(order)
def contract_check(self): contract = self.contract volume = self.volume = self.floor(self.volume, contract.lot_size) price = self.price = self.round(self.price, contract.tick_size) if contract.contract_type == EnumContractType.SPOT: notional = abs(volume) * price else: notional = abs(volume) if abs(volume) < contract.min_quantity: logger.debug(f'Order volume {volume} not reach the min requirement {contract.min_quantity}') return False if notional < contract.min_notional: logger.debug(f'Order notional {notional} not reach the min requirement {contract.min_notional}') return False if abs(volume) > contract.max_quantity: logger.debug(f'Order volume {volume} break the max requirement {contract.max_quantity}') return False if notional > contract.max_notional: logger.debug(f'Order notional {notional} break the max requirement {contract.max_notional}') return False if price < contract.min_price: logger.debug(f'Order price {price} not reach the min requirement {contract.min_price}') return False return True
def create_order(self, order: OrderData, params: dict = None): self.throttle() self._local_order_manager.on_order(order) order = copy.copy(order) contract = self._contracts[order.symbol] symbol_root = contract.symbol_root if params is None: params = {} try: price = order.price if order.order_type == EnumOrderType.MARKET: price = None logger.debug('%s sending order %s', self, order.client_order_id) data = self._exchange.create_order(symbol_root, self.ORDER_TYPE_MAP[order.order_type], self.DIRECTION_MAP[order.direction], order.volume, price, params) if 'status' in data: status = self.STATUS_MAP_REVERSE[data['status']] else: status = EnumOrderStatus.PENDING order.status = status order.order_id = data['id'] logger.debug('%s sent order %s successfully with order id %s and status %s ', self, order.client_order_id, order.order_id, order.status) except ccxt.InsufficientFunds as e: order.status = EnumOrderStatus.REJECTED balance_df = self.fetch_balance() base_amount = balance_df.loc[contract.symbol_base] quote_amount = balance_df.loc[contract.symbol_quote] logger.warn( "Insufficient balance for order: %s\n%s\n%s", e.args, order, f'There was only {contract.symbol_base} \n' f'{base_amount} \n' f'and {quote_amount} \n' f'{contract.symbol_quote} \n' f'in the balance. ' ) except ccxt.InvalidOrder as e: order.status = EnumOrderStatus.REJECTED logger.warn("InvalidOrder error: %s\n%s", e.args, order) except ccxt.ExchangeError as e: logger.warn("Exchange error: %s\n%s", e.args, order) order.status = EnumOrderStatus.REJECTED except ccxt.NetworkError as e: logger.warn("Network error: %s\n%s", e.args, order) order.status = EnumOrderStatus.ERROR except requests.HTTPError as e: logger.warn("Http error: %s\n%s", e.args, order) order.status = EnumOrderStatus.ERROR self._local_order_manager.on_order(order) self.on_order(order) return order
def _process_order_request(self, order: OrderData): exchange = order.contract.exchange if exchange in self._gateway_dict: gateway = self._gateway_dict[exchange] logger.debug('%s send %s', gateway, order) gateway.send_order(order) else: logger.info("No such %s gateway exist", exchange)
def fetch_bars(self, symbol, freq, start_date, end_date=None, return_df=False): symbol_root = self.contracts[symbol].symbol_root if end_date is None: now = datetime.datetime.utcnow() end_date = start_date + TIME_INTERVAL_MAP[freq] * (self.N_LIMIT_BAR - 1) end_date = min(end_date, now - TIME_INTERVAL_MAP[freq]) start_timestamp = int(calendar.timegm(start_date.timetuple()) * 1000) end_timestamp = int(calendar.timegm(end_date.timetuple()) * 1000) time_delta = int(TIME_INTERVAL_MAP[freq].total_seconds() * 1000) ohlcv_list = [] while start_timestamp <= end_timestamp: self.throttle() ohlcv = self._exchange.fetch_ohlcv(symbol_root, freq, start_timestamp, self.N_LIMIT_BAR) if ohlcv is not None and len(ohlcv): ohlcv_list.extend(ohlcv) actual_last_timestamp = ohlcv[-1][0] logger.debug('download {symbol}:{freq} from [{start} ~~ {end}]'.format( symbol=symbol, freq=freq, start=datetime.datetime.fromtimestamp(start_timestamp / 1000), end=datetime.datetime.fromtimestamp(actual_last_timestamp / 1000), )) expect_last_timestamp = start_timestamp + (self.N_LIMIT_BAR - 1) * time_delta next_timestamp = max(actual_last_timestamp, expect_last_timestamp) + time_delta start_timestamp = next_timestamp else: break # transform into data frame: # 1. check data duplication # 2. constrain data in giver date range # 3. timestamp -> datetime bar_df = pd.DataFrame(ohlcv_list, columns=['datetime', 'open', 'high', 'low', 'close', 'volume']) bar_list = [] if len(bar_df): non_duplicate_index = ~bar_df['datetime'].duplicated() date_range_index = bar_df['datetime'] <= end_timestamp filter_index = non_duplicate_index & date_range_index bar_df = bar_df[filter_index] bar_df['datetime'] = pd.to_datetime(bar_df['datetime'], unit='ms') bar_df['frequency'] = freq bar_df['symbol'] = symbol if return_df: return bar_df ohlcv_records = bar_df.to_dict('records') for record in ohlcv_records: bar = BarData.from_dict(record) bar_list.append(bar) return bar_list
def match_order(self, order: OrderData): if EnumOrderStatus.NEW == order.status: self._order_container_dict[order.symbol].add(order) order.status = EnumOrderStatus.PENDING order.order_id = generate_id() self.callback(order) elif EnumOrderStatus.CANCELLING == order.status: self._order_container_dict[order.symbol].remove_by_id( order.client_order_id) order.status = EnumOrderStatus.CANCELLED self.callback(order) logger.debug('order %s cancelled', order.client_order_id)
def fetch_order_status(self, order: OrderData): self.throttle() logger.debug('fetch status for order %s', order) symbol = order.symbol symbol_root = self.contracts[symbol].symbol_root order_id = order.order_id data = self._exchange.fetch_order(order_id, symbol_root) order_status = copy.copy(order) order_status.status = self.STATUS_MAP_REVERSE[data['status']] order_status.executed_volume = data['filled'] return order_status
def on_packet(self, packet: dict): if 'type' in packet: name = packet['type'] if name in self._callback_dict: callback = self._callback_dict[name] callback(packet) elif name == 'ping': pass else: logger.debug( "%s received data without corresponding callback: %s", self, packet) else: logger.debug("%s received other data: %s", self, packet)
def _save(self, base_data: BaseData): if isinstance(base_data, OrderData): self.save_order(base_data) elif isinstance(base_data, TradeData): self.save_trade(base_data) elif isinstance(base_data, PositionData): self.save_position(base_data) elif isinstance(base_data, BalanceData): self.save_balance(base_data) elif isinstance(base_data, PnLData): self.save_pnl(base_data) else: raise TypeError( 'Not an expect data to store, got {}'.format(base_data)) logger.debug('save {} to database'.format(base_data))
def on_order_status(self, order: OrderData): client_order_id = order.client_order_id if order.is_closed(): if client_order_id in self._order_frozen_dict: asset, amount = self._order_frozen_dict[client_order_id] balance = self._get_balance(asset) balance.frozen_amount -= amount del self._order_frozen_dict[client_order_id] logger.debug('%s unfreeze %.6f amount of %s by order %s', self, amount, asset, order.client_order_id) else: if client_order_id not in self._order_frozen_dict: symbol = order.symbol order_type = order.order_type if order_type in ORIGIN_ORDER_TYPES: asset, amount = self._get_position(symbol).frozen_by_order( order) balance = self._get_balance(asset) balance.frozen_amount += amount self._order_frozen_dict[order.client_order_id] = (asset, amount) logger.debug('%s freeze %.6f amount of %s by order %s', self, amount, asset, order.client_order_id)
def cancel_order(self, order_cancel: OrderData): self.throttle() self._local_order_manager.on_order(order_cancel) order_id = order_cancel.order_id contract = self._contracts[order_cancel.symbol] symbol_root = contract.symbol_root logger.debug('%s cancel order %s', self, order_cancel.client_order_id) try: self._exchange.cancel_order(order_id, symbol_root) logger.debug('%s canceled order %s successfully', self, order_cancel.client_order_id) order_cancel.status = EnumOrderStatus.CANCELLED except ccxt.errors.OrderNotFound as e: logger.info("Order already been closed or cancelled before: %s\n%s", e.args, order_cancel) order_status = self.fetch_order_status(order_cancel) order_cancel.on_order(order_status) except ccxt.errors.NetworkError as e: logger.info("Network error: %s\n%s", e.args, order_cancel) order_cancel.status = EnumOrderStatus.CANCEL_ERROR self._local_order_manager.on_order(order_cancel) self.on_order(order_cancel) return order_cancel
def on_user_stream(self, data): if data['e'] == 'executionReport': logger.debug(f'{self} received raw data {data}') if data['C'] != 'null': cli_id = data['C'] else: cli_id = data['c'] order = self.api.oms.clone(cli_id) if order is None: return order.order_id = str(data['i']) order.executed_volume = float(data['z']) order.executed_notional = float(data['Z']) order.status = STATUS_MAP_REVERSE[data['X']] if float(data['l']): trade = TradeData.from_order(order) trade.trade_id = str(data['t']) trade.price = float(data['L']) trade.volume = float(data['l']) trade.commission = float(data['n']) trade.commission_asset = '.'.join((data['N'], self.TAG)) trade.datetime = datetime.datetime.utcfromtimestamp(data['E'] / 1000.0) trade.strategy_id = order.strategy_id trade.client_order_id = order.client_order_id contract = trade.contract if trade.commission != 0 and trade.commission_asset == 'BNB.BNC': if (trade.commission_asset != contract.asset_base) and \ (trade.commission_asset != contract.asset_quote): commission_trade = trade.copy() commission_trade.commission = 0.0 commission_trade.symbol = 'BNB' + contract.symbol_quote + '.BNC' commission_trade.direction = EnumOrderDirection.SELL notional = trade.volume * trade.price * 0.00075 commission_trade.price = notional / trade.commission commission_trade.volume = notional / commission_trade.price commission_trade.trade_id = generate_id() self.api.on_trade(commission_trade) trade.commission_asset = contract.asset_quote trade.commission = notional self.api.on_trade(trade) logger.debug("%s: receive %s", self, trade) self.api.on_order(order) if order.is_closed(): self.api.oms.pop(order.client_order_id) logger.debug("%s: receive %s", self, order)
def cancel_order(self, client_order_id): if not self._trading_mode: logger.info( 'Cancel request not send, Reason: %s is in trading state %s', self, self._trading_mode) return if client_order_id not in self._working_order_dict: logger.debug( 'Cancel request not send, Reason: order %s is not in working order list', client_order_id) return working_order = self._working_order_dict[client_order_id] if working_order.is_closed(): logger.info( 'Cancel request not send, Reason: order %s is already finished with status %s', working_order.client_order_id, working_order.status) return if working_order.status == EnumOrderStatus.CANCELLING: logger.debug( 'Cancel request not send, Reason: order %s has been sent before', client_order_id) return if not working_order.order_id: logger.info( 'Cancel request not send, Reason: order %s is not alive in exchange yet (no order id)', client_order_id) return cancel_req = copy.copy(working_order) cancel_req.status = EnumOrderStatus.CANCELLING logger.debug('%s cancel order with client order id %s', self, client_order_id) self.broker.send_order(cancel_req)
def send_order(self, order: OrderData): logger.debug(f'{self} plan to send {order.pretty_string()}') self._send_order_impl(copy.copy(order))
def on_trade(self, trade: TradeData): super(AlgorithmTemplate, self).on_trade(trade) logger.debug(f'{self} receive new execution volume {trade.volume}') trade_notional = trade.volume * trade.price self.target_order.executed_volume += trade.volume self.target_order.executed_notional += trade_notional
def add(self, order: OrderData): if order.client_order_id not in self._order_dict: self._count += 1 self._order_dict[order.client_order_id] = order logger.debug('order %s created in matcher', order.client_order_id)