async def follow_strategy(pilot_name: str, passenger: AutoPilotTask, etrade: AsyncEtrade, quote: dict): last = get_last(quote) bid = get_bid(quote) ask = get_ask(quote) if bid < passenger.loss_price or ask > passenger.profit_price or last < passenger.pullback_price: if bid < passenger.loss_price: logger.info( "%s %s bid reached the loss price, placing sell order at %s", PREFIX, pilot_name, ask) elif ask > passenger.profit_price: logger.info( "%s %s ask reached the profit price, placing sell order at %s", PREFIX, pilot_name, ask) else: logger.info( "%s %s last price reached the pullback price, placing sell order at %s", PREFIX, pilot_name, ask) order_id = await place_sell_order(passenger, ask, etrade) passenger.state = AutoPilotTask.SELLING passenger.tracking_order_id = order_id
async def sell_position(pilot_name: str, passenger: AutoPilotTask, etrade: AsyncEtrade): if not passenger.tracking_order_id: quote = await etrade.get_quote(passenger.symbol) ask = get_ask(quote) await commit_sell(passenger, ask, etrade) return order = await etrade.get_order_details(passenger.account.account_key, passenger.tracking_order_id, passenger.symbol) if not order: logger.info("%s %s unable to track sell order %s. NOT FOUND.", PREFIX, pilot_name, passenger.tracking_order_id) passenger.status = AutoPilotTask.PAUSED passenger.state = AutoPilotTask.ERROR passenger.error_message = f'unable to track selling order {passenger.tracking_order_id}. NOT FOUND' return details = order.get("OrderDetail")[0] status = OrderStatus(details.get("status")) limit_price = details.get("limitPrice") if status in (OrderStatus.OPEN, OrderStatus.PARTIAL): quote = await etrade.get_quote(passenger.symbol) bid = get_bid(quote) ask = get_ask(quote) placed_at = datetime.utcfromtimestamp( details.get("placedTime") / 1000).replace(tzinfo=pytz.utc) elapsed_time = timezone.now() - placed_at if elapsed_time >= timedelta(seconds=5) and (limit_price < bid or limit_price > ask): try: await etrade.cancel_order(passenger.account.account_key, passenger.tracking_order_id) except ServiceError as e: if e.error_code == 5001: # This order is currently being executed # or rejected. It cannot be cancelled. return elif status == OrderStatus.CANCEL_REQUESTED: logger.debug( "%s %s cancel for order %s has been requested. waiting...", PREFIX, pilot_name, passenger.tracking_order_id) elif status == OrderStatus.CANCELLED: logger.debug("%s %s order %s cancelled. placing new sell order...", PREFIX, pilot_name, passenger.tracking_order_id) instrument = details.get("Instrument")[0] ordered_quantity = instrument.get("orderedQuantity") filled_quantity = instrument.get("filledQuantity") or 0 pending_quantity = int(ordered_quantity) - int(filled_quantity) passenger.quantity = pending_quantity quote = await etrade.get_quote(passenger.symbol) ask = get_ask(quote) await commit_sell(passenger, ask, etrade) elif status == OrderStatus.REJECTED: logger.warning( "%s %s order %s rejected. this case is not being handled.", PREFIX, pilot_name, passenger.tracking_order_id) elif status == OrderStatus.EXPIRED: logger.warning( "%s %s order %s expired. this case is not being handled.", PREFIX, pilot_name, passenger.tracking_order_id) elif status == OrderStatus.EXECUTED: # get possition quantity = await etrade.get_position_quantity( passenger.account.account_key, passenger.symbol) if quantity in (None, 0): instrument = details.get("Instrument")[0] avg_execution_price = Decimal( instrument.get("averageExecutionPrice")) passenger.status = AutoPilotTask.DONE passenger.exit_price = avg_execution_price percent = passenger.exit_percent percent_label = "profit" if percent > Decimal(0) else "loss" logger.info("%s %s position sold for a %s%% %s", PREFIX, pilot_name, percent, percent_label) if passenger.discord_webhook: await post_webhook( passenger.discord_webhook, f"{passenger.symbol} position sold for a {percent}% {percent_label}" ) else: # TODO: place sell order for scale out quantity passenger.quantity = quantity quote = await etrade.get_quote(passenger.symbol) ask = get_ask(quote) await commit_sell(passenger, ask, etrade) else: logger.error("%s %s unhandled status %s for order %s", PREFIX, pilot_name, status, passenger.tracking_order_id) passenger.status = AutoPilotTask.PAUSED passenger.state = AutoPilotTask.ERROR passenger.error_message = f'unhandled status {status} for order {passenger.tracking_order_id}'
async def commit_sell(passenger: AutoPilotTask, sell_price: Decimal, etrade: AsyncEtrade): order_id = await place_sell_order(passenger, sell_price, etrade) passenger.state = AutoPilotTask.SELLING passenger.tracking_order_id = order_id