def exec_indmargin_order(trader, order, market, open_exec_price, close_exec_price): """ Execute the order for indivisible margin position. @todo update to support only indivisible margin order """ current_position = None positions = [] trader.lock() if order.symbol: # in that case position is identifier by its market current_position = trader._positions.get(order.symbol) if current_position and current_position.is_opened(): # increase or reduce the current position org_quantity = current_position.quantity exec_price = 0.0 # # and adjust the position quantity (no possible hedging) # # price difference depending of the direction delta_price = 0 if current_position.direction == Position.LONG: delta_price = close_exec_price - current_position.entry_price # logger.debug("cl", delta_price, " use ", close_exec_price, " other ", open_exec_price, close_exec_price < open_exec_price) elif current_position.direction == Position.SHORT: delta_price = current_position.entry_price - close_exec_price # logger.debug("cs", delta_price, " use ", close_exec_price, " other ", open_exec_price, close_exec_price < open_exec_price) # keep for percent calculation prev_entry_price = current_position.entry_price or close_exec_price leverage = order.leverage # most of thoose data rarely change except the base_exchange_rate value_per_pip = market.value_per_pip contract_size = market.contract_size lot_size = market.lot_size one_pip_means = market.one_pip_means base_exchange_rate = market.base_exchange_rate margin_factor = market.margin_factor # logger.debug(order.symbol, bid_price, ofr_price, open_exec_price, close_exec_price, delta_price, current_position.entry_price, order.price) realized_position_cost = 0.0 # realized cost of the position in base currency # effective meaning of delta price in base currency effective_price = (delta_price / one_pip_means) * value_per_pip # in base currency position_gain_loss = 0.0 if order.direction == current_position.direction: # first, same direction, increase the position # it's what we have really buy realized_position_cost = order.quantity * (lot_size * contract_size ) # in base currency # check available margin margin_cost = realized_position_cost * margin_factor / base_exchange_rate if not trader.has_margin(margin_cost): # and then rejected order trader.unlock() trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_REJECTED, trader.name, (order.symbol, order.ref_order_id)) logger.error( "Not enought free margin for %s need %s but have %s!" % (order.symbol, margin_cost, trader.account.margin_balance)) return False # still in long, position size increase and adjust the entry price entry_price = ( (current_position.entry_price * current_position.quantity) + (open_exec_price * order.quantity)) / 2 current_position.entry_price = entry_price current_position.quantity += order.quantity # directly executed quantity order.executed = order.quantity exec_price = open_exec_price # increase used margin trader.account.add_used_margin(margin_cost) else: # different direction if current_position.quantity > order.quantity: # first case the direction still the same, reduce the position and the margin # take the profit/loss from the difference by order.quantity and adjust the entry price and quantity position_gain_loss = effective_price * order.quantity # it's what we have really closed realized_position_cost = order.quantity * ( lot_size * contract_size) # in base currency # and decrease used margin trader.account.add_used_margin(-realized_position_cost * margin_factor / base_exchange_rate) # entry price might not move... # current_position.entry_price = ((current_position.entry_price * current_position.quantity) - (close_exec_price * order.quantity)) / 2 current_position.quantity -= order.quantity exec_price = close_exec_price # directly executed quantity order.executed = order.quantity elif current_position.quantity == order.quantity: # second case the position is closed, exact quantity in the opposite direction position_gain_loss = effective_price * current_position.quantity current_position.quantity = 0.0 # it's what we have really closed realized_position_cost = order.quantity * ( lot_size * contract_size) # in base currency # directly executed quantity order.executed = order.quantity exec_price = close_exec_price # and decrease used margin trader.account.add_used_margin(-realized_position_cost * margin_factor / base_exchange_rate) else: # third case the position is reversed # 1) get the profit loss position_gain_loss = effective_price * current_position.quantity # it's what we have really closed realized_position_cost = order.quantity * ( lot_size * contract_size) # in base currency # first decrease of released margin trader.account.add_used_margin(-realized_position_cost * margin_factor / base_exchange_rate) # 2) adjust the position entry current_position.quantity = order.quantity - current_position.quantity current_position.entry_price = open_exec_price # 3) the direction is now at opposite current_position.direction = order.direction # directly executed quantity order.executed = order.quantity exec_price = open_exec_price # next increase margin of the new volume trader.account.add_used_margin( (order.quantity * lot_size * contract_size * margin_factor) / base_exchange_rate) # transaction time is current timestamp order.transact_time = trader.timestamp #order.set_position_id(current_position.position_id) if position_gain_loss != 0.0 and realized_position_cost > 0.0: # ratio gain_loss_rate = position_gain_loss / realized_position_cost relative_gain_loss_rate = delta_price / prev_entry_price # if maker close (limit+post-order) (for now same as market) current_position.profit_loss = position_gain_loss current_position.profit_loss_rate = gain_loss_rate # if taker close (market) current_position.profit_loss_market = position_gain_loss current_position.profit_loss_market_rate = gain_loss_rate trader.account.add_realized_profit_loss(position_gain_loss / base_exchange_rate) # display only for debug if position_gain_loss > 0.0: Terminal.inst().high( "Close profitable position with %.2f on %s (%.2fpips) (%.2f%%) at %s" % (position_gain_loss, order.symbol, delta_price / one_pip_means, gain_loss_rate * 100.0, market.format_price(close_exec_price)), view='debug') elif position_gain_loss < 0.0: Terminal.inst().low( "Close loosing position with %.2f on %s (%.2fpips) (%.2f%%) at %s" % (position_gain_loss, order.symbol, delta_price / one_pip_means, gain_loss_rate * 100.0, market.format_price(close_exec_price)), view='debug') logger.debug( "Account balance %.2f / Margin balance %.2f" % (trader.account.balance, trader.account.margin_balance)) else: gain_loss_rate = 0.0 # # history # # and keep for history (backtesting reporting) history = PaperTraderHistoryEntry( order, trader.account.balance, trader.account.margin_balance, delta_price / one_pip_means, gain_loss_rate, position_gain_loss, position_gain_loss / base_exchange_rate) trader._history.add(history) # unlock before notify signals trader.unlock() result = True # # order signal (SIGNAL_ORDER_OPENED+DELETED because we assume fully completed) # order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'direction': order.direction, 'timestamp': order.created_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force } # signal as watcher service (opened + fully traded qty) trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_OPENED, trader.name, (order.symbol, order_data, order.ref_order_id)) order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'trade-id': 0, 'direction': order.direction, 'timestamp': order.transact_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'exec-price': exec_price, 'avg-price': exec_price, # current_position.entry_price, 'filled': order.executed, 'cumulative-filled': order.executed, 'quote-transacted': realized_position_cost, # its margin 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force, 'commission-amount': 0, # @todo 'commission-asset': trader.account.currency } trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_TRADED, trader.name, (order.symbol, order_data, order.ref_order_id)) # # position signal # # signal as watcher service if current_position.quantity <= 0: # closed position position_data = { 'id': current_position.position_id, 'symbol': current_position.symbol, 'direction': current_position.direction, 'timestamp': order.transact_time, 'quantity': 0, 'exec-price': exec_price, 'stop-loss': None, 'take-profit': None } trader.service.watcher_service.notify( Signal.SIGNAL_POSITION_DELETED, trader.name, (order.symbol, position_data, order.ref_order_id)) else: # updated position position_data = { 'id': current_position.position_id, 'symbol': current_position.symbol, 'direction': current_position.direction, 'timestamp': order.transact_time, 'quantity': current_position.quantity, # 'avg-price': current_position.entry_price, 'exec-price': exec_price, 'stop-loss': current_position.stop_loss, 'take-profit': current_position.take_profit, # 'profit-loss': @todo here } trader.service.watcher_service.notify( Signal.SIGNAL_POSITION_UPDATED, trader.name, (order.symbol, position_data, order.ref_order_id)) # and then deleted order trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_DELETED, trader.name, (order.symbol, order.order_id, "")) # if position is empty -> closed -> delete it if current_position.quantity <= 0.0: # take care this does not make an issue current_position.exit(None) # dont a next update # trader.lock() # if current_position.symbol in trader._positions: # del trader._positions[current_position.symbol] # trader.unlock() else: # unique position per market position_id = market.market_id # it's what we have really buy realized_position_cost = order.quantity * ( market.lot_size * market.contract_size) # in base currency # check available margin margin_cost = realized_position_cost * market.margin_factor / market.base_exchange_rate if not trader.has_margin(margin_cost): # and then rejected order trader.unlock() trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_REJECTED, trader.name, (order.symbol, order.ref_order_id)) logger.error( "Not enought free margin for %s need %s but have %s!" % (order.symbol, margin_cost, trader.account.margin_balance)) return False # create a new position at market position = Position(trader) position.symbol = order.symbol position.set_position_id(position_id) position.set_key(trader.service.gen_key()) position.entry(order.direction, order.symbol, order.quantity) position.leverage = order.leverage position.created_time = trader.timestamp account_currency = trader.account.currency # long are open on ofr and short on bid exec_price = open_exec_price position.entry_price = exec_price # logger.debug("%s %f %f %f %i" % ("el" if position.direction>0 else "es", position.entry_price, market.bid, market.ofr, market.bid < market.ofr)) # transaction time is creation position date time order.transact_time = position.created_time order.set_position_id(position_id) # directly executed quantity order.executed = order.quantity trader._positions[position_id] = position # increase used margin trader.account.add_used_margin(margin_cost) # # history # history = PaperTraderHistoryEntry(order, trader.account.balance, trader.account.margin_balance) trader._history.add(history) # unlock before notify signals trader.unlock() result = True # # order signal (SIGNAL_ORDER_OPENED+TRADED+DELETED, fully completed) # order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'direction': order.direction, 'timestamp': order.created_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force } # signal as watcher service (opened + fully traded qty) trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_OPENED, trader.name, (order.symbol, order_data, order.ref_order_id)) order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'trade-id': 0, 'direction': order.direction, 'timestamp': order.transact_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'exec-price': position.entry_price, 'avg-price': position.entry_price, 'filled': order.executed, 'cumulative-filled': order.executed, 'quote-transacted': realized_position_cost, # its margin 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force, 'commission-amount': 0, # @todo 'commission-asset': trader.account.currency } #logger.info("%s %s %s" % (position.entry_price, position.quantity, order.direction)) trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_TRADED, trader.name, (order.symbol, order_data, order.ref_order_id)) # # position signal # position_data = { 'id': position.position_id, 'symbol': position.symbol, 'direction': position.direction, 'timestamp': order.transact_time, 'quantity': position.quantity, 'exec-price': position.entry_price, 'stop-loss': position.stop_loss, 'take-profit': position.take_profit } # signal as watcher service (position opened fully completed) trader.service.watcher_service.notify( Signal.SIGNAL_POSITION_OPENED, trader.name, (order.symbol, position_data, order.ref_order_id)) # and then deleted order trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_DELETED, trader.name, (order.symbol, order.order_id, "")) return result
def __fetch_positions_and_orders(self): # # get opened position and once a position occurs with our order_id create the position and make the link # # https://1broker.com/api/v2/position/open.php?token=YOUR_API_TOKEN&pretty=true var = { 'pretty': 'false', 'token': self._connector.api_key } url = self._base_url + 'position/open.php?' + urllib.parse.urlencode(var) self._connector._conn.request("GET", url) response = self._connector._conn.getresponse() data = response.read() if response.status != 200: Terminal.inst().error("Http error (%s) list positions on %s !" % (response.status, self.name,)) return None data = json.loads(data) if data['error'] or data['warning']: Terminal.inst().error("API error list positions on %s !" % (self.name,)) return None p = data['response'] for pos in p: # if order_id is one of non linked order and not found in positions list order = self._orders.get(pos['order_id']) position = self._positions.get(pos['position_id']) copy_of = pos['copy_of'] if order is not None and position is None: # an order open to a position # insert the new position position = Position(self) position.set_position_id(pos['position_id']) position.set_key(self.service.gen_key()) # create the position position.set_copied_position_id(order.copied_position_id) position.entry( Position.LONG if pos['direction'] == 'long' else Position.SHORT, pos['symbol'], pos['margin'], float(pos['take_profit']) if pos['take_profit'] else None, float(pos['stop_loss']) if pos['stop_loss'] else None, float(pos['leverage']), pos['trailing_stop_loss'] ) position.author = order.author position.entry_price = pos['entry_price'] position.quantity = float(pos['value']) position.shared = pos['shared'] position.profit_loss = float(pos['profit_loss']) position.profit_loss_rate = float(pos['profit_loss_percent']) * 0.01 position.market_close = pos['market_close'] position.created_time = datetime.strptime(pos['date_created'], "%Y-%m-%dT%H:%M:%SZ").timestamp() # .%fZ") # delete the order del self._orders[pos['order_id']] # insert the position self._positions[position.position_id] = position elif position is not None and order is None: # only have to update # update the position position.profit_loss = float(pos['profit_loss']) position.profit_loss_rate = float(pos['profit_loss_percent']) * 0.01 position.market_close = pos['market_close'] # can be a full update if not position.created_time: position.entry( Position.LONG if pos['direction'] == 'long' else Position.SHORT, pos['symbol'], pos['margin'], float(pos['take_profit']) if pos['take_profit'] else None, float(pos['stop_loss']) if pos['stop_loss'] else None, float(pos['leverage']), pos['trailing_stop_loss'] ) position.entry_price = pos['entry_price'] position.quantity = float(pos['value']) position.shared = pos['shared'] position.created_time = datetime.strptime(pos['date_created'], "%Y-%m-%dT%H:%M:%SZ").timestamp() # .%fZ") elif order is None and position is None: # externaly created position or with auto copy of plateforme # create from copy of position (lookup for position) position = Position(self) position.set_position_id(pos['position_id']) position.set_key(self.service.gen_key()) # position.set_order_id(pos['order_id']) # create the position position.set_copied_position_id(copy_of) position.entry( Position.LONG if pos['direction'] == 'long' else Position.SHORT, pos['symbol'], pos['margin'], pos['take_profit'], pos['stop_loss'], float(pos['leverage']), pos['trailing_stop_loss'] ) # retrieve the order, the author... # position.author = order.author position.entry_price = pos['entry_price'] position.quantity = float(pos['value']) position.shared = pos['shared'] position.profit_loss = float(pos['profit_loss']) position.profit_loss_rate = float(pos['profit_loss_percent']) * 0.01 position.market_close = pos['market_close'] position.created_time = datetime.strptime(pos['date_created'], "%Y-%m-%dT%H:%M:%SZ").timestamp() # .%fZ") # insert the position self._positions[position.position_id] = position # retrieve the copied position id from database @todo logger.info("Retrieve pos %s copied from position %s" % (position.position_id, position.copied_position_id)) # # check for cleared position # remove_list = [] for k, position in self._positions.items(): found = False for pos in p: if pos['position_id'] == position.position_id: found = True break if not found: # cleared but a copied trader or himself externaly remove_list.append(k) for k in remove_list: del self._positions[k]
def __fetch_positions(self): """ This is the synchronous REST fetching, but prefer the WS asynchronous and live one. Mainly used for initial fetching. """ try: positions = self._watcher.connector.positions() except Exception as e: logger.error("fetch_market_by_epic: %s" % repr(e)) raise # too this can be done by signals for pos_data in positions: pos = pos_data.get('position') market_data = pos_data.get('market') if not pos or not market_data: continue epic = market_data.get('epic', '') if not epic: continue # # update the market data first # deal_id = pos.get('dealId') market_update_time = datetime.strptime(market_data['updateTime'], '%H:%M:%S').timestamp() if market_data.get('updateTime') else None market_status = (market_data['marketStatus'] == 'TRADEABLE') self.on_update_market(epic, market_status, market_update_time, market_data['bid'], market_data['offer'], base_exchange_rate=None) market = self.market(epic) if market is None: continue # # related order # # retrieve the related order and remove that order or do it at order deleted signals... deal_ref = pos.get('dealReference') original_order = self._orders.get(deal_ref) if original_order: # @todo might be not necessary as a rejected order emit a signal but return rejection error at creation # and deal ref are not into the dict (only accepted order id) logger.debug("Todo original order retrieved") del self._orders[deal_ref] # # create or update the local position # direction = Position.LONG if pos.get('direction') == 'BUY' else Position.SHORT quantity = pos.get('dealSize', 0.0) position = self._positions.get(deal_id) if position is None: position = Position(self) position.set_position_id(deal_id) position.set_key(self.service.gen_key()) position.leverage = 1.0 / market.margin_factor position.entry(direction, epic, quantity, leverage=position.leverage) # position are merged by epic but might be independents self._positions[deal_id] = position # account local tz, but createdDateUTC exists in API v2 (look at HTTP header v=2) position.created_time = datetime.strptime(pos.get('createdDate', '1970/01/01 00:00:00:000'), "%Y/%m/%d %H:%M:%S:%f").timestamp() position.entry_price = pos.get('openLevel', 0.0) position.stop_loss = pos.get('stopLevel', 0.0) position.take_profit = pos.get('limitLevel', 0.0) # garanteed stop if pos.get('controlledRisk', False): pass # @todo # @todo stop type (garantee, market, trailing) position.trailing_stop = pos.get('trailingStep', 0.0) position.trailing_stop_dst = pos.get('trailingStopDistance', 0.0) if market: position.update_profit_loss(market) # remove empty positions, but this can be too done by DELETED signal rm_list = [] for k, position in self._positions.items(): found = False for pos_data in positions: pos = pos_data.get('position') if position.position_id == pos.get('dealId'): found = True break if not found: rm_list.append(k) for rm_pos in rm_list: del self._positions[rm_pos]
def open_position(trader, order, market, open_exec_price): """ Execute the order for margin position. """ current_position = None positions = [] trader.lock() # get a new distinct position id position_id = "siis_" + base64.b64encode( uuid.uuid4().bytes).decode('utf8').rstrip('=\n') realized_position_cost = market.effective_cost(order.quantity, open_exec_price) margin_cost = market.margin_cost(order.quantity, open_exec_price) if not trader._unlimited and trader.account.margin_balance < margin_cost: # and then rejected order trader.unlock() logger.error( "Not enought free margin for %s need %s but have %s!" % (order.symbol, margin_cost, trader.account.margin_balance)) trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_REJECTED, trader.name, (order.symbol, order.ref_order_id)) return False # create a new position at market position = Position(trader) position.symbol = order.symbol position.set_position_id(position_id) position.set_key(trader.service.gen_key()) position.entry(order.direction, order.symbol, order.quantity) position.leverage = order.leverage position.created_time = trader.timestamp account_currency = trader.account.currency # long are open on ofr and short on bid position.entry_price = market.open_exec_price(order.direction) # transaction time is creation position date time order.transact_time = position.created_time order.set_position_id(position_id) # directly executed quantity order.executed = order.quantity trader._positions[position_id] = position # increase used margin trader.account.use_margin(margin_cost) # unlock before notify signals trader.unlock() # # order signal (SIGNAL_ORDER_OPENED+TRADED+DELETED, fully completed) # order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'direction': order.direction, 'timestamp': order.created_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force } # signal as watcher service (opened + fully traded qty) trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_OPENED, trader.name, (order.symbol, order_data, order.ref_order_id)) order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'trade-id': 0, 'direction': order.direction, 'timestamp': order.transact_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'exec-price': position.entry_price, 'avg-price': position.entry_price, 'filled': order.executed, 'cumulative-filled': order.executed, 'quote-transacted': realized_position_cost, # its margin 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force, 'commission-amount': 0, # @todo 'commission-asset': trader.account.currency } trader.service.watcher_service.notify( Signal.SIGNAL_ORDER_TRADED, trader.name, (order.symbol, order_data, order.ref_order_id)) # # position signal # position_data = { 'id': position.position_id, 'symbol': position.symbol, 'direction': position.direction, 'timestamp': order.transact_time, 'quantity': position.quantity, 'exec-price': position.entry_price, 'stop-loss': position.stop_loss, 'take-profit': position.take_profit, 'avg-entry-price': position.entry_price, 'profit-loss': 0.0, 'profit-loss-currency': market.quote } # signal as watcher service (position opened fully completed) trader.service.watcher_service.notify( Signal.SIGNAL_POSITION_OPENED, trader.name, (order.symbol, position_data, order.ref_order_id)) # and then deleted order trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_DELETED, trader.name, (order.symbol, order.order_id, "")) return True
def __update_positions(self, symbol, market): if not self.connected: return # position for each configured market for symbol, market in self._markets.items(): pos = self._watcher.connector.ws.position(symbol) position = None if self._positions.get(symbol): position = self._positions.get(symbol) elif pos['isOpen']: # insert the new position position = Position(self) position.set_position_id(symbol) position.set_key(self.service.gen_key()) quantity = abs(float(pos['currentQty'])) direction = Position.SHORT if pos[ 'currentQty'] < 0 else Position.LONG position.entry(direction, symbol, quantity) position.leverage = pos['leverage'] position.entry_price = pos['avgEntryPrice'] position.created_time = datetime.strptime( pos['openingTimestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").timestamp() # .%fZ") # id is symbol self._positions[symbol] = position elif (not pos['isOpen'] or pos['currentQty'] == 0) and self._positions.get(symbol): # no more position del self._positions[symbol] if position: # absolute value because we work with positive quantity + direction information position.quantity = abs(float(pos['currentQty'])) position.direction = Position.SHORT if pos[ 'currentQty'] < 0 else Position.LONG position.leverage = pos['leverage'] # position.market_close = pos['market_close'] position.entry_price = pos['avgEntryPrice'] position.created_time = datetime.strptime( pos['openingTimestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").timestamp() # .%fZ")
def exec_margin_order(trader, order, market, open_exec_price, close_exec_price): """ Execute the order for margin position. @todo support of hedging else reduce first the opposite direction positions (FIFO method) """ current_position = None positions = [] trader.lock() if order.position_id: current_position = trader._positions.get(order.position_id) else: # @todo pass # # position of the same market on any directions # for k, pos in trader._positions.items(): # if pos.symbol == order.symbol: # positions.append(pos) # if order.hedging and market.hedging: # pass # else: # current_position = positions[-1] if positions else None if current_position and current_position.is_opened(): # increase or reduce the current position org_quantity = current_position.quantity exec_price = 0.0 # # and adjust the position quantity (no hedging) # # price difference depending of the direction delta_price = 0 if current_position.direction == Position.LONG: delta_price = close_exec_price - current_position.entry_price # logger.debug("cl", delta_price, " use ", close_exec_price, " other ", open_exec_price, close_exec_price < open_exec_price) elif current_position.direction == Position.SHORT: delta_price = current_position.entry_price - close_exec_price # logger.debug("cs", delta_price, " use ", close_exec_price, " other ", open_exec_price, close_exec_price < open_exec_price) # keep for percent calculation prev_entry_price = current_position.entry_price or close_exec_price leverage = order.leverage # most of thoose data rarely change except the base_exchange_rate value_per_pip = market.value_per_pip contract_size = market.contract_size lot_size = market.lot_size one_pip_means = market.one_pip_means base_exchange_rate = market.base_exchange_rate margin_factor = market.margin_factor # logger.debug(order.symbol, bid_price, ofr_price, open_exec_price, close_exec_price, delta_price, current_position.entry_price, order.price) realized_position_cost = 0.0 # realized cost of the position in base currency # effective meaning of delta price in base currency effective_price = (delta_price / one_pip_means) * value_per_pip # in base currency position_gain_loss = 0.0 if order.direction == current_position.direction: # first, same direction, increase the position realized_position_cost = market.effective_cost(order.quantity, open_exec_price) margin_cost = market.margin_cost(order.quantity, open_exec_price) if not trader._unlimited and trader.account.margin_balance < margin_cost: # and then rejected order trader.unlock() trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_REJECTED, trader.name, (order.symbol, order.ref_order_id)) logger.error("Not enought free margin for %s need %s but have %s!" % (order.symbol, margin_cost, trader.account.margin_balance)) return False # still in long, position size increase and adjust the entry price entry_price = ((current_position.entry_price * current_position.quantity) + (open_exec_price * order.quantity)) / 2 current_position.entry_price = entry_price current_position.quantity += order.quantity # directly executed quantity order.executed = order.quantity exec_price = open_exec_price # increase used margin trader.account.use_margin(margin_cost) else: # different direction if current_position.quantity > order.quantity: # first case the direction still the same, reduce the position and the margin # take the profit/loss from the difference by order.quantity and adjust the entry price and quantity position_gain_loss = effective_price * order.quantity realized_position_cost = market.effective_cost(order.quantity, close_exec_price) margin_cost = market.margin_cost(order.quantity, close_exec_price) # and decrease used margin trader.account.free_margin(margin_cost) # entry price might not move... # current_position.entry_price = ((current_position.entry_price * current_position.quantity) - (close_exec_price * order.quantity)) / 2 current_position.quantity -= order.quantity current_position.exit_price = close_exec_price exec_price = close_exec_price # directly executed quantity order.executed = order.quantity elif current_position.quantity == order.quantity: # second case the position is closed, exact quantity in the opposite direction position_gain_loss = effective_price * current_position.quantity current_position.quantity = 0.0 current_position.exit_price = close_exec_price realized_position_cost = market.effective_cost(order.quantity, close_exec_price) margin_cost = market.margin_cost(order.quantity, close_exec_price) # directly executed quantity order.executed = order.quantity exec_price = close_exec_price # and decrease used margin trader.account.free_margin(margin_cost) else: # third case the position is reversed # 1) get the profit loss position_gain_loss = effective_price * current_position.quantity realized_position_cost = market.effective_cost(order.quantity, close_exec_price) margin_cost = market.margin_cost(order.quantity, close_exec_price) # first decrease of released margin trader.account.free_margin(margin_cost) # 2) adjust the position entry current_position.quantity = order.quantity - current_position.quantity current_position.entry_price = open_exec_price # 3) the direction is now at opposite current_position.direction = order.direction # directly executed quantity order.executed = order.quantity exec_price = open_exec_price margin_cost = market.margin_cost(order.quantity-current_position.quantity, open_exec_price) # next increase margin of the new volume trader.account.use_margin(margin_cost) # transaction time is current timestamp order.transact_time = trader.timestamp #order.set_position_id(current_position.position_id) if position_gain_loss != 0.0 and realized_position_cost > 0.0: # ratio gain_loss_rate = position_gain_loss / realized_position_cost relative_gain_loss_rate = delta_price / prev_entry_price # if maker close (limit+post-order) (for now same as market) current_position.profit_loss = position_gain_loss current_position.profit_loss_rate = gain_loss_rate # if taker close (market) current_position.profit_loss_market = position_gain_loss current_position.profit_loss_market_rate = gain_loss_rate trader.account.add_realized_profit_loss(position_gain_loss / base_exchange_rate) else: gain_loss_rate = 0.0 # unlock before notify signals trader.unlock() result = True # # order signal (SIGNAL_ORDER_OPENED+DELETED because we assume fully completed) # order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'direction': order.direction, 'timestamp': order.created_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force } # signal as watcher service (opened + fully traded qty) trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_OPENED, trader.name, (order.symbol, order_data, order.ref_order_id)) order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'trade-id': 0, 'direction': order.direction, 'timestamp': order.transact_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'exec-price': exec_price, 'avg-price': current_position.entry_price, 'filled': order.executed, 'cumulative-filled': order.executed, 'quote-transacted': realized_position_cost, # its margin 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force, 'commission-amount': 0, # @todo 'commission-asset': trader.account.currency } trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_TRADED, trader.name, (order.symbol, order_data, order.ref_order_id)) # # position signal # # signal as watcher service if current_position.quantity <= 0: # closed position position_data = { 'id': current_position.position_id, 'symbol': current_position.symbol, 'direction': current_position.direction, 'timestamp': order.transact_time, 'quantity': 0, 'avg-entry-price': current_position.entry_price, 'avg-exit-price': current_position.exit_price, 'exec-price': exec_price, 'stop-loss': None, 'take-profit': None, 'profit-loss': current_position.profit_loss, 'profit-loss-currency': market.quote } trader.service.watcher_service.notify(Signal.SIGNAL_POSITION_DELETED, trader.name, (order.symbol, position_data, order.ref_order_id)) else: # updated position position_data = { 'id': current_position.position_id, 'symbol': current_position.symbol, 'direction': current_position.direction, 'timestamp': order.transact_time, 'quantity': current_position.quantity, 'avg-entry-price': current_position.entry_price, 'avg-exit-price': current_position.exit_price, 'exec-price': exec_price, 'stop-loss': current_position.stop_loss, 'take-profit': current_position.take_profit, 'profit-loss': current_position.profit_loss, 'profit-loss-currency': market.quote } trader.service.watcher_service.notify(Signal.SIGNAL_POSITION_UPDATED, trader.name, (order.symbol, position_data, order.ref_order_id)) # and then deleted order trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_DELETED, trader.name, (order.symbol, order.order_id, "")) # if position is empty -> closed -> delete it if current_position.quantity <= 0.0: current_position.exit(None) # done during next update # trader.lock() # if current_position.position_id in trader._positions: # del trader._positions[current_position.position_id] # trader.unlock() else: # get a new distinct position id position_id = "siis_" + base64.b64encode(uuid.uuid4().bytes).decode('utf8').rstrip('=\n') realized_position_cost = market.effective_cost(order.quantity, open_exec_price) margin_cost = market.margin_cost(order.quantity, open_exec_price) if not trader._unlimited and trader.account.margin_balance < margin_cost: # and then rejected order trader.unlock() trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_REJECTED, trader.name, (order.symbol, order.ref_order_id)) logger.error("Not enought free margin for %s need %s but have %s!" % (order.symbol, margin_cost, trader.account.margin_balance)) return False # create a new position at market position = Position(trader) position.symbol = order.symbol position.set_position_id(position_id) position.set_key(trader.service.gen_key()) position.entry(order.direction, order.symbol, order.quantity) position.leverage = order.leverage position.created_time = trader.timestamp account_currency = trader.account.currency # long are open on ofr and short on bid position.entry_price = market.open_exec_price(order.direction) # logger.debug("%s %f %f %f %i" % ("el" if position.direction>0 else "es", position.entry_price, market.bid, market.ofr, market.bid < market.ofr)) # transaction time is creation position date time order.transact_time = position.created_time order.set_position_id(position_id) # directly executed quantity order.executed = order.quantity trader._positions[position_id] = position # increase used margin trader.account.use_margin(margin_cost) # unlock before notify signals trader.unlock() result = True # # order signal (SIGNAL_ORDER_OPENED+TRADED+DELETED, fully completed) # order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'direction': order.direction, 'timestamp': order.created_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force } # signal as watcher service (opened + fully traded qty) trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_OPENED, trader.name, (order.symbol, order_data, order.ref_order_id)) order_data = { 'id': order.order_id, 'symbol': order.symbol, 'type': order.order_type, 'trade-id': 0, 'direction': order.direction, 'timestamp': order.transact_time, 'quantity': order.quantity, 'price': order.price, 'stop-price': order.stop_price, 'exec-price': position.entry_price, 'avg-price': position.entry_price, 'filled': order.executed, 'cumulative-filled': order.executed, 'quote-transacted': realized_position_cost, # its margin 'stop-loss': order.stop_loss, 'take-profit': order.take_profit, 'time-in-force': order.time_in_force, 'commission-amount': 0, # @todo 'commission-asset': trader.account.currency } #logger.info("%s %s %s" % (position.entry_price, position.quantity, order.direction)) trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_TRADED, trader.name, (order.symbol, order_data, order.ref_order_id)) # # position signal # position_data = { 'id': position.position_id, 'symbol': position.symbol, 'direction': position.direction, 'timestamp': order.transact_time, 'quantity': position.quantity, 'exec-price': position.entry_price, 'stop-loss': position.stop_loss, 'take-profit': position.take_profit, 'avg-entry-price': position.entry_price, 'profit-loss': 0.0, 'profit-loss-currency': market.quote } # signal as watcher service (position opened fully completed) trader.service.watcher_service.notify(Signal.SIGNAL_POSITION_OPENED, trader.name, (order.symbol, position_data, order.ref_order_id)) # and then deleted order trader.service.watcher_service.notify(Signal.SIGNAL_ORDER_DELETED, trader.name, (order.symbol, order.order_id, "")) return result
def __update_positions(self, symbol, market): # position for each configured market for symbol, market in self._markets.items(): pos = self._watcher.connector.ws.position(symbol) position = None if self._positions.get(symbol): position = self._positions.get(symbol) elif pos['isOpen']: # insert the new position position = Position(self) position.set_position_id(symbol) position.set_key(self.service.gen_key()) quantity = abs(float(pos['currentQty'])) direction = Position.SHORT if pos['currentQty'] < 0 else Position.LONG position.entry(direction, symbol, quantity) position.leverage = pos['leverage'] position.entry_price = pos['avgEntryPrice'] position.created_time = datetime.strptime(pos['openingTimestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").timestamp() # .%fZ") # id is symbol self._positions[symbol] = position elif (not pos['isOpen'] or pos['currentQty'] == 0) and self._positions.get(symbol): # no more position del self._positions[symbol] if position: # absolute value because we work with positive quantity + direction information position.quantity = abs(float(pos['currentQty'])) position.direction = Position.SHORT if pos['currentQty'] < 0 else Position.LONG position.leverage = pos['leverage'] # position.market_close = pos['market_close'] position.entry_price = pos['avgEntryPrice'] position.created_time = datetime.strptime(pos['openingTimestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").timestamp() # .%fZ") # XBt to XBT # ratio = 1.0 # if pos['currency'] == 'XBt': # ratio = 1.0 / 100000000.0 # don't want them because they are in XBt or XBT # position.profit_loss = (float(pos['unrealisedPnl']) * ratio) # position.profit_loss_rate = float(pos['unrealisedPnlPcnt']) # # must be updated using the market taker fee # position.profit_loss_market = (float(pos['unrealisedPnl']) * ratio) # position.profit_loss_market_rate = float(pos['unrealisedPnlPcnt']) # compute profit loss in base currency position.update_profit_loss(market)
def __update_positions(self, symbol, market): if not self.connected: return # position for each configured market for symbol, market in self._markets.items(): pos = self._watcher.connector.ws.position(symbol) position = None if self._positions.get(symbol): position = self._positions.get(symbol) elif pos['isOpen']: # insert the new position position = Position(self) position.set_position_id(symbol) position.set_key(self.service.gen_key()) quantity = abs(float(pos['currentQty'])) direction = Position.SHORT if pos['currentQty'] < 0 else Position.LONG position.entry(direction, symbol, quantity) position.leverage = pos['leverage'] position.entry_price = pos['avgEntryPrice'] position.created_time = datetime.strptime(pos['openingTimestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=UTC()).timestamp() # id is symbol self._positions[symbol] = position elif (not pos['isOpen'] or pos['currentQty'] == 0) and self._positions.get(symbol): # no more position del self._positions[symbol] if position: # absolute value because we work with positive quantity + direction information position.quantity = abs(float(pos['currentQty'])) position.direction = Position.SHORT if pos['currentQty'] < 0 else Position.LONG position.leverage = pos['leverage'] # position.market_close = pos['market_close'] position.entry_price = pos['avgEntryPrice'] position.created_time = datetime.strptime(pos['openingTimestamp'], "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=UTC()).timestamp() # XBt to XBT ratio = 1.0 if pos['currency'] == 'XBt': ratio = 1.0 / 100000000.0 # @todo minus taker-fee position.profit_loss = (float(pos['unrealisedPnl']) * ratio) position.profit_loss_rate = float(pos['unrealisedPnlPcnt']) # @todo minus maker-fee position.profit_loss_market = (float(pos['unrealisedPnl']) * ratio) position.profit_loss_market_rate = float(pos['unrealisedPnlPcnt'])