def test_is_market_open(): timezone = "Europe/London" tz = pytz.timezone(timezone) now_time = datetime.now(tz=tz).strftime("%H:%M") expected = Utils.is_between( str(now_time), ("07:55", "16:35")) and BankHolidays().is_work_day(datetime.now(tz=tz)) result = Utils.is_market_open(timezone) assert result == expected
def calculate_stop_limit( self, tradeDirection, current_offer, current_bid, limit_perc, stop_perc ): """ Calculate the stop and limit levels from the given percentages """ limit = None stop = None if tradeDirection == TradeDirection.BUY: limit = current_offer + Utils.percentage_of(limit_perc, current_offer) stop = current_bid - Utils.percentage_of(stop_perc, current_bid) elif tradeDirection == TradeDirection.SELL: limit = current_bid - Utils.percentage_of(limit_perc, current_bid) stop = current_offer + Utils.percentage_of(stop_perc, current_offer) return limit, stop
def wait_for_next_market_opening(self): """ Sleep until the next market opening. Takes into account weekends and bank holidays in UK """ seconds = Utils.get_seconds_to_market_opening(dt.datetime.now()) logging.info("Market is closed! Wait for {0:.2f} hours...".format( seconds / 3600)) time.sleep(seconds)
def is_market_open(self, timezone): """ Return True if the market is open, false otherwise - **timezone**: string representing the timezone """ tz = pytz.timezone(timezone) now_time = datetime.now(tz=tz).strftime("%H:%M") return BankHolidays().is_work_day(datetime.now(tz=tz)) and Utils.is_between( str(now_time), ("07:55", "16:35") )
def get_account_used_perc(self): """ Fetch the percentage of available balance is currently used - Returns the percentage of account used over total available amount """ balance, deposit = self.get_account_balances() if balance is None or deposit is None: return None return Utils.percentage(deposit, balance)
def test_get_seconds_to_market_opening(): now = datetime.now() seconds = Utils.get_seconds_to_market_opening(now) assert seconds > 0 assert seconds is not None opening = now + timedelta(seconds=seconds) assert 0 <= opening.weekday() < 5 expected = opening.replace(hour=8, minute=0, second=2, microsecond=0) diff = expected - opening assert diff.seconds < 10 # Test function if called after midnight but before market opening mock = datetime.now().replace(hour=3, minute=30, second=0, microsecond=0) seconds = Utils.get_seconds_to_market_opening(mock) assert seconds > 0 assert seconds is not None opening = mock + timedelta(seconds=seconds) # assert opening.day == mock.day assert opening.hour == 8 assert opening.minute == 0
def safety_checks(self): """ Perform some safety checks before running the strategy against the next market Return True if the trade can proceed, False otherwise """ percent_used = self.broker.get_account_used_perc() if percent_used is None: logging.warning( "Stop trading because can't fetch percentage of account used") raise NotSafeToTradeException() if percent_used >= self.max_account_usable: logging.warning( "Stop trading because {}% of account is used".format( str(percent_used))) raise NotSafeToTradeException() if not Utils.is_market_open(self.time_zone): logging.warning("Market is closed: stop processing") raise MarketClosedException()
def get_prices(self, epic_id, interval, data_range): """ Returns past prices for the given epic - **epic_id**: market epic as string - **interval**: resolution of the time series: minute, hours, etc. - **data_range**: amount of datapoint to fetch - Returns **None** if an error occurs otherwise the json object returned by IG API """ url = "{}/{}/{}/{}/{}".format(self.apiBaseURL, IG_API_URL.PRICES.value, epic_id, interval, data_range) d = self.http_get(url) if d is not None and "allowance" in d: remaining_allowance = d["allowance"]["remainingAllowance"] reset_time = Utils.humanize_time( int(d["allowance"]["allowanceExpiry"])) if remaining_allowance < 100: logging.warn("Remaining API calls left: {}".format( str(remaining_allowance))) logging.warn("Time to API Key reset: {}".format( str(reset_time))) return d
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 get_seconds_to_next_spin(self): """ Calculate the amount of seconds to wait for between each strategy spin """ # Run this strategy at market opening return Utils.get_seconds_to_market_opening(datetime.now())
def test_humanize_time(): assert Utils.humanize_time(3600) == "01:00:00" assert Utils.humanize_time(4800) == "01:20:00" assert Utils.humanize_time(4811) == "01:20:11"
def test_is_between(): mock = "10:10" assert Utils.is_between(mock, ("10:09", "10:11")) mock = "00:00" assert Utils.is_between(mock, ("23:59", "00:01"))
def test_percentage(): assert Utils.percentage(1, 100) == 1 assert Utils.percentage(0, 100) == 0 assert Utils.percentage(200, 100) == 200
def test_percentage_of(): assert Utils.percentage_of(1, 100) == 1 assert Utils.percentage_of(0, 100) == 0 assert Utils.percentage_of(1, 1) == 0.01
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