def find_trade_signal(self, market, datapoints): """ TODO add description of strategy key points """ limit_perc = self.limit_p stop_perc = max(market.stop_distance_min, self.stop_p) # Spread constraint if market.bid - market.offer > self.max_spread: return TradeDirection.NONE, None, None # Compute mid price current_mid = Utils.midpoint(market.bid, market.offer) high_prices = datapoints["high"] low_prices = datapoints["low"] close_prices = datapoints["close"] ltv = datapoints["volume"] # Check dataset integrity array_len_check = [] array_len_check.append(len(high_prices)) array_len_check.append(len(low_prices)) array_len_check.append(len(close_prices)) array_len_check.append(len(ltv)) if not all(x == array_len_check[0] for x in array_len_check): logging.error("Historic datapoints incomplete for {}".format(market.epic)) return TradeDirection.NONE, None, None # compute weighted average and std deviation of prices using volume as weight low_prices = numpy.ma.asarray(low_prices) high_prices = numpy.ma.asarray(high_prices) ltv = numpy.ma.asarray(ltv) low_weighted_avg, low_weighted_std_dev = self.weighted_avg_and_std( low_prices, ltv ) high_weighted_avg, high_weighted_std_dev = self.weighted_avg_and_std( high_prices, ltv ) # The VWAP can be used similar to moving averages, where prices above # the VWAP reflect a bullish sentiment and prices below the VWAP # reflect a bearish sentiment. Traders may initiate short positions as # a stock price moves below VWAP for a given time period or initiate # long position as the price moves above VWAP tmp_high_weight_var = float(high_weighted_avg + high_weighted_std_dev) tmp_low_weight_var = float(low_weighted_avg + low_weighted_std_dev) # e.g # series = [0,0,0,2,0,0,0,-2,0,0,0,2,0,0,0,-2,0] maxtab_high, _mintab_high = self.peakdet(high_prices, 0.3) _maxtab_low, mintab_low = self.peakdet(low_prices, 0.3) # convert to array so can work on min/max mintab_low_a = array(mintab_low)[:, 1] maxtab_high_a = array(maxtab_high)[:, 1] xb = range(0, len(mintab_low_a)) xc = range(0, len(maxtab_high_a)) mintab_low_a_slope, mintab_low_a_intercept, mintab_low_a_lo_slope, mintab_low_a_hi_slope = stats.mstats.theilslopes( mintab_low_a, xb, 0.99 ) maxtab_high_a_slope, maxtab_high_a_intercept, maxtab_high_a_lo_slope, maxtab_high_a_hi_slope = stats.mstats.theilslopes( maxtab_high_a, xc, 0.99 ) peak_count_high = 0 peak_count_low = 0 # how may "peaks" are BELOW the threshold for a in mintab_low_a: if float(a) < float(tmp_low_weight_var): peak_count_low += 1 # how may "peaks" are ABOVE the threshold for a in maxtab_high_a: if float(a) > float(tmp_high_weight_var): peak_count_high += 1 additional_checks_sell = [ int(peak_count_low) > int(peak_count_high), float(mintab_low_a_slope) < float(maxtab_high_a_slope), ] additional_checks_buy = [ int(peak_count_high) > int(peak_count_low), float(maxtab_high_a_slope) > float(mintab_low_a_slope), ] sell_rules = [ float(current_mid) >= float(numpy.max(maxtab_high_a)), all(additional_checks_sell), ] buy_rules = [ float(current_mid) <= float(numpy.min(mintab_low_a)), all(additional_checks_buy), ] trade_direction = TradeDirection.NONE if any(buy_rules): trade_direction = TradeDirection.BUY elif any(sell_rules): trade_direction = TradeDirection.SELL if trade_direction is TradeDirection.NONE: return trade_direction, None, None logging.info("Strategy says: {} {}".format(trade_direction.name, market.id)) ATR = self.calculate_stop_loss(close_prices, high_prices, low_prices) if trade_direction is TradeDirection.BUY: pip_limit = int( abs(float(max(high_prices)) - float(market.bid)) * self.profit_indicator_multiplier ) ce_stop = self.Chandelier_Exit_formula( trade_direction, ATR, min(low_prices) ) stop_pips = str(int(abs(float(market.bid) - (ce_stop)))) elif trade_direction is TradeDirection.SELL: pip_limit = int( abs(float(min(low_prices)) - float(market.bid)) * self.profit_indicator_multiplier ) ce_stop = self.Chandelier_Exit_formula( trade_direction, ATR, max(high_prices) ) stop_pips = str(int(abs(float(market.bid) - (ce_stop)))) esma_new_margin_req = int(Utils.percentage_of(self.ESMA_new_margin, market.bid)) if int(esma_new_margin_req) > int(stop_pips): stop_pips = int(esma_new_margin_req) # is there a case for a 20% drop? ... Especially over 18 weeks or # so? if int(stop_pips) > int(esma_new_margin_req): stop_pips = int(esma_new_margin_req) if int(pip_limit) == 0: # not worth the trade trade_direction = TradeDirection.NONE if int(pip_limit) == 1: # not worth the trade trade_direction = TradeDirection.NONE if int(pip_limit) >= int(self.greed_indicator): pip_limit = int(self.greed_indicator - 1) if int(stop_pips) > int(self.too_high_margin): logging.warning("Junk data for {}".format(market.epic)) return TradeDirection.NONE, None, None return trade_direction, pip_limit, stop_pips
def test_midpoint(): assert Utils.midpoint(0, 10) == 5 assert Utils.midpoint(-10, 10) == 0 assert Utils.midpoint(10, -10) == 0 assert Utils.midpoint(0, 0) == 0 assert Utils.midpoint(1, 2) == 1.5