예제 #1
0
class ChannelStrategy(StrategyWithExitModulesAndFilter):
    def __init__(self):
        super().__init__()
        self.channel: KuegiChannel = None
        self.trail_to_swing = False
        self.delayed_swing_trail = True
        self.trail_back = False
        self.trail_active = False

    def myId(self):
        return "ChannelStrategy"

    def withChannel(self, max_look_back, threshold_factor, buffer_factor,
                    max_dist_factor, max_swing_length):
        self.channel = KuegiChannel(max_look_back, threshold_factor,
                                    buffer_factor, max_dist_factor,
                                    max_swing_length)
        return self

    def withTrail(self,
                  trail_to_swing: bool = False,
                  delayed_swing: bool = True,
                  trail_back: bool = False):
        self.trail_active = True
        self.delayed_swing_trail = delayed_swing
        self.trail_to_swing = trail_to_swing
        self.trail_back = trail_back
        return self

    def init(self, bars: List[Bar], account: Account, symbol: Symbol):
        super().init(bars, account, symbol)
        if self.channel is None:
            self.logger.error("No channel provided on init")
        else:
            self.logger.info(
                "init with %i %.1f %.3f %.1f %i | %.3f %.1f %i %.1f | %s %s %s %s"
                % (self.channel.max_look_back, self.channel.threshold_factor,
                   self.channel.buffer_factor, self.channel.max_dist_factor,
                   self.channel.max_swing_length, self.risk_factor,
                   self.max_risk_mul, self.risk_type, self.atr_factor_risk,
                   self.trail_active, self.delayed_swing_trail,
                   self.trail_to_swing, self.trail_back))
            self.channel.on_tick(bars)

    def min_bars_needed(self) -> int:
        return self.channel.max_look_back + 1

    def got_data_for_position_sync(self, bars: List[Bar]) -> bool:
        result = super().got_data_for_position_sync(bars)
        return result and (self.channel.get_data(bars[1]) is not None)

    def get_stop_for_unmatched_amount(self, amount: float, bars: List[Bar]):
        # ignore possible stops from modules for now
        data = self.channel.get_data(bars[1])
        stopLong = int(
            max(data.shortSwing, data.longTrail) if data.
            shortSwing is not None else data.longTrail)
        stopShort = int(
            min(data.longSwing, data.shortTrail) if data.
            longSwing is not None else data.shortTrail)
        stop = stopLong if amount > 0 else stopShort

    def prep_bars(self, is_new_bar: bool, bars: list):
        if is_new_bar:
            self.channel.on_tick(bars)

    def manage_open_order(self, order, position, bars, to_update, to_cancel,
                          open_positions):
        # first the modules
        super().manage_open_order(order, position, bars, to_update, to_cancel,
                                  open_positions)
        # now the channel stuff
        last_data: Data = self.channel.get_data(bars[2])
        data: Data = self.channel.get_data(bars[1])
        if data is not None:
            stopLong = data.longTrail
            stopShort = data.shortTrail
            if self.trail_to_swing and \
                    data.longSwing is not None and data.shortSwing is not None and \
                    (not self.delayed_swing_trail or (last_data is not None and
                                                      last_data.longSwing is not None and
                                                      last_data.shortSwing is not None)):
                stopLong = max(data.shortSwing, stopLong)
                stopShort = min(data.longSwing, stopShort)

            orderType = TradingBot.order_type_from_order_id(order.id)
            if position is not None and orderType == OrderType.SL:
                # trail
                newStop = order.stop_price
                isLong = position.amount > 0
                if self.trail_active:
                    trail = stopLong if isLong else stopShort
                    if (trail - newStop) * position.amount > 0 or \
                            (self.trail_back and position.initial_stop is not None
                                and (trail - position.initial_stop) * position.amount > 0):
                        newStop = math.floor(
                            trail) if not isLong else math.ceil(trail)

                if newStop != order.stop_price:
                    order.stop_price = newStop
                    to_update.append(order)

    def add_to_plot(self, fig: go.Figure, bars: List[Bar], time):
        super().add_to_plot(fig, bars, time)
        lines = self.channel.get_number_of_lines()
        styles = self.channel.get_line_styles()
        names = self.channel.get_line_names()
        offset = 1  # we take it with offset 1
        self.logger.info("adding channel")
        for idx in range(0, lines):
            sub_data = list(
                map(lambda b: self.channel.get_data_for_plot(b)[idx], bars))
            fig.add_scatter(x=time,
                            y=sub_data[offset:],
                            mode='lines',
                            line=styles[idx],
                            name=self.channel.id + "_" + names[idx])
예제 #2
0
class BotWithChannel(TradingBot):
    def __init__(self, logger, directionFilter: int = 0):
        super().__init__(logger, directionFilter)
        self.channel: KuegiChannel = None
        self.risk_factor = 1
        self.risk_type = 0  # 0= all equal, 1= 1 atr eq 1 R
        self.max_risk_mul = 1
        self.be_factor = 0
        self.be_buffer = 0
        self.trail_to_swing = False
        self.delayed_swing_trail = True
        self.trail_back = False
        self.trail_active = False

    def withRM(self,
               risk_factor: float = 0.01,
               max_risk_mul: float = 2,
               risk_type: int = 0):
        self.risk_factor = risk_factor
        self.risk_type = risk_type  # 0= all equal, 1= 1 atr eq 1 R
        self.max_risk_mul = max_risk_mul
        return self

    def withChannel(self, max_look_back, threshold_factor, buffer_factor,
                    max_dist_factor, max_swing_length):
        self.channel = KuegiChannel(max_look_back, threshold_factor,
                                    buffer_factor, max_dist_factor,
                                    max_swing_length)
        return self

    def withBE(self, factor, buffer):
        self.be_factor = factor
        self.be_buffer = buffer
        return self

    def withTrail(self,
                  trail_to_swing: bool = False,
                  delayed_swing: bool = True,
                  trail_back: bool = False):
        self.trail_active = True
        self.delayed_swing_trail = delayed_swing
        self.trail_to_swing = trail_to_swing
        self.trail_back = trail_back
        return self

    def init(self,
             bars: List[Bar],
             account: Account,
             symbol: Symbol,
             unique_id: str = ""):
        if self.channel is None:
            self.logger.error("No channel provided on init")
        else:
            self.logger.info(
                "init %s with %i %.1f %.3f %.1f %i | %.3f %.1f %i | %.1f %.1f | %s %s %s %s"
                % (unique_id, self.channel.max_look_back,
                   self.channel.threshold_factor, self.channel.buffer_factor,
                   self.channel.max_dist_factor, self.channel.max_swing_length,
                   self.risk_factor, self.max_risk_mul, self.risk_type,
                   self.be_factor, self.be_buffer, self.trail_active,
                   self.delayed_swing_trail, self.trail_to_swing,
                   self.trail_back))
            self.channel.on_tick(bars)
        super().init(bars=bars,
                     account=account,
                     symbol=symbol,
                     unique_id=unique_id)

    def min_bars_needed(self):
        return self.channel.max_look_back + 1

    def prep_bars(self, bars: list):
        if self.is_new_bar:
            self.channel.on_tick(bars)

    def got_data_for_position_sync(self, bars: List[Bar]):
        return self.channel.get_data(bars[1]) is not None

    def get_stop_for_unmatched_amount(self, amount: float, bars: List[Bar]):
        data = self.channel.get_data(bars[1])
        stopLong = int(
            max(data.shortSwing, data.longTrail) if data.
            shortSwing is not None else data.longTrail)
        stopShort = int(
            min(data.longSwing, data.shortTrail) if data.
            longSwing is not None else data.shortTrail)
        return stopLong if amount > 0 else stopShort

    def manage_open_orders(self, bars: List[Bar], account: Account):
        self.sync_executions(bars, account)

        # Trailing
        if len(bars) < 5:
            return

        # trail stop only on new bar
        last_data: Data = self.channel.get_data(bars[2])
        data: Data = self.channel.get_data(bars[1])
        if data is not None:
            stopLong = data.longTrail
            stopShort = data.shortTrail
            if self.trail_to_swing and \
                    data.longSwing is not None and data.shortSwing is not None and \
                    (not self.delayed_swing_trail or (last_data is not None and
                                                      last_data.longSwing is not None and
                                                      last_data.shortSwing is not None)):
                stopLong = max(data.shortSwing, stopLong)
                stopShort = min(data.longSwing, stopShort)

            to_update = []
            for order in account.open_orders:
                posId = self.position_id_from_order_id(order.id)
                if posId not in self.open_positions.keys():
                    continue
                pos: Position = self.open_positions[posId]
                orderType = self.order_type_from_order_id(order.id)
                if pos is not None and orderType == OrderType.SL:
                    # trail
                    newStop = order.stop_price
                    isLong = pos.amount > 0
                    if self.trail_active:
                        newStop = self.__trail_stop(
                            direction=1 if isLong else -1,
                            current_stop=newStop,
                            trail=stopLong if isLong else stopShort,
                            initial_stop=pos.initial_stop)

                    if self.be_factor > 0 and pos.wanted_entry is not None and pos.initial_stop is not None:
                        entry_diff = (pos.wanted_entry - pos.initial_stop)
                        ep = bars[0].high if isLong else bars[0].low
                        if (ep -
                            (pos.wanted_entry +
                             entry_diff * self.be_factor)) * pos.amount > 0:
                            newStop = self.__trail_stop(
                                direction=1 if isLong else -1,
                                current_stop=newStop,
                                trail=pos.wanted_entry +
                                entry_diff * self.be_buffer,
                                initial_stop=pos.initial_stop)
                    if newStop != order.stop_price:
                        order.stop_price = newStop
                        to_update.append(order)

            for order in to_update:
                self.order_interface.update_order(order)

    def calc_pos_size(self, risk, entry, exitPrice, data: Data):
        if self.risk_type <= 2:
            delta = entry - exitPrice
            if self.risk_type == 1:
                # use atr as delta reference, but max X the actual delta. so risk is never more than X times the
                # wanted risk
                delta = math.copysign(
                    min(self.max_risk_mul * abs(delta),
                        self.max_risk_mul * data.atr), delta)

            if not self.symbol.isInverse:
                size = risk / delta
            else:
                size = -int(risk / (1 / entry - 1 / (entry - delta)))
            return size

    def __trail_stop(self, direction, current_stop, trail, initial_stop):
        # direction should be > 0 for long and < 0 for short
        if (trail - current_stop) * direction > 0 or \
                (self.trail_back and initial_stop is not None and (trail - initial_stop) * direction > 0):
            return math.floor(trail) if direction < 0 else math.ceil(trail)
        else:
            return current_stop

    def add_to_plot(self, fig: go.Figure, bars: List[Bar], time):
        super().add_to_plot(fig, bars, time)
        lines = self.channel.get_number_of_lines()
        styles = self.channel.get_line_styles()
        names = self.channel.get_line_names()
        offset = 1  # we take it with offset 1
        self.logger.info("adding channel")
        for idx in range(0, lines):
            sub_data = list(
                map(lambda b: self.channel.get_data_for_plot(b)[idx], bars))
            fig.add_scatter(x=time,
                            y=sub_data[offset:],
                            mode='lines',
                            line=styles[idx],
                            name=self.channel.id + "_" + names[idx])