Пример #1
0
    def handle_order_execution(self, order: Order, intrabar: Bar):
        amount = order.amount - order.executed_amount
        order.executed_amount = order.amount
        fee = self.taker_fee
        if order.limit_price:
            price = order.limit_price
            fee = self.maker_fee
        elif order.stop_price:
            price = int(
                order.stop_price *
                (1 + math.copysign(self.market_slipage_percent, order.amount) /
                 100) / self.symbol.tickSize) * self.symbol.tickSize
        else:
            price = intrabar.open * (
                1 +
                math.copysign(self.market_slipage_percent, order.amount) / 100)
        price = min(intrabar.high, max(
            intrabar.low,
            price))  # only prices within the bar. might mean less slipage
        order.executed_price = price
        self.account.open_position.quantity += amount
        delta = amount * (price if not self.symbol.isInverse else -1 / price)
        self.account.open_position.walletBalance -= delta
        self.account.open_position.walletBalance -= math.fabs(delta) * fee

        order.active = False
        order.execution_tstamp = intrabar.tstamp
        order.final_reason = 'executed'
        self.account.order_history.append(order)
        self.account.open_orders.remove(order)
        logger.debug("executed order " + order.id + " | " +
                     str(self.account.usd_equity) + " " +
                     str(self.account.open_position.quantity))
Пример #2
0
    def internal_send_order(self, order: Order):
        if order.limit_price is not None:
            order.limit_price = round(order.limit_price, self.symbol_object.pricePrecision)
            if order.stop_price is not None:
                order.stop_price = round(order.stop_price, self.symbol_object.pricePrecision)
                order_type = OrderType.STOP
            else:
                order_type = OrderType.LIMIT
        elif order.stop_price is not None:
            order.stop_price = round(order.stop_price, self.symbol_object.pricePrecision)
            order_type = OrderType.STOP_MARKET
        else:
            order_type = OrderType.MARKET

        order.amount = round(order.amount, self.symbol_object.quantityPrecision)
        quantityFormat = "{:." + str(self.symbol_object.quantityPrecision) + "f}"
        priceFormat = "{:." + str(self.symbol_object.pricePrecision) + "f}"
        # yes have to send the price and quantity in as str (although it wants float) cause otherwise it converts it
        # inernally and that sometimes f**k up the precision (0.023 -> 0.02299999999)
        resultOrder: binance_f.model.Order = self.client.post_order(
            symbol=self.symbol,
            side=OrderSide.BUY if order.amount > 0 else OrderSide.SELL,
            ordertype=order_type,
            timeInForce=TimeInForce.GTC if order_type in [OrderType.LIMIT, OrderType.STOP] else None,
            quantity=quantityFormat.format(abs(order.amount)),
            price=priceFormat.format(order.limit_price) if order.limit_price is not None else None,
            stopPrice=priceFormat.format(order.stop_price) if order.stop_price is not None else None,
            newClientOrderId=order.id)
        order.exchange_id = resultOrder.orderId
Пример #3
0
    def internal_send_order(self, order: Order):
        order_type = "Market"
        if order.stop_price is not None and (
                self.last - order.stop_price) * order.amount >= 0:
            order.stop_price = None  # already triggered
        if order.limit_price is not None:
            if order.stop_price is not None:
                order_type = "StopLimit"
            else:
                order_type = "Limit"
        elif order.stop_price is not None:
            order_type = "Stop"
            if (order.stop_price >= self.last and order.amount < 0) or \
                    (order.stop_price <= self.last and order.amount > 0):  # prevent error of "would trigger immediatly"
                order_type = "Market"

        params = dict(symbol=self.symbol,
                      clOrdID=order.id,
                      side="Buy" if order.amount > 0 else "Sell",
                      orderQty=abs(order.amount),
                      ordType=order_type,
                      stopPxEp=self.scale_price(order.stop_price),
                      priceEp=self.scale_price(order.limit_price),
                      triggerType="ByLastPrice"
                      if order.stop_price is not None else None)
        result = self.client.place_order(params)
        if "data" in result.keys() and "orderID" in result["data"]["orderID"]:
            order.exchange_id = result["data"]["orderID"]
Пример #4
0
    def internal_send_order(self, order: Order):
        order_type = "Market"
        if order.limit_price is not None:
            order_type = "Limit"
        result = None
        if order.stop_price is not None:
            # conditional order
            base_side = 1 if order.amount < 0 else -1  # buy stops are triggered when price goes higher (so it is
            # considered lower before)
            result = self._execute(
                self.bybit.Conditional.Conditional_new(
                    side=("Buy" if order.amount > 0 else "Sell"),
                    symbol=self.symbol,
                    order_type=order_type,
                    qty=abs(order.amount),
                    price=order.limit_price,
                    stop_px=order.stop_price,
                    order_link_id=order.id,
                    base_price=order.stop_price + base_side,
                    time_in_force="GoodTillCancel"))
            if result is not None:
                order.exchange_id = result['stop_order_id']

        else:
            result = self._execute(
                self.bybit.Order.Order_newV2(
                    side=("Buy" if order.amount > 0 else "Sell"),
                    symbol=self.symbol,
                    order_type=order_type,
                    qty=abs(order.amount),
                    price=order.limit_price,
                    order_link_id=order.id,
                    time_in_force="GoodTillCancel"))
            if result is not None:
                order.exchange_id = result['order_id']
Пример #5
0
 def update_order(self, order: Order):
     for existing_order in self.account.open_orders:
         if existing_order.id == order.id:
             self.account.open_orders.remove(existing_order)
             self.account.open_orders.append(order)
             order.tstamp = self.current_bars[0].last_tick_tstamp
             self.logger.debug("updated order %s" % (order.print_info()))
             break
Пример #6
0
    def handle_order_execution(self, order: Order, intrabar: Bar):
        amount = order.amount - order.executed_amount
        order.executed_amount = order.amount
        fee = self.taker_fee
        if order.limit_price:
            price = order.limit_price
            fee = self.maker_fee
        elif order.stop_price:
            price = int(
                order.stop_price *
                (1 + math.copysign(self.market_slipage_percent, order.amount) /
                 100) / self.symbol.tickSize) * self.symbol.tickSize
        else:
            price = intrabar.open * (
                1 +
                math.copysign(self.market_slipage_percent, order.amount) / 100)
        price = min(intrabar.high, max(
            intrabar.low,
            price))  # only prices within the bar. might mean less slipage
        order.executed_price = price
        oldAmount = self.account.open_position.quantity
        if oldAmount != 0:
            oldavgentry = self.account.open_position.avgEntryPrice
            if oldAmount * amount > 0:
                self.account.open_position.avgEntryPrice = (
                    oldavgentry * oldAmount + price * amount) / (oldAmount +
                                                                 amount)
            if oldAmount * amount < 0:
                if abs(oldAmount) < abs(amount):
                    profit = oldAmount * (
                        (price - oldavgentry) if not self.symbol.isInverse else
                        (-1 / price + 1 / oldavgentry))
                    self.account.open_position.walletBalance += profit
                    #close current, open new
                    self.account.open_position.avgEntryPrice = price
                else:
                    #closes the position by "-amount" cause amount is the side and direction of the close
                    profit = -amount * (
                        (price - oldavgentry) if not self.symbol.isInverse else
                        (-1 / price + 1 / oldavgentry))
                    self.account.open_position.walletBalance += profit
        else:
            self.account.open_position.avgEntryPrice = price
        self.account.open_position.quantity += amount
        volume = amount * (price if not self.symbol.isInverse else -1 / price)
        self.account.open_position.walletBalance -= math.fabs(volume) * fee

        order.active = False
        order.execution_tstamp = intrabar.tstamp
        order.final_reason = 'executed'
        self.account.order_history.append(order)
        self.account.open_orders.remove(order)
        self.logger.debug("executed order %s | %.0f %.2f | %.2f@ %.1f" %
                          (order.id, self.account.usd_equity,
                           self.account.open_position.quantity,
                           order.executed_amount, order.executed_price))
Пример #7
0
    def send_order(self, order: Order):
        # check if order is val
        if order.amount == 0:
            self.logger.error("trying to send order without amount")
            return
        self.logger.debug("added order %s" % (order.print_info()))

        order.tstamp = self.current_bars[0].tstamp
        if order not in self.account.open_orders:  # bot might add it himself temporarily.
            self.account.open_orders.append(order)
Пример #8
0
 def send_order(self, order: Order):
     if order.amount == 0:
         self.logger.error("trying to send order without amount")
         return
     if self.telegram_bot is not None:
         self.telegram_bot.send_log(
             "Sending (" + self.id + "): " + order.print_info(), order.id)
     order.tstamp = self.bars[0].tstamp
     if order not in self.account.open_orders:  # bot might add it himself temporarily.
         self.account.open_orders.append(order)
     self.exchange.send_order(order)
Пример #9
0
 def orderDictToOrder(self, o) -> Order:
     """
     {
             "bizError": 0,
             "orderID": "9cb95282-7840-42d6-9768-ab8901385a67",
             "clOrdID": "7eaa9987-928c-652e-cc6a-82fc35641706",
             "symbol": "BTCUSD",
             "side": "Buy",
             "actionTimeNs": 1580533011677666800,
             "transactTimeNs": 1580533011677666800,
             "orderType": null,
             "priceEp": 84000000,
             "price": 8400,
             "orderQty": 1,
             "displayQty": 1,
             "timeInForce": null,
             "reduceOnly": false,
             "stopPxEp": 0,
             "closedPnlEv": 0,
             "closedPnl": 0,
             "closedSize": 0,
             "cumQty": 0,
             "cumValueEv": 0,
             "cumValue": 0,
             "leavesQty": 0,
             "leavesValueEv": 0,
             "leavesValue": 0,
             "stopPx": 0,
             "stopDirection": "Falling",
             "ordStatus": "Untriggered"
         },
     """
     sideMult = -1 if o['side'] == Client.SIDE_SELL else 1
     stop = self.noneIfZero(
         o['stopPx']) if 'stopPx' in o else self.noneIfZero(
             o['stopPxEp'], True)
     price = self.noneIfZero(
         o['price']) if 'price' in o else self.noneIfZero(
             o['priceEp'], True)
     order = Order(orderId=o['clOrdID'],
                   stop=stop,
                   limit=price,
                   amount=o['orderQty'] * sideMult)
     order.exchange_id = o['orderID']
     order.tstamp = o['actionTimeNs'] / 1000000000
     order.active = o['ordStatus'] in [
         Client.ORDER_STATUS_NEW, Client.ORDER_STATUS_UNTRIGGERED,
         Client.ORDER_STATUS_TRIGGERED
     ]
     order.executed_amount = o['cumQty'] * sideMult
     val = o['cumValue'] if 'cumValue' in o else o[
         'cumValueEv'] / self.valueScale
     order.executed_price = o['cumQty'] / val if val != 0 else 0
     if order.executed_amount != 0:
         order.execution_tstamp = o['transactTimeNs'] / 1000000000
     order.stop_triggered = order.stop_price is not None and o[
         'ordStatus'] == Client.ORDER_STATUS_TRIGGERED
     return order
Пример #10
0
    def internal_send_order(self, order: Order):
        order_type = "Market"
        if order.limit_price is not None:
            order_type = "Limit"
        if order.stop_price is not None and (self.last - order.stop_price) * order.amount >= 0:
            order.stop_price = None  # already triggered

        orderType = TradingBot.order_type_from_order_id(order.id)

        if order.stop_price is not None:
            # conditional order
            base_side = self.symbol_info.tickSize * (
                1 if order.amount < 0 else -1)  # buy stops are triggered when price goes higher (so it is
            # considered lower before)
            normalizedStop = self.symbol_info.normalizePrice(order.stop_price, order.amount > 0)
            result = self._execute(self.bybit.LinearConditional.LinearConditional_new(side=("Buy" if order.amount > 0 else "Sell"),
                                                                          symbol=self.symbol,
                                                                          order_type=order_type,
                                                                          qty=strOrNone(
                                                                              self.symbol_info.normalizeSize(
                                                                                  abs(order.amount))),
                                                                          price=strOrNone(
                                                                              self.symbol_info.normalizePrice(
                                                                                  order.limit_price, order.amount < 0)),
                                                                          stop_px=strOrNone(normalizedStop),
                                                                          order_link_id=order.id,
                                                                          base_price=strOrNone(round(
                                                                              normalizedStop + base_side,
                                                                              self.symbol_info.pricePrecision)),
                                                                          time_in_force="GoodTillCancel",
                                                                          reduce_only= orderType != OrderType.ENTRY,
                                                                          close_on_trigger= orderType != OrderType.ENTRY))
            if result is not None:
                order.exchange_id = result['stop_order_id']

        else:
            result = self._execute(self.bybit.LinearOrder.LinearOrder_new(side=("Buy" if order.amount > 0 else "Sell"),
                                                              symbol=self.symbol,
                                                              order_type=order_type,
                                                              qty=strOrNone(
                                                                              self.symbol_info.normalizeSize(
                                                                                  abs(order.amount))),
                                                              price=strOrNone(
                                                                  self.symbol_info.normalizePrice(order.limit_price,
                                                                                                  order.amount < 0)),
                                                              order_link_id=order.id,
                                                              time_in_force="GoodTillCancel",
                                                              reduce_only= orderType != OrderType.ENTRY,
                                                              close_on_trigger= orderType != OrderType.ENTRY))
            if result is not None:
                order.exchange_id = result['order_id']
Пример #11
0
 def update_order(self, order: Order):
     for existing_order in self.account.open_orders:
         if existing_order.id == order.id:
             self.account.open_orders.remove(existing_order)
             self.account.open_orders.append(order)
             self.logger.debug("updated order %s" % (order.print_info()))
             break
Пример #12
0
    def internal_send_order(self, order: Order):
        order_type = "Market"
        if order.limit_price is not None:
            order_type = "Limit"
        if order.stop_price is not None and (
                self.last - order.stop_price) * order.amount >= 0:
            order.stop_price = None  # already triggered

        if order.stop_price is not None:
            # conditional order
            base_side = self.symbol_info.tickSize * (
                1 if order.amount < 0 else -1
            )  # buy stops are triggered when price goes higher (so it is
            # considered lower before)
            normalizedStop = self.symbol_info.normalizePrice(
                order.stop_price, order.amount > 0)
            result = self.handle_result(
                lambda: self.pybit.place_conditional_order(
                    side=("Buy" if order.amount > 0 else "Sell"),
                    symbol=self.symbol,
                    order_type=order_type,
                    qty=strOrNone(int(abs(order.amount))),
                    price=strOrNone(
                        self.symbol_info.normalizePrice(
                            order.limit_price, order.amount < 0)),
                    stop_px=strOrNone(normalizedStop),
                    order_link_id=order.id,
                    base_price=strOrNone(
                        round(normalizedStop + base_side, self.symbol_info.
                              pricePrecision)),
                    time_in_force="GoodTillCancel"))
            if result is not None:
                order.exchange_id = result['stop_order_id']

        else:
            result = self.handle_result(lambda: self.pybit.place_active_order(
                side=("Buy" if order.amount > 0 else "Sell"),
                symbol=self.symbol,
                order_type=order_type,
                qty=strOrNone(int(abs(order.amount))),
                price=strOrNone(
                    self.symbol_info.normalizePrice(order.limit_price, order.
                                                    amount < 0)),
                order_link_id=order.id,
                time_in_force="GoodTillCancel"))
            if result is not None:
                order.exchange_id = result['order_id']
Пример #13
0
 def update_order(self, order: Order):
     if self.telegram_bot is not None:
         self.telegram_bot.send_log(
             "updating (" + self.id + "): " + order.print_info(), order.id)
     self.exchange.update_order(order)
     self.exchange.on_tick_callback(
         True
     )  ##simulate tick to prevent early updates (need to wait for exchange to update order
Пример #14
0
    def position_got_opened_or_changed(self, position: Position,
                                       bars: List[Bar], account: Account,
                                       open_positions):
        other_id = TradingBot.get_other_direction_id(position.id)
        if other_id in open_positions.keys():
            open_positions[other_id].markForCancel = bars[0].tstamp

        # add stop
        gotStop = False  # safety check needed to not add multiple SL in case of an error
        gotTp = False
        for order in account.open_orders:
            orderType = TradingBot.order_type_from_order_id(order.id)
            posId = TradingBot.position_id_from_order_id(order.id)
            if orderType == OrderType.SL and posId == position.id:
                gotStop = True
                if abs(order.amount +
                       position.current_open_amount) > self.symbol.lotSize / 2:
                    order.amount = -position.current_open_amount
                    self.order_interface.update_order(order)
            elif self.tp_fac > 0 and orderType == OrderType.TP and posId == position.id:
                gotTp = True
                amount = self.symbol.normalizeSize(
                    -position.current_open_amount + order.executed_amount)
                if abs(order.amount - amount) > self.symbol.lotSize / 2:
                    order.amount = amount
                    self.order_interface.update_order(order)

        if not gotStop:
            order = Order(orderId=TradingBot.generate_order_id(
                positionId=position.id, type=OrderType.SL),
                          stop=position.initial_stop,
                          amount=-position.amount)
            self.order_interface.send_order(order)
        if self.tp_fac > 0 and not gotTp:
            ref = position.filled_entry - position.initial_stop
            #tp = position.filled_entry + ref * self.tp_fac
            data: Data = self.channel.get_data(bars[1])
            if order.amount < 0:
                tp = data.shortTrail
            else:
                tp = data.longTrail

            order = Order(orderId=TradingBot.generate_order_id(
                positionId=position.id, type=OrderType.TP),
                          limit=tp,
                          amount=-position.amount)
Пример #15
0
 def send_order(self, order: Order):
     if order.amount == 0:
         self.logger.error("trying to send order without amount")
         return
     order.tstamp = self.bars[0].tstamp
     if order not in self.account.open_orders:  # bot might add it himself temporarily.
         self.account.open_orders.append(order)
     self.exchange.send_order(order)
Пример #16
0
 def manage_open_position(self, position, bars, account, pos_ids_to_cancel):
     if self.close_after_bars >= 0 \
             and position.status == PositionStatus.OPEN \
             and position.entry_tstamp < bars[self.close_after_bars].tstamp:
         self.order_interface.send_order(
             Order(orderId=TradingBot.generate_order_id(
                 positionId=position.id, type=OrderType.SL),
                   amount=-position.currentOpenAmount))
Пример #17
0
    def position_got_opened(self, position: Position, bars: List[Bar],
                            account: Account, open_positions):

        gotTP = False
        gotSL = False
        for order in position.connectedOrders:
            type = TradingBot.order_type_from_order_id(order.id)
            if type == OrderType.TP:
                gotTP = True
                amount = self.symbol.normalizeSize(
                    -position.currentOpenAmount + order.executed_amount)
                if abs(order.amount - amount) > self.symbol.lotSize / 2:
                    order.amount = amount
                    self.order_interface.update_order(order)
            if type == OrderType.SL:
                gotSL = True
                amount = self.symbol.normalizeSize(-position.currentOpenAmount)
                if abs(order.amount - amount) > self.symbol.lotSize / 2:
                    order.amount = amount
                    self.order_interface.update_order(order)

        if not gotTP:
            slDiff = position.wanted_entry - position.initial_stop
            # reverse calc the std at time of signal and use tp factor accordingly
            tp = self.symbol.normalizePrice(
                position.wanted_entry + slDiff /
                (self.sl_factor - self.entry_factor) *
                (self.entry_factor - self.tp_factor), position.amount > 0)

            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    position.id, OrderType.TP),
                      amount=-position.currentOpenAmount,
                      stop=None,
                      limit=tp))
        if not gotSL:
            order = Order(orderId=TradingBot.generate_order_id(
                positionId=position.id, type=OrderType.SL),
                          stop=position.initial_stop,
                          amount=-position.currentOpenAmount)
            self.order_interface.send_order(order)
            # added temporarily, cause sync with open orders is in the next loop and otherwise the orders vs
            # position check fails
            if order not in account.open_orders:  # outside world might have already added it
                account.open_orders.append(order)
Пример #18
0
    def send_order(self, order: Order):
        # check if order is val
        if order.amount == 0:
            self.logger.error("trying to send order without amount")
            return
        [posId,
         order_type] = TradingBot.position_id_and_type_from_order_id(order.id)
        if order_type == OrderType.ENTRY:
            [unused, direction] = TradingBot.split_pos_Id(posId)
            if direction == PositionDirection.LONG and order.amount < 0:
                self.logger.error("sending long entry with negative amount")
            if direction == PositionDirection.SHORT and order.amount > 0:
                self.logger.error("sending short entry with positive amount")

        self.logger.debug("added order %s" % (order.print_info()))

        order.tstamp = self.current_bars[0].tstamp
        if order not in self.account.open_orders:  # bot might add it himself temporarily.
            self.account.open_orders.append(order)
Пример #19
0
    def get_orders(self) -> List[Order]:
        mexOrders = self.bitmex.open_orders()
        result: List[Order] = []
        for o in mexOrders:
            sideMulti = 1 if o["side"] == "Buy" else -1
            order = Order(orderId=o["clOrdID"],
                          stop=o["stopPx"],
                          limit=o["price"],
                          amount=o["orderQty"] * sideMulti)
            order.stop_triggered = o["triggered"] == "StopOrderTriggered"
            order.executed_amount = (o["cumQty"]) * sideMulti
            order.tstamp = parse_utc_timestamp(o['timestamp'])
            order.execution_tstamp = order.tstamp
            order.active = o['ordStatus'] == 'New'
            order.exchange_id = o["orderID"]
            order.executed_price = o["avgPx"]
            result.append(order)

        return result
Пример #20
0
    def position_got_opened(self, position: Position, bars: List[Bar], account: Account, open_positions):
        other_id = TradingBot.get_other_direction_id(position.id)
        if other_id in open_positions.keys():
            open_positions[other_id].markForCancel = bars[0].tstamp

        # add stop
        order = Order(orderId=TradingBot.generate_order_id(positionId=position.id,
                                                           type=OrderType.SL),
                      stop=position.initial_stop,
                      amount=-position.amount)
        self.order_interface.send_order(order)
        # added temporarily, cause sync with open orders is in the next loop and otherwise the orders vs
        # position check fails
        if order not in account.open_orders:  # outside world might have already added it
            account.open_orders.append(order)
Пример #21
0
 def convertOrder(self, apiOrder: binance_f.model.Order) -> Order:
     direction = 1 if apiOrder.side == OrderSide.BUY else -1
     order = Order(orderId=apiOrder.clientOrderId,
                   amount=apiOrder.origQty * direction,
                   limit=apiOrder.price,
                   stop=apiOrder.stopPrice)
     order.executed_amount = apiOrder.executedQty * direction
     order.executed_price = apiOrder.avgPrice
     order.active = apiOrder.status in ["NEW", "PARTIALLY_FILLED"]
     order.exchange_id = apiOrder.orderId
     return order
Пример #22
0
    def internal_send_order(self, order: Order):
        if order.limit_price is not None:
            if order.stop_price is not None:
                order_type = OrderType.STOP
            else:
                order_type = OrderType.LIMIT
        else:
            order_type = OrderType.STOP_MARKET

        resultOrder: binance_f.model.Order = self.client.post_order(
            symbol=self.symbol,
            side=OrderSide.BUY if order.amount > 0 else OrderSide.SELL,
            ordertype=order_type,
            timeInForce=TimeInForce.GTC,
            quantity=abs(order.amount),
            price=order.limit_price,
            stopPrice=order.stop_price,
            newClientOrderId=order.id)
        order.exchange_id = resultOrder.orderId
Пример #23
0
    def position_got_opened(self, position: Position, bars: List[Bar], account: Account, open_positions):
        other_id = TradingBot.get_other_direction_id(position.id)
        if other_id in open_positions.keys():
            open_positions[other_id].markForCancel = bars[0].tstamp

        # add stop
        gotStop= False # safety check needed to not add multiple SL in case of an error
        for order in account.open_orders:
            orderType = TradingBot.order_type_from_order_id(order.id)
            posId = TradingBot.position_id_from_order_id(order.id)
            if orderType == OrderType.SL and posId == position.id:
                gotStop= True
                break
        if not gotStop:
            order = Order(orderId=TradingBot.generate_order_id(positionId=position.id,
                                                           type=OrderType.SL),
                      stop=position.initial_stop,
                      amount=-position.amount)
            self.order_interface.send_order(order)
            # added temporarily, cause sync with open orders is in the next loop and otherwise the orders vs
            # position check fails
            if order not in account.open_orders:  # outside world might have already added it
                account.open_orders.append(order)
Пример #24
0
 def cancel_order(self, order: Order):
     if self.telegram_bot is not None:
         self.telegram_bot.send_log(
             "canceling (" + self.id + "): " + order.print_info(), order.id)
     order.active = False  # already mark it as cancelled, so not to mess up next loop
     self.exchange.cancel_order(order)
Пример #25
0
    def __open_position(self, direction, bars, swing, open_positions):
        directionFactor = 1
        oppDirection = PositionDirection.SHORT
        extreme = bars[1].low
        capFunc = min
        if direction == PositionDirection.SHORT:
            directionFactor = -1
            oppDirection = PositionDirection.LONG
            extreme = bars[1].high
            capFunc = max
        oppDirectionFactor = directionFactor * -1

        expectedEntrySplipagePerc = 0.0015
        expectedExitSlipagePerc = 0.0015

        data: Data = self.channel.get_data(bars[1])

        if self.close_on_opposite:
            for pos in open_positions.values():
                if pos.status == PositionStatus.OPEN and \
                        TradingBot.split_pos_Id(pos.id)[1] == oppDirection:
                    # execution will trigger close and cancel of other orders
                    self.order_interface.send_order(
                        Order(orderId=TradingBot.generate_order_id(
                            pos.id, OrderType.SL),
                              amount=-pos.amount,
                              stop=None,
                              limit=None))

        if self.init_stop_type == 1:
            stop = extreme
        elif self.init_stop_type == 2:
            stop = extreme + (extreme - bars[1].close) * 0.5
        else:
            stop = capFunc(swing, (extreme + bars[1].close) / 2)
        stop = stop + oppDirectionFactor  # buffer

        entry = bars[0].open
        signalId = self.get_signal_id(bars)

        #case long: entry * (1 + oppDirectionFactor*self.min_stop_diff_perc / 100) >= stop
        if 0 <= directionFactor*(entry * (1 + oppDirectionFactor*self.min_stop_diff_perc / 100) - stop) \
                or not self.ignore_on_tight_stop:
            stop = capFunc(
                stop,
                entry *
                (1 + oppDirectionFactor * self.min_stop_diff_perc / 100))

            amount = self.calc_pos_size(
                risk=self.risk_factor,
                exitPrice=stop *
                (1 + oppDirectionFactor * expectedExitSlipagePerc),
                entry=entry *
                (1 + directionFactor * expectedEntrySplipagePerc),
                atr=data.atr)

            posId = TradingBot.full_pos_id(signalId, direction)
            pos = Position(id=posId,
                           entry=entry,
                           amount=amount,
                           stop=stop,
                           tstamp=bars[0].tstamp)
            open_positions[posId] = pos
            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    posId, OrderType.ENTRY),
                      amount=amount,
                      stop=None,
                      limit=None))
            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    posId, OrderType.SL),
                      amount=-amount,
                      stop=stop,
                      limit=None))
            if self.tp_fac > 0:
                ref = entry - stop
                if self.tp_use_atr:
                    ref = math.copysign(data.atr, entry - stop)
                tp = entry + ref * self.tp_fac
                self.order_interface.send_order(
                    Order(orderId=TradingBot.generate_order_id(
                        posId, OrderType.TP),
                          amount=-amount,
                          stop=None,
                          limit=tp))
            pos.status = PositionStatus.OPEN
Пример #26
0
 def orderDictToOrder(o):
     sideMulti = 1 if o["side"] == "Buy" else -1
     ext = o['ext_fields'] if 'ext_fields' in o.keys() else None
     stop = o['trigger_price'] if 'trigger_price' in o.keys() else None
     if stop is None:
         stop = o['stop_px'] if 'stop_px' in o.keys() else None
     if stop is None and ext is not None and 'trigger_price' in ext.keys():
         stop = ext['trigger_price']
     order = Order(
         orderId=o["order_link_id"],
         stop=float(stop) if stop is not None else None,
         limit=float(o["price"]) if o['order_type'] == 'Limit' else None,
         amount=float(o["qty"] * sideMulti))
     if "order_status" in o.keys():
         order.stop_triggered = o[
             "order_status"] == "New" and stop is not None
         order.active = o['order_status'] == 'New' or o[
             'order_status'] == 'Untriggered'
     elif "stop_order_status" in o.keys():
         order.stop_triggered = o["stop_order_status"] == 'Triggered' or o[
             'stop_order_status'] == 'Active'
         order.active = o['stop_order_status'] == 'Triggered' or o[
             'stop_order_status'] == 'Untriggered'
     exec = o['cum_exec_qty'] if 'cum_exec_qty' in o.keys() else 0
     order.executed_amount = float(exec) * sideMulti
     order.tstamp = parse_utc_timestamp(o['timestamp'] if 'timestamp' in
                                        o.keys() else o['created_at'])
     order.exchange_id = o["order_id"] if 'order_id' in o.keys(
     ) else o['stop_order_id']
     order.executed_price = None
     if 'cum_exec_value' in o.keys() and 'cum_exec_qty' in o.keys(
     ) and float(o['cum_exec_value']) != 0:
         order.executed_price = o['cum_exec_qty'] / float(
             o["cum_exec_value"])  # cause of inverse
     return order
Пример #27
0
    def sync_positions_with_open_orders(self, bars: List[Bar], account: Account):
        open_pos = 0
        for pos in self.open_positions.values():
            pos.connectedOrders= [] # will be filled now
            if pos.status == PositionStatus.OPEN:
                open_pos += pos.amount

        if not self.got_data_for_position_sync(bars):
            self.logger.warn("got no initial data, can't sync positions")
            return

        remaining_pos_ids = []
        remaining_pos_ids += self.open_positions.keys()
        remaining_orders = []
        remaining_orders += account.open_orders

        # first check if there even is a diparity (positions without stops, or orders without position)
        for order in account.open_orders:
            if not order.active:
                remaining_orders.remove(order)
                continue  # got cancelled during run
            orderType = self.order_type_from_order_id(order.id)
            if orderType is None:
                remaining_orders.remove(order)
                continue  # none of ours
            posId = self.position_id_from_order_id(order.id)
            if posId in self.open_positions.keys():
                pos = self.open_positions[posId]
                pos.connectedOrders.append(order)
                remaining_orders.remove(order)
                if posId in remaining_pos_ids:
                    if (orderType == OrderType.SL and pos.status == PositionStatus.OPEN) \
                            or (orderType == OrderType.ENTRY and pos.status == PositionStatus.PENDING):
                        # only remove from remaining if its open with SL or pending with entry. every position needs
                        # a stoploss!
                        remaining_pos_ids.remove(posId)

        for pos in self.open_positions.values():
            self.check_open_orders_in_position(pos)

        if len(remaining_orders) == 0 and len(remaining_pos_ids) == 0 and abs(
                open_pos - account.open_position.quantity) < 0.1:
            return

        self.logger.info("Has to start order/pos sync with bot vs acc: %.3f vs. %.3f and %i vs %i, remaining: %i,  %i"
                         % (
                             open_pos, account.open_position.quantity, len(self.open_positions),
                             len(account.open_orders),
                             len(remaining_orders), len(remaining_pos_ids)))

        remainingPosition = account.open_position.quantity
        for pos in self.open_positions.values():
            if pos.status == PositionStatus.OPEN:
                remainingPosition -= pos.amount

        waiting_tps = []

        # now remaining orders and remaining positions contain the not matched ones
        for order in remaining_orders:
            orderType = self.order_type_from_order_id(order.id)
            posId = self.position_id_from_order_id(order.id)
            if not order.active:  # already canceled or executed
                continue

            if orderType == OrderType.ENTRY:
                # add position for unkown order
                stop = self.get_stop_for_unmatched_amount(order.amount, bars)
                if stop is not None:
                    newPos = Position(id=posId,
                                      entry=order.limit_price if order.limit_price is not None else order.stop_price,
                                      amount=order.amount,
                                      stop=stop,
                                      tstamp=bars[0].tstamp)
                    newPos.status = PositionStatus.PENDING if not order.stop_triggered else PositionStatus.TRIGGERED
                    self.open_positions[posId] = newPos
                    self.logger.warn("found unknown entry %s %.1f @ %.1f, added position"
                                     % (order.id, order.amount,
                                        order.stop_price if order.stop_price is not None else order.limit_price))
                else:
                    self.logger.warn(
                        "found unknown entry %s %.1f @ %.1f, but don't know what stop to use -> canceling"
                        % (order.id, order.amount,
                           order.stop_price if order.stop_price is not None else order.limit_price))
                    self.order_interface.cancel_order(order)

            elif orderType == OrderType.SL and remainingPosition * order.amount < 0 and abs(remainingPosition) > abs(
                    order.amount):
                # only assume open position for the waiting SL with the remainingPosition also indicates it, 
                # otherwise it might be a pending cancel (from executed TP) or already executed
                newPos = Position(id=posId, entry=None, amount=-order.amount,
                                  stop=order.stop_price, tstamp=bars[0].tstamp)
                newPos.status = PositionStatus.OPEN
                remainingPosition -= newPos.amount
                self.open_positions[posId] = newPos
                self.logger.warn("found unknown exit %s %.1f @ %.1f, opened position for it" % (
                    order.id, order.amount,
                    order.stop_price if order.stop_price is not None else order.limit_price))
            else:
                waiting_tps.append(order)

        # cancel orphaned TPs
        for order in waiting_tps:
            orderType = self.order_type_from_order_id(order.id)
            posId = self.position_id_from_order_id(order.id)
            if posId not in self.open_positions.keys():  # still not in (might have been added in previous for)
                self.logger.warn(
                    "didn't find matching position for order %s %.1f @ %.1f -> canceling"
                    % (order.id, order.amount,
                       order.stop_price if order.stop_price is not None else order.limit_price))
                self.order_interface.cancel_order(order)

        self.logger.info("found " + str(len(self.open_positions)) + " existing positions on sync")

        # positions with no exit in the market
        for posId in remaining_pos_ids:
            pos = self.open_positions[posId]
            if pos.status == PositionStatus.PENDING or pos.status == PositionStatus.TRIGGERED:
                # should have the opening order in the system, but doesn't
                # not sure why: in doubt: not create wrong orders
                if remainingPosition * pos.amount > 0 and abs(remainingPosition) >= abs(pos.amount):
                    # assume position was opened without us realizing (during downtime)
                    self.logger.warn(
                        "pending position with no entry order but open position looks like it was opened: %s" % (posId))
                    self.handle_opened_position(position=pos, order=None, bars=bars, account=account)
                    remainingPosition -= pos.amount
                else:
                    self.logger.warn(
                        "pending position with no entry order and no sign of opening -> close missed: %s" % (posId))
                    pos.status = PositionStatus.MISSED
                    self.position_closed(pos, account)
            elif pos.status == PositionStatus.OPEN:
                if remainingPosition == 0 and pos.initial_stop is not None:  # for some reason everything matches but we are missing the stop in the market
                    self.logger.warn(
                        "found position with no stop in market. added stop for it: %s with %.1f contracts" % (
                        posId, pos.amount))
                    self.order_interface.send_order(
                        Order(orderId=self.generate_order_id(posId, OrderType.SL), amount=-pos.amount,
                              stop=pos.initial_stop))
                else:
                    self.logger.warn(
                        "found position with no stop in market. %s with %.1f contracts. but remaining Position doesn't match so assume it was already closed." % (
                        posId, pos.amount))
                    self.position_closed(pos, account)
                    remainingPosition += pos.amount
            else:
                self.logger.warn(
                        "pending position with noconnection order not pending or open? closed: %s" % (posId))
                self.position_closed(pos, account)

        # now there should not be any mismatch between positions and orders.
        if remainingPosition != 0:
            unmatched_stop = self.get_stop_for_unmatched_amount(remainingPosition, bars)
            signalId = str(bars[1].tstamp) + '+' + str(randint(0, 99))
            if unmatched_stop is not None:
                posId = self.full_pos_id(signalId,
                                         PositionDirection.LONG if remainingPosition > 0 else PositionDirection.SHORT)
                newPos = Position(id=posId, entry=None, amount=remainingPosition,
                                  stop=unmatched_stop, tstamp=bars[0].tstamp)
                newPos.status = PositionStatus.OPEN
                self.open_positions[posId] = newPos
                # add stop
                self.logger.info(
                    "couldn't account for " + str(newPos.amount) + " open contracts. Adding position with stop for it")
                self.order_interface.send_order(Order(orderId=self.generate_order_id(posId, OrderType.SL),
                                                      stop=newPos.initial_stop, amount=-newPos.amount))
            elif account.open_position.quantity * remainingPosition > 0:
                self.logger.info(
                    "couldn't account for " + str(remainingPosition) + " open contracts. Market close")
                self.order_interface.send_order(Order(orderId=signalId + "_marketClose", amount=-remainingPosition))
            else:
                self.logger.info(
                    "couldn't account for " + str(
                        remainingPosition) + " open contracts. But close would increase exposure-> ignored")
Пример #28
0
    def run(self):
        self.reset()
        self.logger.info("starting backtest with " + str(len(self.bars)) +
                         " bars and " + str(self.account.equity) + " equity")
        for i in range(self.bot.min_bars_needed(), len(self.bars)):
            if i == len(self.bars) - 1 or i < self.bot.min_bars_needed():
                continue  # ignore last bar and first x

            # slice bars. TODO: also slice intrabar to simulate tick
            self.current_bars = self.bars[-(i + 1):]
            # add one bar with 1 tick on open to show to bot that the old one is closed
            next_bar = self.bars[-i - 2]
            forming_bar = Bar(tstamp=next_bar.tstamp,
                              open=next_bar.open,
                              high=next_bar.open,
                              low=next_bar.open,
                              close=next_bar.open,
                              volume=0,
                              subbars=[])
            self.current_bars.insert(0, forming_bar)
            self.current_bars[0].did_change = True
            self.current_bars[1].did_change = True

            self.do_funding()
            # self.bot.on_tick(self.current_bars, self.account)
            for subbar in reversed(next_bar.subbars):
                # check open orders & update account
                self.handle_open_orders(subbar)
                open = len(self.account.open_orders)
                forming_bar.add_subbar(subbar)
                self.bot.on_tick(self.current_bars, self.account)
                if open != len(self.account.open_orders):
                    self.handle_open_orders(subbar)  # got new ones
                self.current_bars[1].did_change = False

            next_bar.bot_data = forming_bar.bot_data
            for b in self.current_bars:
                if b.did_change:
                    b.did_change = False
                else:
                    break  # no need to go further

        if self.account.open_position.quantity != 0:
            self.send_order(
                Order(orderId="endOfTest",
                      amount=-self.account.open_position.quantity))
            self.handle_open_orders(self.bars[0].subbars[-1])

        if len(self.bot.position_history) > 0:
            daysInPos = 0
            maxDays = 0
            minDays = self.bot.position_history[0].daysInPos() if len(
                self.bot.position_history) > 0 else 0
            for pos in self.bot.position_history:
                if pos.status != PositionStatus.CLOSED:
                    continue
                if pos.exit_tstamp is None:
                    pos.exit_tstamp = self.bars[0].tstamp
                daysInPos += pos.daysInPos()
                maxDays = max(maxDays, pos.daysInPos())
                minDays = min(minDays, pos.daysInPos())
            daysInPos /= len(self.bot.position_history)

            profit = self.account.equity - self.initialEquity
            uw_updates_per_day = 1440  # every minute
            total_days = (self.bars[0].tstamp -
                          self.bars[-1].tstamp) / (60 * 60 * 24)
            rel = profit / (self.maxDD if self.maxDD > 0 else 1)
            rel_per_year = rel / (total_days / 365)
            self.logger.info(
                "finished | closed pos: " +
                str(len(self.bot.position_history)) + " | open pos: " +
                str(len(self.bot.open_positions)) + " | profit: " +
                ("%.2f" % (100 * profit / self.initialEquity)) + " | HH: " +
                ("%.2f" % (100 * (self.hh / self.initialEquity - 1))) +
                " | maxDD: " + ("%.2f" %
                                (100 * self.maxDD / self.initialEquity)) +
                " | maxExp: " + ("%.2f" %
                                 (self.maxExposure / self.initialEquity)) +
                " | rel: " + ("%.2f" % (rel_per_year)) + " | UW days: " +
                ("%.1f" % (self.max_underwater / uw_updates_per_day)) +
                " | pos days: " + ("%.1f/%.1f/%.1f" %
                                   (minDays, daysInPos, maxDays)))
        else:
            self.logger.info("finished with no trades")

        #self.write_results_to_files()
        return self
Пример #29
0
    def callback(self, data_type: 'SubscribeMessageType', event: 'any'):
        gotTick = False
        # refresh userdata every 15 min
        if self.lastUserDataKeep < time.time() - 15 * 60:
            self.lastUserDataKeep = time.time()
            self.client.keep_user_data_stream()
        # TODO: implement! (update bars, orders and account)
        if data_type == SubscribeMessageType.RESPONSE:
            pass  # what to do herE?
        elif data_type == SubscribeMessageType.PAYLOAD:
            if event.eventType == "kline":
                # {'eventType': 'kline', 'eventTime': 1587064627164, 'symbol': 'BTCUSDT',
                # 'data': <binance_f.model.candlestickevent.Candlestick object at 0x0000016B89856760>}
                if event.symbol == self.symbol:
                    candle: Candlestick = event.data
                    if len(self.candles) > 0:
                        if candle.startTime <= self.candles[
                                0].startTime and candle.startTime > self.candles[
                                    -1].startTime:
                            # somewhere inbetween to replace
                            for idx in range(0, len(self.candles)):
                                if candle.startTime == self.candles[
                                        idx].startTime:
                                    self.candles[idx] = candle
                                    break
                        elif candle.startTime > self.candles[0].startTime:
                            self.candles.insert(0, candle)
                            gotTick = True
                    else:
                        self.candles.append(candle)
                        gotTick = True
            elif (event.eventType == "ACCOUNT_UPDATE"):
                # {'eventType': 'ACCOUNT_UPDATE', 'eventTime': 1587063874367, 'transactionTime': 1587063874365,
                # 'balances': [<binance_f.model.accountupdate.Balance object at 0x000001FAF470E100>,...],
                # 'positions': [<binance_f.model.accountupdate.Position object at 0x000001FAF470E1C0>...]}
                usdBalance = 0
                for b in event.balances:
                    bal: Balance = b
                    if bal.asset == "USDT":
                        usdBalance = bal.walletBalance
                for p in event.positions:
                    pos: Position = p
                    if pos.symbol not in self.positions.keys():
                        self.positions[pos.symbol] = AccountPosition(
                            pos.symbol,
                            avgEntryPrice=float(pos.entryPrice),
                            quantity=float(pos.amount),
                            walletBalance=usdBalance
                            if "USDT" in pos.symbol else 0)
                    else:
                        accountPos = self.positions[pos.symbol]
                        accountPos.quantity = float(pos.amount)
                        accountPos.avgEntryPrice = float(pos.entryPrice)
                        if "USDT" in pos.symbol:
                            accountPos.walletBalance = usdBalance

            elif (event.eventType == "ORDER_TRADE_UPDATE"):
                # {'eventType': 'ORDER_TRADE_UPDATE', 'eventTime': 1587063513592, 'transactionTime': 1587063513589,
                # 'symbol': 'BTCUSDT', 'clientOrderId': 'web_ybDNrTjCi765K3AvOMRK', 'side': 'BUY', 'type': 'LIMIT',
                # 'timeInForce': 'GTC', 'origQty': 0.01, 'price': 6901.0, 'avgPrice': 0.0, 'stopPrice': 0.0,
                # 'executionType': 'NEW', 'orderStatus': 'NEW', 'orderId': 2705199704, 'lastFilledQty': 0.0,
                # 'cumulativeFilledQty': 0.0, 'lastFilledPrice': 0.0, 'commissionAsset': None, 'commissionAmount': None,
                # 'orderTradeTime': 1587063513589, 'tradeID': 0, 'bidsNotional': 138.81, 'asksNotional': 0.0,
                # 'isMarkerSide': False, 'isReduceOnly': False, 'workingType': 'CONTRACT_PRICE'}
                sideMulti = 1 if event.side == 'BUY' else -1
                order: Order = Order(orderId=event.clientOrderId,
                                     stop=event.stopPrice,
                                     limit=event.price,
                                     amount=event.origQty * sideMulti)
                order.exchange_id = event.orderId
                #FIXME: how do i know stop triggered on binance?
                #order.stop_triggered =
                order.executed_amount = event.cumulativeFilledQty * sideMulti
                order.executed_price = event.avgPrice
                order.tstamp = event.transactionTime
                order.execution_tstamp = event.orderTradeTime

                prev: Order = self.orders[
                    order.
                    exchange_id] if order.exchange_id in self.orders.keys(
                    ) else None
                if prev is not None:
                    if prev.tstamp > order.tstamp or abs(
                            prev.executed_amount) > abs(order.executed_amount):
                        # already got newer information, probably the info of the stop order getting
                        # triggered, when i already got the info about execution
                        self.logger.info("ignoring delayed update for %s " %
                                         (prev.id))
                    if order.stop_price is None:
                        order.stop_price = prev.stop_price
                    if order.limit_price is None:
                        order.limit_price = prev.limit_price
                prev = order
                if not prev.active and prev.execution_tstamp == 0:
                    prev.execution_tstamp = datetime.utcnow().timestamp()
                self.orders[order.exchange_id] = prev

                self.logger.info("received order update: %s" % (str(order)))
        else:
            self.logger.warn("Unknown Data in websocket callback")

        if gotTick and self.on_tick_callback is not None:
            self.on_tick_callback()  # got something new
Пример #30
0
    def open_orders(self, is_new_bar, directionFilter, bars, account,
                    open_positions):
        if (not is_new_bar) or len(bars) < self.min_bars_needed():
            return  # only open orders on beginning of bar

        if not self.entries_allowed(bars):
            self.logger.info("no entries allowed")
            return

        # check for signal. we are at the open of the new bar. so bars[0] contains of only 1 tick.
        # we look at data bars[1] and bars[2]
        prevFast = self.fastMA.get_data(bars[2])
        currentFast = self.fastMA.get_data(bars[1])
        prevSlow = self.slowMA.get_data(bars[2])
        currentSlow = self.slowMA.get_data(bars[1])
        swingData: Data = self.swings.get_data(bars[1])  # for stops

        # include the expected slipage in the risk calculation
        expectedEntrySplipagePerc = 0.0015
        expectedExitSlipagePerc = 0.0015
        signalId = "MACross+" + str(bars[0].tstamp)

        if prevFast <= prevSlow and currentFast > currentSlow:
            # cross up -> long entry
            entry = bars[0].open  # current price
            stop = swingData.swingLow
            if stop is None:
                stop = lowest(bars, self.swings.before + self.swings.after, 1,
                              BarSeries.LOW)
            amount = self.calc_pos_size(
                risk=self.risk_factor,
                exitPrice=stop * (1 - expectedExitSlipagePerc),
                entry=entry * (1 + expectedEntrySplipagePerc))

            # open the position and save it
            posId = TradingBot.full_pos_id(signalId, PositionDirection.LONG)
            pos = Position(id=posId,
                           entry=entry,
                           amount=amount,
                           stop=stop,
                           tstamp=bars[0].tstamp)
            open_positions[posId] = pos
            # send entry as market, immediatly send SL too
            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    posId, OrderType.ENTRY),
                      amount=amount,
                      stop=None,
                      limit=None))
            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    posId, OrderType.SL),
                      amount=-amount,
                      stop=stop,
                      limit=None))
            pos.status = PositionStatus.OPEN

        elif prevFast >= prevSlow and currentFast < currentSlow:
            # cross down -> short entry
            entry = bars[0].open  # current price
            stop = swingData.swingHigh
            if stop is None:
                stop = highest(bars, self.swings.before + self.swings.after, 1,
                               BarSeries.HIGH)
            amount = self.calc_pos_size(
                risk=self.risk_factor,
                exitPrice=stop * (1 + expectedExitSlipagePerc),
                entry=entry * (1 - expectedEntrySplipagePerc))

            # open the position and save it
            posId = TradingBot.full_pos_id(signalId, PositionDirection.SHORT)
            pos = Position(id=posId,
                           entry=entry,
                           amount=amount,
                           stop=stop,
                           tstamp=bars[0].tstamp)
            open_positions[posId] = pos
            # send entry as market, immediatly send SL too
            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    posId, OrderType.ENTRY),
                      amount=amount,
                      stop=None,
                      limit=None))
            self.order_interface.send_order(
                Order(orderId=TradingBot.generate_order_id(
                    posId, OrderType.SL),
                      amount=-amount,
                      stop=stop,
                      limit=None))
            pos.status = PositionStatus.OPEN