def test_loss_amount__returns_correct_value(self): strat = TradingStrategy(loss_percent=Decimal('3')) auto_pilot = AutoPilotTask(strategy=strat, base_price=Decimal('100')) loss_amount = auto_pilot.loss_amount assert loss_amount == Decimal('3')
async def green_light(pilot_name: str, passenger: AutoPilotTask, etrade: AsyncEtrade) -> bool: # handle override signal if passenger.signal == AutoPilotTask.MANUAL_OVERRIDE: logger.info("%s %s received signal MANUAL_OVERRIDE.", PREFIX, pilot_name) logger.info("%s %s releasing control...", PREFIX, pilot_name) passenger.status = AutoPilotTask.DONE return False # if no market session, sleep until market opens if MarketSession.current(passenger.is_otc) is None: logger.debug("%s %s market is closed. sleeping 1h", PREFIX, pilot_name) time_till_open = time_till_market_open(passenger.is_otc) await asyncio.sleep(time_till_open) return False # if no access token, sleep 1s (repeat until valid access) if not etrade.is_session_active(): logger.debug("%s %s waiting for valid %s session...", PREFIX, pilot_name, etrade.name) await asyncio.sleep(1) return False return True
def test_profit_amount__returns_rounded_value(self): strat = TradingStrategy(profit_percent=Decimal('8')) auto_pilot1 = AutoPilotTask(strategy=strat, base_price=Decimal('4.5324')) auto_pilot2 = AutoPilotTask(strategy=strat, base_price=Decimal('0.45324')) auto_pilot3 = AutoPilotTask(strategy=strat, base_price=Decimal('0.045324')) profit_amount1 = auto_pilot1.profit_amount profit_amount2 = auto_pilot2.profit_amount profit_amount3 = auto_pilot3.profit_amount assert profit_amount1 == Decimal('0.363') assert profit_amount2 == Decimal('0.0363') assert profit_amount3 == Decimal('0.00363')
def test_profit_amount__returns_correct_value(self): strat = TradingStrategy(profit_percent=Decimal('8')) auto_pilot = AutoPilotTask(strategy=strat, base_price=Decimal('100')) profit_amount = auto_pilot.profit_amount assert profit_amount == Decimal('8')
def test_loss_amount__returns_rounded_value(self): strat = TradingStrategy(loss_percent=Decimal('3')) auto_pilot1 = AutoPilotTask(strategy=strat, base_price=Decimal('4.5324')) auto_pilot2 = AutoPilotTask(strategy=strat, base_price=Decimal('0.45324')) auto_pilot3 = AutoPilotTask(strategy=strat, base_price=Decimal('0.045324')) loss_amount1 = auto_pilot1.loss_amount loss_amount2 = auto_pilot2.loss_amount loss_amount3 = auto_pilot3.loss_amount assert loss_amount1 == Decimal('0.136') assert loss_amount2 == Decimal('0.0136') assert loss_amount3 == Decimal('0.00136')
def test_loss_price__returns_rounded_value(self): strat = TradingStrategy(loss_percent=Decimal('3')) auto_pilot1 = AutoPilotTask(strategy=strat, base_price=Decimal('4.5324'), loss_ref_price=Decimal('10')) auto_pilot2 = AutoPilotTask(strategy=strat, base_price=Decimal('0.45324'), loss_ref_price=Decimal('1')) auto_pilot3 = AutoPilotTask(strategy=strat, base_price=Decimal('0.045324'), loss_ref_price=Decimal('0.1')) loss_price1 = auto_pilot1.loss_price loss_price2 = auto_pilot2.loss_price loss_price3 = auto_pilot3.loss_price assert loss_price1 == Decimal('9.86') assert loss_price2 == Decimal('0.986') assert loss_price3 == Decimal('0.0986')
async def minimize_loss(pilot_name: str, passenger: AutoPilotTask, etrade: AsyncEtrade, quote: dict): await follow_strategy(pilot_name, passenger, etrade, quote) if passenger.state == AutoPilotTask.SELLING: return bid = get_bid(quote) ref_price_thresdhold = passenger.loss_ref_price + passenger.loss_amount * 2 if bid > ref_price_thresdhold: passenger.loss_ref_price = passenger.loss_ref_price + passenger.loss_amount
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 track_position(pilot_name: str, passenger: AutoPilotTask, etrade: AsyncEtrade): logger.debug("%s %s tracking.", PREFIX, pilot_name) quote = await etrade.get_quote(passenger.symbol) quote = clean_quote(quote) last = get_last(quote) # passenger.tracking_data['quotes'].append(quote.get('All')) if passenger.top_price < last: passenger.tracking_data['top'] = str(last) if passenger.modifier == AutoPilotTask.FOLLOW_STRATEGY: await follow_strategy(pilot_name, passenger, etrade, quote) elif passenger.modifier == AutoPilotTask.MINIMIZE_LOSS: await minimize_loss(pilot_name, passenger, etrade, quote) elif passenger.modifier == AutoPilotTask.MAXIMIZE_PROFIT: await follow_strategy(pilot_name, passenger, etrade, quote) elif passenger.modifier == AutoPilotTask.MIN_LOSS_MAX_PROFIT: await follow_strategy(pilot_name, passenger, etrade, quote) else: await follow_strategy(pilot_name, passenger, etrade, quote)
def save_passenger(passenger: AutoPilotTask): passenger.save()
def delete_passenger(passenger: AutoPilotTask): passenger.delete()
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
def mutate_and_get_payload(cls, root, info, symbol, strategy_id=None, provider_id=None, account_id=None, modifier=None): user = info.context.user strategy = user.trading_strategies.get( id=strategy_id) if strategy_id else cls.get_default_strategy(info) if not strategy: return AutoPilotON( error=AutoPilotONError.STRATEGY_REQUIRED, error_message= 'Either set the strategy_id param or configure a default.') provider = user.service_providers.get( id=provider_id) if provider_id else cls.get_default_privider(info) if not provider: return AutoPilotON( error=AutoPilotONError.PROVIDER_REQUIRED, error_message= 'Either set the provider_id param or configure a default.') account = user.accounts.get( id=account_id) if account_id else cls.get_default_account(info) if not account: return AutoPilotON( error=AutoPilotONError.ACCOUNT_REQUIRED, error_message= 'Either set the account_id param or configure a default.') if AutoPilotTask.objects.filter(symbol=symbol, status=AutoPilotTask.RUNNING).exists(): return AutoPilotON( error=AutoPilotONError.ALREADY_EXISTS, error_message=f'Autopilot for {symbol} already exists.') if modifier is None: modifier = cls.get_default_modifier(user) discord_webhook = cls.get_discord_webhook(info) etrade = get_provider_instance(provider) quantity, entry_price = etrade.get_position(account.account_key, symbol) if not quantity or not entry_price: return AutoPilotON( error=AutoPilotONError.NO_POSITION_FOR_SYMBOL, error_message= f'No position found for {symbol}. Position: {quantity}@{entry_price}' ) quote = etrade.get_quote(symbol) is_otc = etrade.is_otc(quote) task = AutoPilotTask(user=user, strategy=strategy, provider=provider, account=account, is_otc=is_otc, symbol=symbol, quantity=quantity, entry_price=entry_price, base_price=entry_price, loss_ref_price=entry_price, profit_ref_price=entry_price, ref_time=timezone.now(), modifier=modifier, discord_webhook=discord_webhook) task.save() return AutoPilotON()
def mutate_and_get_payload(cls, root, info, symbol, strategy_id, provider_id, margin='0.00', price='0.0000', quantity=0, account_id=None, autopilot=False): strategy = TradingStrategy.objects.get(id=strategy_id) provider = ServiceProvider.objects.select_related('session') \ .get(id=provider_id) account = Account.objects.get(id=account_id) if account_id else None account_key = account.account_key.strip() if account \ else provider.account_key.strip() if not account_key: return BuyStock( error=BuyStockError.ACCOUNT_NOT_PROVIDED, error_message= 'Either specify an accountId that has a valid accountKey ' + 'or configure a default accountKey on the provider.') if not account: account = Account.objects.get(account_key=account_key) if not strategy.funded(account): return BuyStock( error=BuyStockError.INSUFFICIENT_FUNDS, error_message='Insufficient funds. Strategy selected ' + 'requires more cash available for investment.') etrade = get_provider_instance(provider) if autopilot: quote = etrade.get_quote(symbol) is_otc = etrade.is_otc(quote) user = info.context.user settings = Settings.objects.filter(user_id=user.id).first() default_modifier = settings.default_autopilot_modifier if settings else None discord_webhook = settings.discord_webhook if settings else None task = AutoPilotTask(signal=AutoPilotTask.BUY, user=info.context.user, strategy=strategy, provider=provider, account=account, is_otc=is_otc, symbol=symbol, quantity=quantity, entry_price=price, base_price=price, loss_ref_price=price, profit_ref_price=price, ref_time=timezone.now(), modifier=default_modifier, discord_webhook=discord_webhook) task.save() return BuyStock() if Decimal(price): limit_price = Decimal(price).quantize(Decimal('0.0001')) else: quantized_margin = Decimal(margin).quantize(Decimal('0.001')) quote = etrade.get_quote(symbol) limit_price = get_limit_price( OrderAction.BUY, get_bid(quote), Decimal(quantized_margin) or strategy.price_margin) if not quantity: quantity = strategy.get_quantity_for( buying_power=account.real_value, price_per_share=limit_price) order_params = { 'account_key': account_key, 'market_session': MarketSession.current().value, 'action': OrderAction.BUY.value, 'symbol': symbol, 'price_type': PriceType.LIMIT.value, 'quantity': quantity, 'limit_price': limit_price } preview_ids = etrade.preview_order( order_client_id=get_random_string(length=20), **order_params) etrade.place_order(order_client_id=get_random_string(length=20), preview_ids=preview_ids, **order_params) # TODO: HANDLE Code: 1527. Message: Opening orders for this security cannot be accepted online at this time. For assistance with placing this order, please contact Customer Service at 1-800-ETRADE-1 (1-800-387-2331). return BuyStock()