예제 #1
0
    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')
예제 #2
0
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
예제 #3
0
    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')
예제 #4
0
    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')
예제 #5
0
    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')
예제 #6
0
    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')
예제 #7
0
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
예제 #8
0
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
예제 #9
0
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)
예제 #10
0
def save_passenger(passenger: AutoPilotTask):
    passenger.save()
예제 #11
0
def delete_passenger(passenger: AutoPilotTask):
    passenger.delete()
예제 #12
0
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}'
예제 #13
0
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
예제 #14
0
    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()
예제 #15
0
    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()