import logging from functools import reduce import datetime import talib.abstract as ta import pandas_ta as pta import logging import numpy as np import pandas as pd # from murrey_math import calculate_murrey_math_levels import freqtrade.vendor.qtpylib.indicators as qtpylib from technical import qtpylib from datetime import timedelta, datetime, timezone from pandas import DataFrame, Series from technical import qtpylib from typing import Optional from freqtrade.strategy.interface import IStrategy from technical.pivots_points import pivots_points from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_minutes from freqtrade.persistence import Trade from freqtrade.strategy import ( BooleanParameter, CategoricalParameter, DecimalParameter, IStrategy, IntParameter, RealParameter, merge_informative_pair, ) from scipy.signal import argrelextrema from typing import Optional from functools import reduce import warnings import math warnings.simplefilter(action="ignore", category=pd.errors.PerformanceWarning) logger = logging.getLogger(__name__) class TankAi15Modulus(IStrategy): exit_profit_only = True ### No selling at a loss use_custom_stoploss = True trailing_stop = False ignore_roi_if_entry_signal = True process_only_new_candles = True can_short = True use_exit_signal = True startup_candle_count: int = 200 stoploss = -0.99 timeframe = "15m" # DCA position_adjustment_enable = True max_entry_position_adjustment = 2 # initially set to 3 max_dca_multiplier = 1.5 safety_order_reserve = DecimalParameter(1, 10, default=1.5, space="buy", optimize=True) ### Custom Functions # Modulus peaks = IntParameter( 70, 100, default=80, space="buy", optimize=True ) ### initial smallest window bull_bear = IntParameter(120, 250, default=180, space="buy", optimize=True) trend = DecimalParameter( low=25, high=40, default=29.6, decimals=1, space="buy", optimize=True, load=True ) volatility = DecimalParameter( low=30, high=50, default=38.4, decimals=1, space="buy", optimize=True, load=True ) sensitivity = IntParameter(7, 15, default=11, space="buy", optimize=True, load=True) atr = IntParameter(3, 7, default=5, space="buy", optimize=True, load=True) # Custom Entry increment = DecimalParameter( low=1.0005, high=1.002, default=1.001, decimals=4, space="buy", optimize=True, load=True ) last_entry_price = None # trailing stop loss optimiziation tsl_target5 = DecimalParameter( low=0.3, high=0.4, default=0.3, decimals=2, space="sell", optimize=True, load=True ) ts5 = DecimalParameter( low=0.04, high=0.06, default=0.05, decimals=2, space="sell", optimize=True, load=True ) tsl_target4 = DecimalParameter( low=0.18, high=0.3, default=0.2, decimals=2, space="sell", optimize=True, load=True ) ts4 = DecimalParameter( low=0.03, high=0.05, default=0.045, decimals=3, space="sell", optimize=True, load=True ) tsl_target3 = DecimalParameter( low=0.12, high=0.18, default=0.15, decimals=2, space="sell", optimize=True, load=True ) ts3 = DecimalParameter( low=0.025, high=0.04, default=0.035, decimals=3, space="sell", optimize=True, load=True ) tsl_target2 = DecimalParameter( low=0.07, high=0.12, default=0.1, decimals=2, space="sell", optimize=True, load=True ) ts2 = DecimalParameter( low=0.015, high=0.03, default=0.02, decimals=2, space="sell", optimize=True, load=True ) tsl_target1 = DecimalParameter( low=0.04, high=0.07, default=0.06, decimals=3, space="sell", optimize=True, load=True ) ts1 = DecimalParameter( low=0.01, high=0.016, default=0.013, decimals=3, space="sell", optimize=True, load=True ) tsl_target0 = DecimalParameter( low=0.02, high=0.05, default=0.040, decimals=3, space="sell", optimize=True, load=True ) ts0 = DecimalParameter( low=0.008, high=0.015, default=0.012, decimals=3, space="sell", optimize=True, load=True ) minute1 = IntParameter(low=360, high=1000, default=360, space="sell", optimize=True, load=True) minute0 = IntParameter(low=240, high=720, default=240, space="sell", optimize=True, load=True) # protections cooldown_lookback = IntParameter(2, 48, default=1, space="protection", optimize=True) stop_duration = IntParameter(12, 200, default=4, space="protection", optimize=True) use_stop_protection = BooleanParameter(default=True, space="protection", optimize=True) minimal_roi = { "0": 0.40, "60": 0.35, "120": 0.30, "240": 0.25, "360": 0.20, "720": 0.15, "1440": 0.10, "2880": 0.075, "4320": 0.05, "5760": 0.03, "5761": 0.025, "7200": 0.005, } plot_config = { "main_plot": { "200sma": {"color": "#eec83f", "type": "line"}, "enter_tag": {"color": "#3ab61a"}, "exit_tag": {"color": "#6e16a4"}, }, "subplots": { "AI": { "do_predict": {"color": "#4f227c", "type": "bar"}, "minima": {"color": "#2a643c", "type": "line"}, "maxima": {"color": "#e277b7", "type": "line"}, "minima_check": {"color": "#2a643c", "type": "line"}, "maxima_check": {"color": "#e277b7", "type": "line"}, }, "order": {"order": {"color": "#20ba5f", "type": "line"}}, "max/min": { "max": {"color": "#8d299", "type": "line"}, "atr_pcnt": {"color": "#30e7da", "type": "line"}, "min": {"color": "#19f63a", "type": "line"}, }, "ATR Pcnt": { "atr_pcnt": {"color": "#2187f0", "type": "line"}, "atr_threshold": {"color": "#d9cd6c", "type": "line"}, }, }, } @property def protections(self): prot = [] prot.append( {"method": "CooldownPeriod", "stop_duration_candles": self.cooldown_lookback.value} ) if self.use_stop_protection.value: prot.append( { "method": "StoplossGuard", "lookback_period_candles": 24 * 3, "trade_limit": 2, "stop_duration_candles": self.stop_duration.value, "only_per_pair": False, } ) return prot ### Custom Functions ### # This is called when placing the initial order (opening trade) # Let unlimited stakes leave funds open for DCA orders def custom_stake_amount( self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: Optional[float], max_stake: float, leverage: float, entry_tag: Optional[str], side: str, **kwargs, ) -> float: proposed_stake = proposed_rate / (self.max_dca_multiplier + self.safety_order_reserve.value) return proposed_stake def adjust_trade_position( self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: Optional[float], max_stake: float, current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, **kwargs, ) -> Optional[float]: dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) filled_entries = trade.select_filled_orders(trade.entry_side) count_of_entries = trade.nr_of_successful_entries trade_duration = (current_time - trade.open_date_utc).seconds / 60 if current_profit is not None: logger.info( f"{trade.pair} - Current Profit: {current_profit} # of Entries: {trade.nr_of_successful_entries}" ) if current_profit > 0.10 and trade.nr_of_successful_exits == 0: return -(trade.stake_amount / 2) if current_profit > 0.15 and trade.nr_of_successful_exits == 1: return -(trade.stake_amount / 3) if current_profit > -0.015 and trade.nr_of_successful_entries == 1: return None if current_profit > -0.065 and trade.nr_of_successful_entries == 2: return None if current_profit > -0.125 and trade.nr_of_successful_entries == 3: return None if current_profit > -0.20 and trade.nr_of_successful_entries == 4: return None if current_profit > -0.25 and trade.nr_of_successful_entries == 5: return None try: stake_amount = filled_entries[0].cost if count_of_entries == 1: stake_amount *= 1 elif count_of_entries == 2: stake_amount *= 2 elif count_of_entries == 3: stake_amount *= 4 elif count_of_entries == 4: stake_amount *= 8 elif count_of_entries == 5: stake_amount *= 16 return stake_amount except Exception as exception: return None def custom_entry_price( self, pair: str, trade: Optional["Trade"], current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs, ) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe( pair=pair, timeframe=self.timeframe ) entry_price = ( dataframe["close"].iat[-1] + dataframe["open"].iat[-1] + proposed_rate + proposed_rate ) / 4 if proposed_rate < entry_price: entry_price = proposed_rate logger.info( f"{pair} Using Entry Price: {entry_price} | close: {dataframe['close'].iat[-1]} open: {dataframe['open'].iat[-1]} proposed_rate: {proposed_rate}" ) if self.last_entry_price is not None and abs(entry_price - self.last_entry_price) < 0.0001: entry_price *= self.increment.value logger.info( f"{pair} Incremented entry price: {entry_price} based on previous entry price : {self.last_entry_price}." ) self.last_entry_price = entry_price return entry_price def confirm_trade_exit( self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs, ) -> bool: if exit_reason == "roi" and trade.calc_profit_ratio(rate) < 0: logger.info(f"{trade.pair} roi is below 0") return False if exit_reason == "partial_exit" and trade.calc_profit_ratio(rate) < 0: logger.info(f"{trade.pair} partial exit is below 0") return False if exit_reason == "trailing_stop_loss" and trade.calc_profit_ratio(rate) < 0: logger.info(f"{trade.pair} trailing stop price is below 0") return False return True def custom_stoploss( self, pair: str, trade: "Trade", current_time: datetime, current_rate: float, current_profit: float, **kwargs, ) -> float: dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) current_candle = dataframe.iloc[-1].squeeze() trade_duration = (current_time - trade.open_date_utc).seconds / 60 total_trade_days = (current_time - trade.open_date_utc).days if current_candle["rsi"] < 90: for stop5 in self.tsl_target5.range: if current_profit > stop5: for stop5a in self.ts5.range: return stop5a for stop4 in self.tsl_target4.range: if current_profit > stop4: for stop4a in self.ts4.range: return stop4a for stop3 in self.tsl_target3.range: if current_profit > stop3: for stop3a in self.ts3.range: return stop3a for stop2 in self.tsl_target2.range: if current_profit > stop2: for stop2a in self.ts2.range: return stop2a if total_trade_days > 1: for stop1 in self.tsl_target1.range: if current_profit > stop1: for stop1a in self.ts1.range: return stop1a if total_trade_days < 1 and trade_duration < self.minute1.value: for stop1 in self.tsl_target1.range: if current_profit > stop1: for stop1a in self.ts1.range: return stop1a if total_trade_days > 2: for stop0 in self.tsl_target0.range: if current_profit > stop0: for stop0a in self.ts0.range: return stop0a if total_trade_days < 1 and trade_duration < self.minute0.value: for stop0 in self.tsl_target0.range: if current_profit > stop0: for stop0a in self.ts0.range: return stop0a else: for stop0 in self.tsl_target0.range: if current_profit > stop0: return 0.99 return self.stoploss # Leverage def leverage( self, pair: str, current_time: "datetime", current_rate: float, proposed_leverage: float, max_leverage: float, side: str, **kwargs, ) -> float: """ Customize leverage for each new trade. :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime :param current_rate: Rate, calculated based on pricing settings in exit_pricing. :param proposed_leverage: A leverage proposed by the bot. :param max_leverage: Max leverage allowed on this pair :param side: 'long' or 'short' - indicating the direction of the proposed trade :return: A leverage amount, which is between 1.0 and max_leverage. """ return 20 def feature_engineering_expand_all(self, dataframe, period, **kwargs): dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) dataframe["%-rocr-period"] = ta.ROCR(dataframe, timeperiod=period) dataframe["%-cmf-period"] = chaikin_mf(dataframe, periods=period) dataframe["%-chop-period"] = qtpylib.chopiness(dataframe, period) dataframe["%-linear-period"] = ta.LINEARREG_ANGLE(dataframe["close"], timeperiod=period) dataframe["%-atr-period"] = ta.ATR(dataframe, timeperiod=period) dataframe["%-atr-periodp"] = dataframe["%-atr-period"] / dataframe["close"] * 1000 return dataframe ### Custom Functions def custom_stake_amount( self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: Optional[float], max_stake: float, leverage: float, entry_tag: Optional[str], side: str, **kwargs, ) -> float: proposed_stake = proposed_stake / ( self.max_dca_multiplier + self.safety_order_reserve.value ) # Corrected variable name return proposed_stake ### Feature Engineering (continued) def feature_engineering_expand_basic(self, dataframe, metadata, **kwargs): dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-obv"] = ta.OBV(dataframe) dataframe["dpo"] = pta.dpo(dataframe["close"], length=40, centered=False) dataframe["%-dpo"] = dataframe["dpo"] dataframe["%-willr14"] = pta.willr(dataframe["high"], dataframe["low"], dataframe["close"]) vwap_low, vwap, vwap_high = VWAPB(dataframe, 20, 1) dataframe["%-vwap_upperband"] = vwap_high dataframe["%-vwap_middleband"] = vwap dataframe["%-vwap_lowerband"] = vwap_low dataframe["%-vwap_width"] = ( (dataframe["%-vwap_upperband"] - dataframe["%-vwap_lowerband"]) / dataframe["%-vwap_middleband"] ) * 100 dataframe["%-dist_to_vwap_upperband"] = get_distance( dataframe["close"], dataframe["%-vwap_upperband"] ) dataframe["%-dist_to_vwap_middleband"] = get_distance( dataframe["close"], dataframe["%-vwap_middleband"] ) dataframe["%-dist_to_vwap_lowerband"] = get_distance( dataframe["close"], dataframe["%-vwap_lowerband"] ) dataframe["%-tail"] = (dataframe["close"] - dataframe["low"]).abs() dataframe["%-wick"] = (dataframe["high"] - dataframe["close"]).abs() dataframe["%-rawclose"] = dataframe["close"] dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-raw_volume"] = dataframe["volume"] dataframe["%-raw_price"] = dataframe["close"] dataframe["%-raw_open"] = dataframe["open"] dataframe["%-raw_low"] = dataframe["low"] dataframe["%-raw_high"] = dataframe["high"] heikinashi = qtpylib.heikinashi(dataframe) dataframe["%-ha_open"] = heikinashi["open"] dataframe["%-ha_close"] = heikinashi["close"] dataframe["ha_open"] = heikinashi["open"] dataframe["ha_close"] = heikinashi["close"] dataframe["%-ha_high"] = heikinashi["high"] dataframe["%-ha_low"] = heikinashi["low"] dataframe["%-ha_closedelta"] = heikinashi["close"] - heikinashi["close"].shift() dataframe["%-ha_tail"] = heikinashi["close"] - heikinashi["low"] dataframe["%-ha_wick"] = heikinashi["high"] - heikinashi["close"] dataframe["%-HLC3"] = (heikinashi["high"] + heikinashi["low"] + heikinashi["close"]) / 3 murrey_math_levels = calculate_murrey_math_levels(dataframe) for level, value in murrey_math_levels.items(): dataframe[level] = value dataframe["%-+3/8"] = dataframe["[+3/8]P"] dataframe["%-+2/8"] = dataframe["[+2/8]P"] dataframe["%-+1/8"] = dataframe["[+1/8]P"] dataframe["%-8/8"] = dataframe["[8/8]P"] dataframe["%-7/8"] = dataframe["[7/8]P"] dataframe["%-6/8"] = dataframe["[6/8]P"] dataframe["%-5/8"] = dataframe["[5/8]P"] dataframe["%-4/8"] = dataframe["[4/8]P"] dataframe["%-3/8"] = dataframe["[3/8]P"] dataframe["%-2/8"] = dataframe["[2/8]P"] dataframe["%-1/8"] = dataframe["[1/8]P"] dataframe["%-0/8"] = dataframe["[0/8]P"] dataframe["%--1/8"] = dataframe["[-1/8]P"] dataframe["%--2/8"] = dataframe["[-2/8]P"] dataframe["%--3/8"] = dataframe["[-3/8]P"] dataframe["ema_2"] = ta.EMA(dataframe, timeperiod=2) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[+3/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[+2/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[+1/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[8/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[4/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[0/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[-1/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[-2/8]P"]) dataframe["%-distema2"] = get_distance(dataframe["ema_2"], dataframe["[-3/8]P"]) dataframe["%-entrythreshold4"] = dataframe["%-tail"] - dataframe["[0/8]P"] dataframe["%-entrythreshold5"] = dataframe["%-tail"] - dataframe["[-1/8]P"] dataframe["%-entrythreshold6"] = dataframe["%-tail"] - dataframe["[-2/8]P"] dataframe["%-entrythreshold7"] = dataframe["%-tail"] - dataframe["[-3/8]P"] dataframe["%-exitthreshold4"] = dataframe["%-wick"] - dataframe["[8/8]P"] dataframe["%-exitthreshold5"] = dataframe["%-wick"] - dataframe["[+1/8]P"] dataframe["%-exitthreshold6"] = dataframe["%-wick"] - dataframe["[+2/8]P"] dataframe["%-exitthreshold7"] = dataframe["%-wick"] - dataframe["[+3/8]P"] dataframe["mmlextreme_oscillator"] = 100 * ( (dataframe["close"] - dataframe["[-3/8]P"]) / (dataframe["[+3/8]P"] - dataframe["[-3/8]P"]) ) dataframe["%-mmlextreme_oscillator"] = dataframe["mmlextreme_oscillator"] dataframe["%-perc_change"] = (dataframe["high"] / dataframe["open"] - 1) * 100 dataframe["%-candle_1perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x >= 1, 1, 0).sum()) .shift() ) dataframe["%-candle_2perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x >= 2, 1, 0).sum()) .shift() ) dataframe["%-candle_3perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x >= 3, 1, 0).sum()) .shift() ) dataframe["%-candle_5perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x >= 5, 1, 0).sum()) .shift() ) dataframe["%-candle_-1perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x <= -1, -1, 0).sum()) .shift() ) dataframe["%-candle_-2perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x <= -2, -1, 0).sum()) .shift() ) dataframe["%-candle_-3perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x <= -3, -1, 0).sum()) .shift() ) dataframe["%-candle_-5perc_50"] = ( dataframe["%-perc_change"] .rolling(50) .apply(lambda x: np.where(x <= -5, -1, 0).sum()) .shift() ) dataframe["%-close_percentage"] = (dataframe["close"] - dataframe["low"]) / ( dataframe["high"] - dataframe["low"] ) dataframe["%-body_size"] = abs(dataframe["open"] - dataframe["close"]) dataframe["%-range_size"] = dataframe["high"] - dataframe["low"] dataframe["%-body_range_ratio"] = dataframe["%-body_size"] / dataframe["%-range_size"] dataframe["%-upper_wick_size"] = dataframe["high"] - dataframe[["open", "close"]].max( axis=1 ) dataframe["%-upper_wick_range_ratio"] = ( dataframe["%-upper_wick_size"] / dataframe["%-range_size"] ) lookback_period = 10 dataframe["%-max_high"] = dataframe["high"].rolling(lookback_period).max() dataframe["%-min_low"] = dataframe["low"].rolling(lookback_period).min() dataframe["%-close_position"] = (dataframe["close"] - dataframe["%-min_low"]) / ( dataframe["%-max_high"] - dataframe["%-min_low"] ) dataframe["%-current_candle_perc_change"] = ( dataframe["high"] / dataframe["open"] - 1 ) * 100 # Lazy Bear Impulse Macd dataframe["%-hi"] = ta.SMA(dataframe["high"], timeperiod=28) dataframe["%-lo"] = ta.SMA(dataframe["low"], timeperiod=28) dataframe["%-ema1"] = ta.EMA(dataframe["%-HLC3"], timeperiod=28) dataframe["%-ema2"] = ta.EMA(dataframe["%-ema1"], timeperiod=28) dataframe["%-d"] = dataframe["%-ema1"] - dataframe["%-ema2"] dataframe["%-mi"] = dataframe["%-ema1"] + dataframe["%-d"] dataframe["%-md"] = np.where( dataframe["%-mi"] > dataframe["%-hi"], dataframe["%-mi"] - dataframe["%-hi"], np.where( dataframe["%-mi"] < dataframe["%-lo"], dataframe["%-mi"] - dataframe["%-lo"], 0 ), ) dataframe["%-sb"] = ta.SMA(dataframe["%-md"], timeperiod=8) dataframe["%-sh"] = dataframe["%-md"] - dataframe["%-sb"] # WaveTrend using OHLC4 or HA close - 3/21 ap = 0.333 * (heikinashi["high"] + heikinashi["low"] + heikinashi["close"]) dataframe["esa"] = ta.EMA(ap, timeperiod=9) dataframe["d"] = ta.EMA(abs(ap - dataframe["esa"]), timeperiod=9) dataframe["%-wave_ci"] = (ap - dataframe["esa"]) / (0.015 * dataframe["d"]) dataframe["%-wave_t1"] = ta.EMA(dataframe["%-wave_ci"], timeperiod=12) dataframe["%-wave_t2"] = ta.SMA(dataframe["%-wave_t1"], timeperiod=4) # 200 SMA and distance dataframe["%-200sma"] = ta.SMA(dataframe, timeperiod=200) dataframe["%-200sma_dist"] = get_distance(heikinashi["close"], dataframe["%-200sma"]) return dataframe def feature_engineering_standard(self, dataframe, **kwargs): dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7 dataframe["%-hour_of_day"] = (dataframe["date"].dt.hour + 1) / 25 return dataframe def set_freqai_targets(self, dataframe, **kwargs): heikinashi = qtpylib.heikinashi(dataframe) dataframe["ha_open"] = heikinashi["open"] dataframe["ha_close"] = heikinashi["close"] dataframe["sma"] = ta.EMA(dataframe, timeperiod=184) dataframe["sma_pc"] = ( abs((dataframe["sma"] - dataframe["sma"].shift()) / dataframe["sma"]) * 100 ) dataframe["atr_pcnt"] = (qtpylib.atr(dataframe, window=self.atr.value)) / dataframe[ "ha_close" ] dataframe["modulation"] = ( 1 + (dataframe["sma_pc"] * self.trend.value) + (dataframe["atr_pcnt"] * self.volatility.value) ) min_window = self.peaks.value max_window = self.bull_bear.value dataframe["order"] = ( (dataframe["modulation"] * self.peaks.value) .round() .fillna(self.bull_bear.value) .astype(int) ) dataframe["order"] = np.where( dataframe["order"] > max_window, max_window, dataframe["order"] ) dataframe["order"] = np.where( dataframe["order"] < min_window, min_window, dataframe["order"] ) order = ( dataframe["order"].iloc[-1] if not dataframe["order"].empty else self.bull_bear.value ) dataframe["&s-extrema"] = 0 min_peaks = argrelextrema(dataframe["low"].values, np.less, order=order) max_peaks = argrelextrema(dataframe["high"].values, np.greater, order=order) for mp in min_peaks[0]: dataframe.at[mp, "&s-extrema"] = -1 for mp in max_peaks[0]: dataframe.at[mp, "&s-extrema"] = 1 dataframe["minima"] = np.where(dataframe["&s-extrema"] == -1, 1, 0) dataframe["maxima"] = np.where(dataframe["&s-extrema"] == 1, 1, 0) dataframe["&s-extrema"] = ( dataframe["&s-extrema"] .rolling(window=5, win_type="gaussian", center=True) .mean(std=0.5) ) dataframe["max"] = dataframe["close"].rolling(order).max() / dataframe["close"] - 1 dataframe["min"] = abs(dataframe["close"].rolling(order).min() / dataframe["close"] - 1) dataframe["mm_width"] = dataframe["max"] - dataframe["min"] dataframe["atr_threshold"] = dataframe["atr_pcnt"].rolling(order).max() return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["rsi"] = ta.RSI(dataframe) dataframe["200sma"] = ta.SMA(dataframe, timeperiod=200) dataframe = self.freqai.start(dataframe, metadata, self) murrey_math_levels = calculate_murrey_math_levels(dataframe) for level, value in murrey_math_levels.items(): dataframe[level] = value dataframe["mmlextreme_oscillator"] = 100 * ( (dataframe["close"] - dataframe["[4/8]P"]) / (dataframe["[+3/8]P"] - dataframe["[-3/8]P"]) ) dataframe["DI_catch"] = np.where(dataframe["DI_values"] > dataframe["DI_cutoff"], 0, 1) dataframe["minima_sort_threshold"] = dataframe["&s-minima_sort_threshold"] dataframe["maxima_sort_threshold"] = dataframe["&s-maxima_sort_threshold"] dataframe["min_threshold_mean"] = dataframe["minima_sort_threshold"].expanding().mean() dataframe["max_threshold_mean"] = dataframe["maxima_sort_threshold"].expanding().mean() dataframe["maxima_check"] = ( dataframe["maxima"].rolling(4).apply(lambda x: int((x != 1).all()), raw=True).fillna(0) ) dataframe["minima_check"] = ( dataframe["minima"].rolling(4).apply(lambda x: int((x != 1).all()), raw=True).fillna(0) ) pair = metadata["pair"] if dataframe["maxima"].iloc[-1] == 1 and dataframe["maxima_check"].iloc[-1] == 0: self.dp.send_msg(f"*** {pair} *** Maxima Detected - Potential Short!!!") if dataframe["minima"].iloc[-1] == 1 and dataframe["minima_check"].iloc[-1] == 0: self.dp.send_msg(f"*** {pair} *** Minima Detected - Potential Long!!!") return dataframe def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: df.loc[ (df["do_predict"] == 0) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & (df["atr_pcnt"] > df["min"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_long", "enter_tag"], ] = (1, "Minima Check Full Send") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & (df["order"] == self.bull_bear.value) & (df["atr_pcnt"] > df["min"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_long", "enter_tag"], ] = (1, "Minima Check Predicted Max Order") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & (df["atr_pcnt"] > df["min"]) & (df["order"] < self.bull_bear.value) & (df["order"] > 135) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_long", "enter_tag"], ] = (1, "Minima Check Predicted Mid Order") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & ((df["atr_threshold"] * 2) > df["min"]) & (df["order"] <= 135) & (df["order"] >= self.peaks.value) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_long", "enter_tag"], ] = (1, "Minima Check Predicted Min Order") # short df.loc[ (df["do_predict"] == 0) & (df["maxima_check"] == 0) & (df["&s-extrema"] > df["max"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_short", "enter_tag"], ] = (1, "Maxima Full Send") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["maxima_check"] == 0) & (df["maxima_check"].shift(4) == 1) & (df["order"] == self.bull_bear.value) & (df["atr_pcnt"] > df["max"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_short", "enter_tag"], ] = (1, "Maxima Check Predicted Max Order") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["maxima_check"] == 0) & (df["maxima_check"].shift(4) == 1) & ((df["atr_threshold"] * 2) > df["max"]) & (df["atr_pcnt"] > df["max"]) & (df["order"] < self.bull_bear.value) & (df["order"] > 135) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_short", "enter_tag"], ] = (1, "Maxima Check Predicted Mid Order1") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["maxima_check"] == 0) & (df["maxima_check"].shift(4) == 1) & ((df["atr_threshold"] * 2) > df["max"]) & (df["order"] <= 135) & (df["order"] >= self.peaks.value) & (df["volume"] > 0), # Make sure Volume is not 0 ["enter_short", "enter_tag"], ] = (1, "Maxima Check Predicted Min Order2") return df def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: df.loc[ (df["do_predict"] == 0) & (df["maxima_check"] == 0) & (df["&s-extrema"] > df["max"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_long", "exit_tag"], ] = (1, "Maxima Full Send") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["maxima_check"] == 0) & (df["maxima_check"].shift(4) == 1) & (df["order"] == self.bull_bear.value) & (df["atr_pcnt"] > df["max"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_long", "exit_tag"], ] = (1, "Maxima Check Predicted Max Order") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["maxima_check"] == 0) & (df["maxima_check"].shift(4) == 1) & ((df["atr_threshold"] * 2) > df["max"]) & (df["atr_pcnt"] > df["max"]) & (df["order"] < self.bull_bear.value) & (df["order"] > 135) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_long", "exit_tag"], ] = (1, "Maxima Check Predicted Mid Order1") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["maxima_check"] == 0) & (df["maxima_check"].shift(4) == 1) & ((df["atr_threshold"] * 2) > df["max"]) & (df["order"] <= 135) & (df["order"] >= self.peaks.value) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_long", "exit_tag"], ] = (1, "Maxima Check Predicted Min Order2") # short df.loc[ (df["do_predict"] == 0) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & (df["atr_pcnt"] > df["min"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_short", "exit_tag"], ] = (1, "Minima Check Full Send") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & (df["order"] == self.bull_bear.value) & (df["atr_pcnt"] > df["min"]) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_short", "exit_tag"], ] = (1, "Minima Check Predicted Max Order") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & (df["atr_pcnt"] > df["min"]) & (df["order"] < self.bull_bear.value) & (df["order"] > 135) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_short", "exit_tag"], ] = (1, "Minima Check Predicted Mid Order1") df.loc[ (df["do_predict"] == 1) & (df["DI_catch"] == 1) & (df["minima_check"] == 0) & (df["minima_check"].shift(4) == 1) & ((df["atr_threshold"] * 2) > df["min"]) & (df["order"] <= 135) & (df["order"] >= self.peaks.value) & (df["volume"] > 0), # Make sure Volume is not 0 ["exit_short", "exit_tag"], ] = (1, "Minima Check Predicted Min Order2") return df def top_percent_change(dataframe: DataFrame, length: int) -> float: if length == 0: return (dataframe["open"] - dataframe["close"]) / dataframe["close"] else: return (dataframe["open"].rolling(length).max() - dataframe["close"]) / dataframe["close"] def chaikin_mf(df, periods=20): close = df["close"] low = df["low"] high = df["high"] volume = df["volume"] mfv = ((close - low) - (high - close)) / (high - low) mfv = mfv.fillna(0.0) mfv *= volume cmf = mfv.rolling(periods).sum() / volume.rolling(periods).sum() return Series(cmf, name="cmf") def VWAPB(dataframe, window_size=20, num_of_std=1): df = dataframe.copy() df["vwap"] = qtpylib.rolling_vwap(df, window=window_size) rolling_std = df["vwap"].rolling(window=window_size).std() df["vwap_low"] = df["vwap"] - (rolling_std * num_of_std) df["vwap_high"] = df["vwap"] + (rolling_std * num_of_std) return df["vwap_low"], df["vwap"], df["vwap_high"] def get_distance(p1, p2): return abs((p1) - (p2)) def calculate_murrey_math_levels(df, window_size=64): # df = df.iloc[-window_size:] # Calculate rolling 64-bar maximum and minimum values rolling_max_H = df["high"].rolling(window=window_size).max() rolling_min_L = df["low"].rolling(window=window_size).min() max_H = rolling_max_H min_L = rolling_min_L range_HL = max_H - min_L def calculate_fractal(v2): fractal = 0 if 25000 < v2 <= 250000: fractal = 100000 elif 2500 < v2 <= 25000: fractal = 10000 elif 250 < v2 <= 2500: fractal = 1000 elif 25 < v2 <= 250: fractal = 100 elif 12.5 < v2 <= 25: fractal = 12.5 elif 6.25 < v2 <= 12.5: fractal = 12.5 elif 3.125 < v2 <= 6.25: fractal = 6.25 elif 1.5625 < v2 <= 3.125: fractal = 3.125 elif 0.390625 < v2 <= 1.5625: fractal = 1.5625 elif 0 < v2 <= 0.390625: fractal = 0.1953125 return fractal def calculate_octave(v1, v2, mn, mx): range_ = v2 - v1 sum_ = np.floor(np.log(calculate_fractal(v1) / range_) / np.log(2)) octave = calculate_fractal(v1) * (0.5**sum_) mn = np.floor(v1 / octave) * octave if mn + octave > v2: mx = mn + octave else: mx = mn + (2 * octave) return mx def calculate_x_values(v1, v2, mn, mx): dmml = (v2 - v1) / 8 x_values = [] # Calculate the midpoints of each segment midpoints = [mn + i * dmml for i in range(8)] for i in range(7): x_i = (midpoints[i] + midpoints[i + 1]) / 2 x_values.append(x_i) finalH = max(x_values) # Maximum of the x_values is the finalH return x_values, finalH def calculate_y_values(x_values, mn): y_values = [] for x in x_values: if x > 0: y = mn else: y = 0 y_values.append(y) return y_values def calculate_mml(mn, finalH, mx): dmml = ((finalH - finalL) / 8) * 1.0699 mml = (float([mx][0]) * 0.99875) + (dmml * 3) # mml = (float([mx]) * 0.99875) + (dmml * 3) ml = [] for i in range(0, 16): calc = mml - (dmml * (i)) ml.append(calc) murrey_math_levels = { "[-3/8]P": ml[14], "[-2/8]P": ml[13], "[-1/8]P": ml[12], "[0/8]P": ml[11], "[1/8]P": ml[10], "[2/8]P": ml[9], "[3/8]P": ml[8], "[4/8]P": ml[7], "[5/8]P": ml[6], "[6/8]P": ml[5], "[7/8]P": ml[4], "[8/8]P": ml[3], "[+1/8]P": ml[2], "[+2/8]P": ml[1], "[+3/8]P": ml[0], } return mml, murrey_math_levels for i in range(len(df)): mn = np.min(min_L.iloc[: i + 1]) mx = np.max(max_H.iloc[: i + 1]) x_values, finalH = calculate_x_values(mn, mx, mn, mx) y_values = calculate_y_values(x_values, mn) finalL = np.min(y_values) mml, murrey_math_levels = calculate_mml(finalL, finalH, mx) # Add Murrey Math levels to the DataFrame at each time step for level, value in murrey_math_levels.items(): df.at[df.index[i], level] = value return df def PC(dataframe, in1, in2): df = dataframe.copy() pc = ((in2 - in1) / in1) * 100 return pc