async def place_orders(self): """ 下单 """ orders_data = [] if self.trader.position and self.trader.position.short_quantity: # 平空单 price = round(self.ask1_price - 0.1, 1) quantity = -self.trader.position.short_quantity action = ORDER_ACTION_BUY new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({"price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_LIMIT, "lever_rate": 5}) self.last_ask_price = self.ask1_price if self.trader.assets and self.trader.assets.assets.get(self.raw_symbol): # 开空单 price = round(self.bid1_price + 0.1, 1) volume = int(float(self.trader.assets.assets.get(self.raw_symbol).get("free")) * 5/ price / self.contract_size) if volume >= 1: quantity = - volume # 空1张 action = ORDER_ACTION_SELL new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({"price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_LIMIT, "lever_rate": 5}) self.last_bid_price = self.bid1_price if orders_data: order_nos, error = await self.trader.create_orders(orders_data) if error: logger.error(self.strategy, "create future order error! error:", error, caller=self) logger.info(self.strategy, "create future orders success:", order_nos, caller=self)
async def on_ticker(self, *args, **kwargs): """ 定时执行任务 """ if self.trader.assets is None: return if self.trader.position is None: return ts_diff = int(time.time() * 1000) - self.last_orderbook_timestamp if ts_diff > self.orderbook_invalid_seconds * 1000: logger.warn("received orderbook timestamp exceed:", self.strategy, self.symbol, ts_diff, caller=self) return # 获取标记价格,每次的挂单价格与指数价格进行比较。 success, error = await self.trader.rest_api.get_market_index( contract_code=self.symbol) if error: logger.error("Get swap market index faild:", error, caller=self) else: for item in success["data"]: if item["contract_code"] == self.symbol: self.mark_price = item["mark_price"] await self.cancel_orders() await self.place_orders()
async def cancel_orders(self): """ 取消订单 """ order_nos = [orderno for orderno in self.trader.orders] #if order_nos and self.last_mark_price != self.mark_price: if order_nos: _, errors = await self.trader.revoke_order(*order_nos) if errors: logger.error(self.strategy, "cancel option order error! error:", errors, caller=self) # 出错则取消所有挂单 _, errors = await self.trader.revoke_order() if errors: logger.error(self.strategy, "cancel all option orders error! error:", errors, caller=self) else: logger.info(self.strategy, "cancel all option orders success!", caller=self) else: logger.info(self.strategy, "cancel option order:", order_nos, caller=self)
def __init__(self, platform=None, symbols=None, channels=None, orderbook_length=None, orderbooks_length=None,\ klines_length=None, trades_length=None, wss=None, \ orderbook_update_callback=None, kline_update_callback=None, trade_update_callback=None, **kwargs): """initialize trade object.""" kwargs["platform"] = platform kwargs["symbols"] = symbols kwargs["channels"] = channels kwargs["orderbook_length"] = orderbook_length kwargs["orderbooks_length"] = orderbooks_length kwargs["klines_length"] = klines_length kwargs["trades_length"] = trades_length kwargs["wss"] = wss kwargs["orderbook_update_callback"] = orderbook_update_callback kwargs["kline_update_callback"] = kline_update_callback kwargs["trade_update_callback"] = trade_update_callback self._raw_params = copy.copy(kwargs) self._on_orderbook_update_callback = orderbook_update_callback self._on_kline_update_callback = kline_update_callback self._on_trade_update_callback = trade_update_callback if platform == const.HUOBI_SWAP: from huobi.platforms.huobi_swap_market import HuobiSwapMarket as M elif platform == const.HUOBI_FUTURE: from huobi.platforms.huobi_future_market import HuobiFutureMarket as M elif platform == const.HUOBI_OPTION: from huobi.platforms.huobi_option_market import HuobiOptionMarket as M elif platform == const.HUOBI_USDT_SWAP: from huobi.platforms.huobi_usdt_swap_market import HuobiUsdtSwapMarket as M elif platform == const.HUOBI_USDT_SWAP_CROSS: from huobi.platforms.huobi_usdt_swap_market import HuobiUsdtSwapMarket as M else: logger.error("platform error:", platform, caller=self) return self._m = M(**kwargs)
async def auth_callback(self, data): if data["err-code"] != 0: e = Error( "Websocket connection authorized failed: {}".format(data)) logger.error(e, caller=self) SingleTask.run(self._init_success_callback, False, e) return self._subscribe_order_ok = False self._subscribe_position_ok = False self._subscribe_asset_ok = False # subscribe order data = { "op": "sub", "cid": tools.get_uuid1(), "topic": self._order_channel } await self.ws.send_json(data) # subscribe position data = { "op": "sub", "cid": tools.get_uuid1(), "topic": self._position_channel } await self.ws.send_json(data) # subscribe asset data = { "op": "sub", "cid": tools.get_uuid1(), "topic": self._asset_channel } await self.ws.send_json(data)
async def sub_callback(self, data): if data["err-code"] != 0: e = Error("subscribe {} failed!".format(data["topic"])) logger.error(e, caller=self) SingleTask.run(self._init_success_callback, False, e) return if data["topic"] == self._order_channel: self._subscribe_order_ok = True elif data["topic"] == self._position_channel: self._subscribe_position_ok = True elif data["topic"] == self._asset_channel: self._subscribe_asset_ok = True if self._subscribe_order_ok and self._subscribe_position_ok \ and self._subscribe_asset_ok: success, error = await self._rest_api.get_open_orders(self._symbol) if error: e = Error("get open orders failed!") SingleTask.run(self._init_success_callback, False, e) elif "data" in success and "orders" in success["data"]: for order_info in success["data"]["orders"]: order_info["ts"] = order_info["created_at"] self._update_order(order_info) SingleTask.run(self._init_success_callback, True, None) else: logger.warn("get open orders:", success, caller=self) e = Error("Get Open Orders Unknown error") SingleTask.run(self._init_success_callback, False, e)
async def receive(self): """ 接收消息 """ async for msg in self.ws: if msg.type == aiohttp.WSMsgType.TEXT: try: data = json.loads(msg.data) except: data = msg.data await asyncio().create_task(self.process(data)) elif msg.type == aiohttp.WSMsgType.BINARY: await asyncio.create_task(self.process_binary(msg.data)) elif msg.type == aiohttp.WSMsgType.CLOSED: logger.warn("receive event CLOSED:", msg, caller=self) await asyncio.create_task(self._reconnect()) elif msg.type == aiohttp.WSMsgType.CLOSE: logger.warn("receive event CLOSE:", msg, caller=self) await asyncio.create_task(self._reconnect()) elif msg.type == aiohttp.WSMsgType.CLOSING: logger.warn("receive event CLOSING:", msg, caller=self) await asyncio.create_task(self._reconnect()) elif msg.type == aiohttp.WSMsgType.ERROR: logger.error("receive event ERROR:", msg, caller=self) await asyncio.create_task(self._reconnect()) else: logger.warn("unhandled msg:", msg, caller=self)
async def connected_callback(self): """ After create Websocket connection successfully, we will subscribing orderbook/trade events. """ for ch in self._channels: if ch == "kline": for symbol in self._symbols: channel = self._symbol_to_channel(symbol, "kline") if not channel: continue kline = {"sub": channel} await self.ws.send_json(kline) elif ch == "orderbook": for symbol in self._symbols: channel = self._symbol_to_channel(symbol, "depth") if not channel: continue data = {"sub": channel} await self.ws.send_json(data) elif ch == "trade": for symbol in self._symbols: channel = self._symbol_to_channel(symbol, "trade") if not channel: continue data = {"sub": channel} await self.ws.send_json(data) else: logger.error("channel error! channel:", ch, caller=self)
def __init__(self, **kwargs): """Initialize.""" e = None if not kwargs.get("account"): e = Error("param account miss") if not kwargs.get("strategy"): e = Error("param strategy miss") if not kwargs.get("symbol"): e = Error("param symbol miss") if not kwargs.get("contract_type"): e = Error("param contract_type miss") if not kwargs.get("host"): kwargs["host"] = "https://api.hbdm.com" if not kwargs.get("wss"): kwargs["wss"] = "wss://api.hbdm.com" if not kwargs.get("access_key"): e = Error("param access_key miss") if not kwargs.get("secret_key"): e = Error("param secret_key miss") if e: logger.error(e, caller=self) if kwargs.get("init_success_callback"): SingleTask.run(kwargs["init_success_callback"], False, e) return self._account = kwargs["account"] self._strategy = kwargs["strategy"] self._platform = HUOBI_USDT_SWAP_CROSS self._symbol = kwargs["symbol"] self._contract_type = kwargs["contract_type"] self._host = kwargs["host"] self._wss = kwargs["wss"] self._access_key = kwargs["access_key"] self._secret_key = kwargs["secret_key"] self._order_update_callback = kwargs.get("order_update_callback") self._position_update_callback = kwargs.get("position_update_callback") self._asset_update_callback = kwargs.get("asset_update_callback") self._init_success_callback = kwargs.get("init_success_callback") url = self._wss + "/linear-swap-notification" super(HuobiUsdtSwapCrossTrade, self).__init__(url, send_hb_interval=5) self._assets = {} # Asset detail, {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }. self._orders = {} # Order objects, {"order_id": order, ...}. self._position = Position(self._platform, self._account, self._strategy, self._symbol + '/' + self._contract_type) self._order_channel = "orders_cross.{symbol}".format(symbol=self._symbol) self._position_channel = "positions_cross.{symbol}".format(symbol=self._symbol) self._asset_channel = "accounts_cross.{symbol}".format(symbol="USDT") self._subscribe_order_ok = False self._subscribe_position_ok = False self._subscribe_asset_ok = False self._rest_api = HuobiUsdtSwapCrossRestAPI(self._access_key, self._secret_key, self._host) self.initialize()
async def cancel_orders(self): """ 取消订单 """ order_nos = [ orderno for orderno in self.trader.orders ] if order_nos and self.last_ask_price != self.ask1_price: _, errors = await self.trader.revoke_order(*order_nos) if errors: logger.error(self.strategy,"cancel future order error! error:", errors, caller=self) else: logger.info(self.strategy,"cancel future order:", order_nos, caller=self)
async def on_event_kline_update(self, kline: Kline): """ kline更新 self.market.klines 是最新的kline组成的队列,记录的是历史N次kline的数据。 本回调所传的kline是最新的单次kline。 """ logger.debug("kline update:", kline, caller=self) result = await mongo.MongoDBBase('quant', 'kline').find_one_and_update({'platform': kline.platform, 'symbol': kline.symbol, \ 'timestamp': kline.timestamp, 'kline_type': kline.kline_type}, {'$set': {'open': kline.open, 'high': kline.high, \ 'close': kline.close, 'low': kline.low, 'volume': kline.volume }}, upsert=True, return_document=True) if not result: logger.error("insert mongo error ", kline.platform + kline.symbol, kline, result) logger.debug("insert mongo success: ", result)
def __init__(self, strategy=None, platform=None, symbol=None, host=None, wss=None, account=None, access_key=None, secret_key=None, asset_update_callback=None, order_update_callback=None, position_update_callback=None, init_success_callback=None, **kwargs): """initialize trade object.""" kwargs["strategy"] = strategy kwargs["platform"] = platform kwargs["symbol"] = symbol kwargs["host"] = host kwargs["wss"] = wss kwargs["account"] = account kwargs["access_key"] = access_key kwargs["secret_key"] = secret_key kwargs["asset_update_callback"] = asset_update_callback kwargs["order_update_callback"] = self._on_order_update_callback kwargs["position_update_callback"] = self._on_position_update_callback kwargs["init_success_callback"] = self._on_init_success_callback self._raw_params = copy.copy(kwargs) self._order_update_callback = order_update_callback self._position_update_callback = position_update_callback self._init_success_callback = init_success_callback if platform == const.HUOBI_SWAP: from huobi.platforms.huobi_swap_trade import HuobiSwapTrade as T elif platform == const.HUOBI_FUTURE: from huobi.platforms.huobi_future_trade import HuobiFutureTrade as T elif platform == const.HUOBI_OPTION: from huobi.platforms.huobi_option_trade import HuobiOptionTrade as T elif platform == const.HUOBI_USDT_SWAP: from huobi.platforms.huobi_usdt_swap_trade import HuobiUsdtSwapTrade as T elif platform == const.HUOBI_USDT_SWAP_CROSS: from huobi.platforms.huobi_usdt_swap_cross_trade import HuobiUsdtSwapCrossTrade as T else: logger.error("platform error:", platform, caller=self) e = Error("platform error") SingleTask.run(self._init_success_callback, False, e) return kwargs.pop("platform") self._t = T(**kwargs)
async def _connect(self): logger.info("url:", self._url, caller=self) METHOD_LOCKERS = {} proxy = config.proxy self.session = aiohttp.ClientSession() try: self.ws = await self.session.ws_connect(self._url, proxy=proxy) self.closed = False except aiohttp.ClientConnectionError: logger.error("connect to server error! url:", self._url, caller=self) return asyncio.create_task(self.connected_callback()) asyncio.create_task(self.receive())
def _symbol_to_channel(self, symbol, channel_type): """ Convert symbol to channel. Args: symbol: Trade pair name.such as BTC-USD channel_type: channel name, kline / ticker / depth. """ if channel_type == "kline": channel = "market.{s}.kline.1min".format(s=symbol.upper()) elif channel_type == "depth": channel = "market.{s}.depth.step6".format(s=symbol.upper()) elif channel_type == "trade": channel = "market.{s}.trade.detail".format(s=symbol.upper()) else: logger.error("channel type error! channel type:", channel_type, caller=self) return None self._c_to_s[channel] = symbol return channel
async def _send_heartbeat_msg(self, *args, **kwargs): """ 发送心跳给服务器 """ if not self.ws: logger.warn("websocket connection not connected yet!", caller=self) return if self.heartbeat_msg: try: if isinstance(self.heartbeat_msg, dict): await self.ws.send_json(self.heartbeat_msg) elif isinstance(self.heartbeat_msg, str): await self.ws.send_str(self.heartbeat_msg) else: logger.error("send heartbeat msg failed! heartbeat msg:", self.heartbeat_msg, caller=self) return logger.debug("send ping message:", self.heartbeat_msg, caller=self) except ConnectionResetError: traceback.print_exc() await asyncio.create_task(self._reconnect())
async def process_binary(self, msg): """ Process binary message that received from Websocket connection. """ data = json.loads(gzip.decompress(msg).decode()) logger.debug("data:", json.dumps(data), caller=self) channel = data.get("ch") if not channel: if data.get("ping"): hb_msg = {"pong": data.get("ping")} await self.ws.send_json(hb_msg) return symbol = self._c_to_s[channel] if channel.find("kline") != -1: await self.process_kline(data) elif channel.find("depth") != -1: await self.process_orderbook(data) elif channel.find("trade") != -1: await self.process_trade(data) else: logger.error("event error! msg:", msg, caller=self)
async def delta_hedging(self, *args, **kwargs): """ delta hedge """ logger.debug("delta hedging", caller=self) option_delta = 0 assets, error = await self.trader.rest_api.get_asset_info( self.raw_symbol) if error: logger.error(self.strategy, "get option asset error! error:", error, caller=self) else: for item in assets["data"]: if item["symbol"] == self.raw_symbol: o_margin_balance = item["margin_balance"] o_delta = item["delta"] if item["delta"] else 0 o_gamma = item["gamma"] if item["gamma"] else 0 o_theta = item["theta"] if item["theta"] else 0 o_vega = item["vega"] if item["vega"] else 0 option_delta = o_delta + o_margin_balance #增加delta对冲,使用期货对冲。 accounts, error = await self.swap_trader.rest_api.get_account_position( self.swap_symbol) if error: logger.error(self.strategy, "get swap account and position error! error:", error, caller=self) else: margin_balance = accounts["data"][0]["margin_balance"] long_position = 0 short_position = 0 delta_long = 0 delta_short = 0 long_last_price = 0 short_last_price = 0 for position in accounts["data"][0]["positions"]: if position["direction"] == "buy": long_position = position["volume"] long_cost_open = position["cost_open"] long_last_price = position["last_price"] if position["direction"] == "sell": short_position = position["volume"] short_cost_open = position["cost_open"] short_last_price = position["last_price"] if long_position: delta_long = self.swap_volume_usd * int( long_position) / float(long_last_price) if short_position: delta_short = self.swap_volume_usd * int( short_position) / float(short_last_price) swap_delta = margin_balance - delta_short + delta_long t_delta = option_delta + swap_delta orders_data = [] # 对冲对应数量的币 if abs(t_delta) >= self.delta_limit: if t_delta > 0: # 开空单 price = 0 volume = int(t_delta * long_last_price / self.swap_volume_usd) if volume: quantity = -volume # action = ORDER_ACTION_SELL new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({ "price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_MARKET, "lever_rate": 10 }) else: # 开多单 price = 0 volume = abs( int(t_delta * long_last_price / self.swap_volume_usd)) if volume: quantity = volume # action = ORDER_ACTION_BUY new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({ "price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_MARKET, "lever_rate": 10 }) if orders_data: order_nos, error = await self.swap_trader.create_orders( orders_data) if error: logger.error(self.strategy, "create swap order error! error:", error, caller=self) else: logger.info(self.strategy, "create swap orders success:", order_nos, caller=self)
def __init__(self, **kwargs): """Initialize.""" e = None if not kwargs.get("account"): e = Error("param account miss") if not kwargs.get("strategy"): e = Error("param strategy miss") if not kwargs.get("symbol"): e = Error("param symbol miss") if not kwargs.get("host"): kwargs["host"] = "https://api.hbdm.com" if not kwargs.get("wss"): kwargs["wss"] = "wss://api.hbdm.com" if not kwargs.get("access_key"): e = Error("param access_key miss") if not kwargs.get("secret_key"): e = Error("param secret_key miss") if e: logger.error(e, caller=self) if kwargs.get("init_success_callback"): SingleTask.run(kwargs["init_success_callback"], False, e) return self._account = kwargs["account"] self._strategy = kwargs["strategy"] self._platform = HUOBI_OPTION self._symbol = kwargs["symbol"] self._host = kwargs["host"] self._wss = kwargs["wss"] self._access_key = kwargs["access_key"] self._secret_key = kwargs["secret_key"] self._contract_code = kwargs.get("contract_code") self._order_update_callback = kwargs.get("order_update_callback") self._position_update_callback = kwargs.get("position_update_callback") self._asset_update_callback = kwargs.get("asset_update_callback") self._init_success_callback = kwargs.get("init_success_callback") self._lock = asyncio.Lock() url = self._wss + "/option-notification" super(HuobiOptionTrade, self).__init__(url, send_hb_interval=5) # self._raw_symbol = self._symbol.split("-")[0] # self._trade_partition = self._symbol.split("-")[1] self._assets = {} # Asset detail, {"BTC": {"free": "1.1", "locked": "2.2", "total": "3.3"}, ... }. self._orders = {} # Order objects, {"order_id": order, ...}. self._position = Position(self._platform, self._account, self._strategy, self._symbol) self._order_channel = "orders.{symbol}-USDT".format(symbol=self._symbol) self._position_channel = "positions.{symbol}-USDT".format(symbol=self._symbol) self._asset_channels = ["accounts.{symbol}-USDT".format(symbol=self._symbol), "accounts.USDT-USDT".format(symbol=self._symbol)] self._subscribe_order_ok = False self._subscribe_position_ok = False self._subscribe_asset_ok = False self._rest_api = HuobiOptionRestAPI(self._access_key, self._secret_key, self._host) self.initialize()
async def fetch(cls, method, url, params=None, body=None, data=None, headers=None, timeout=30, **kwargs): """ Create a HTTP request. Args: method: HTTP request method. (GET/POST/PUT/DELETE) url: Request url. params: HTTP query params. body: HTTP request body, string or bytes format. data: HTTP request body, dict format. headers: HTTP request header. timeout: HTTP request timeout(seconds), default is 30s. kwargs: proxy: HTTP proxy. Return: code: HTTP response code. success: HTTP response data. If something wrong, this field is None. error: If something wrong, this field will holding a Error information, otherwise it's None. Raises: HTTP request exceptions or response data parse exceptions. All the exceptions will be captured and return Error information. """ # session = cls._get_session(url) async with aiohttp.ClientSession() as session: if not kwargs.get("proxy"): kwargs["proxy"] = config.proxy try: if method == "GET": response = await session.get(url, params=params, headers=headers, timeout=timeout, **kwargs) elif method == "POST": response = await session.post(url, params=params, data=body, json=data, headers=headers, timeout=timeout, **kwargs) elif method == "PUT": response = await session.put(url, params=params, data=body, json=data, headers=headers, timeout=timeout, **kwargs) elif method == "DELETE": response = await session.delete(url, params=params, data=body, json=data, headers=headers, timeout=timeout, **kwargs) else: error = "http method error!" return None, None, error except Exception as e: logger.error("method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "Error:", e, caller=cls) return None, None, e code = response.status if code not in (200, 201, 202, 203, 204, 205, 206): text = await response.text() logger.error("method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "code:", code, "result:", text, caller=cls) return code, None, text try: result = await response.json() except: result = await response.text() logger.debug("method:", method, "url:", url, "headers:", headers, "params:", params, "body:", body, "data:", data, "code:", code, "result:", json.dumps(result), caller=cls) return code, result, None
async def _send_heartbeat_msg(self, *args, **kwargs): data = {"op": "pong", "ts": str(int(time.time() * 1000))} if not self.ws: logger.error("Websocket connection not yeah!", caller=self) return await self.ws.send_json(data)
async def place_orders(self): """ 下单 """ orders_data = [] if self.trader.position and self.trader.position.short_quantity: # 平空单 price = round(self.mark_price - self.spread, 1) quantity = -self.trader.position.short_quantity action = ORDER_ACTION_BUY new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({ "price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_LIMIT }) self.last_mark_price = self.mark_price if self.trader.position and self.trader.position.long_quantity: # 平多单 price = round(self.mark_price + self.spread, 1) quantity = self.trader.position.long_quantity action = ORDER_ACTION_SELL new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({ "price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_LIMIT }) self.last_mark_price = self.mark_price if self.trader.assets and self.trader.assets.assets.get( self.raw_symbol).get("free"): # 开空单 if self.trader.position and self.trader.position.short_quantity and self.trader.position.short_quantity >= self.max_quantity: logger.warn("option short position exceeds the max quantity: ", self.symbol, self.trader.position.short_quantity, self.max_quantity, caller=self) else: price = round(self.mark_price + self.spread, 1) volume = self.volume if volume: quantity = -volume # 空张 action = ORDER_ACTION_SELL new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({ "price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_LIMIT }) self.last_mark_price = self.mark_price if self.trader.assets and self.trader.assets.assets.get( self.partition_symbol) and self.trader.assets.assets.get( self.partition_symbol).get("free"): # 开多单 if self.trader.position and self.trader.position.long_quantity and self.trader.position.long_quantity >= self.max_quantity: logger.warn("option long position exceeds the max quantity: ", self.symbol, self.trader.position.long_quantity, self.max_quantity, caller=self) else: price = round(self.mark_price - self.spread, 1) volume = self.volume if volume: quantity = volume # 多张 action = ORDER_ACTION_BUY new_price = str(price) # 将价格转换为字符串,保持精度 if quantity: orders_data.append({ "price": new_price, "quantity": quantity, "action": action, "order_type": ORDER_TYPE_LIMIT }) self.last_mark_price = self.mark_price if orders_data: order_nos, error = await self.trader.create_orders(orders_data) if error: logger.error(self.strategy, "create future order error! error:", error, caller=self) logger.info(self.strategy, "create future orders success:", order_nos, caller=self)