def reduce_position_at(self, qty, price, role=None) -> Order: self._validate_qty(qty) qty = abs(qty) # validation if price < 0: raise ValueError('price cannot be negative.') # validation if self.position.is_close: raise OrderNotAllowed( 'Cannot submit a reduce_position order when there is not open position' ) side = jh.opposite_side(jh.type_to_side(self.position.type)) # validation if side == 'buy' and price >= self.position.current_price: raise OrderNotAllowed( 'Cannot reduce (via LIMIT) buy at ${} when current_price is ${}' .format(price, self.position.current_price)) # validation if side == 'sell' and price <= self.position.current_price: raise OrderNotAllowed( 'Cannot reduce (via LIMIT) sell at ${} when current_price is ${}' .format(price, self.position.current_price)) return self.api.limit_order(self.exchange, self.symbol, qty, price, side, role, [order_flags.REDUCE_ONLY])
def stop_loss_at(self, qty, price, role=None) -> Order: self._validate_qty(qty) # validation if self.position.is_close: raise OrderNotAllowed( 'Cannot submit a (reduce_only) stop_loss order when there is not open position' ) side = jh.opposite_side(jh.type_to_side(self.position.type)) if price < 0: raise ValueError('price cannot be negative.') if side == 'buy' and price < self.position.current_price: raise OrderNotAllowed( 'Cannot submit a buy stop at {} when current price is {}'. format(price, self.position.current_price)) if side == 'sell' and price > self.position.current_price: raise OrderNotAllowed( 'Cannot submit a sell stop at {} when current price is {}.'. format(price, self.position.current_price)) return self.api.stop_order(self.exchange, self.symbol, abs(qty), price, side, role, [order_flags.REDUCE_ONLY])
def reduce_position_at(self, qty: float, price: float, role: str = None) -> Union[Order, None]: self._validate_qty(qty) qty = abs(qty) # validation if price < 0: raise ValueError('price cannot be negative.') # validation if self.position.is_close: raise OrderNotAllowed( 'Cannot submit a reduce_position order when there is no open position' ) side = jh.opposite_side(jh.type_to_side(self.position.type)) if abs(price - self.position.current_price) < 0.0001: return self.api.market_order(self.exchange, self.symbol, qty, price, side, role, [order_flags.REDUCE_ONLY]) elif (side == 'sell' and self.position.type == 'long' and price > self.position.current_price) or ( side == 'buy' and self.position.type == 'short' and price < self.position.current_price): return self.api.limit_order(self.exchange, self.symbol, qty, price, side, role, [order_flags.REDUCE_ONLY]) elif (side == 'sell' and self.position.type == 'long' and price < self.position.current_price) or ( side == 'buy' and self.position.type == 'short' and price > self.position.current_price): return self.api.stop_order(self.exchange, self.symbol, abs(qty), price, side, role, [order_flags.REDUCE_ONLY]) else: raise OrderNotAllowed( "This order doesn't seem to be for reducing the position.")
def test_type_to_side(): assert jh.type_to_side('long') == 'buy' assert jh.type_to_side('short') == 'sell'
def _log_position_update(self, order: Order, role: str) -> None: """ A log can be either about opening, adding, reducing, or closing the position. Arguments: order {order} -- the order object """ # set the trade_id for the order if we're in the middle of a trade. Otherwise, it # is done at order_roles.OPEN_POSITION if self.trade: order.trade_id = self.trade.id if role == order_roles.OPEN_POSITION: self.trade = CompletedTrade() self.trade.leverage = self.leverage self.trade.orders = [order] self.trade.timeframe = self.timeframe self.trade.id = jh.generate_unique_id() order.trade_id = self.trade.id self.trade.strategy_name = self.name self.trade.exchange = order.exchange self.trade.symbol = order.symbol self.trade.type = trade_types.LONG if order.side == sides.BUY else trade_types.SHORT self.trade.qty = order.qty self.trade.opened_at = jh.now_to_timestamp() self.trade.entry_candle_timestamp = self.current_candle[0] elif role == order_roles.INCREASE_POSITION: self.trade.orders.append(order) self.trade.qty += order.qty elif role == order_roles.REDUCE_POSITION: self.trade.orders.append(order) self.trade.qty += order.qty elif role == order_roles.CLOSE_POSITION: self.trade.exit_candle_timestamp = self.current_candle[0] self.trade.orders.append(order) # calculate average stop-loss price sum_price = 0 sum_qty = 0 if self._log_stop_loss is not None: for l in self._log_stop_loss: sum_qty += abs(l[0]) sum_price += abs(l[0]) * l[1] self.trade.stop_loss_at = sum_price / sum_qty else: self.trade.stop_loss_at = np.nan # calculate average take-profit price sum_price = 0 sum_qty = 0 if self._log_take_profit is not None: for l in self._log_take_profit: sum_qty += abs(l[0]) sum_price += abs(l[0]) * l[1] self.trade.take_profit_at = sum_price / sum_qty else: self.trade.take_profit_at = np.nan # calculate average entry_price price sum_price = 0 sum_qty = 0 for l in self.trade.orders: if not l.is_executed: continue if jh.side_to_type(l.side) != self.trade.type: continue sum_qty += abs(l.qty) sum_price += abs(l.qty) * l.price self.trade.entry_price = sum_price / sum_qty # calculate average exit_price sum_price = 0 sum_qty = 0 for l in self.trade.orders: if not l.is_executed: continue if jh.side_to_type(l.side) == self.trade.type: continue sum_qty += abs(l.qty) sum_price += abs(l.qty) * l.price self.trade.exit_price = sum_price / sum_qty self.trade.closed_at = jh.now_to_timestamp() self.trade.qty = pydash.sum_by( filter(lambda o: o.side == jh.type_to_side(self.trade.type), self.trade.orders), lambda o: abs(o.qty)) store.completed_trades.add_trade(self.trade) if jh.is_livetrading(): store_completed_trade_into_db(self.trade) self.trade = None self.trades_count += 1 if jh.is_livetrading(): store_order_into_db(order)