"""EmaCrossFunding + filtr pozycjonowania tlumu. Do trzech regul EmaCross i filtra funding dokladamy czwarty sygnal z danych pozarynkowych: stosunek kont long do short na Binance Futures. Dlaczego akurat ten: w tescie na przeplywie zlecen byl jedynym obok "duzi kontra tlum", ktory na obu polowach danych mial ten sam znak i przewage powyzej progu kosztow (-42.7 bps na horyzoncie dobowym). Wszystko inne miescilo sie w szumie albo zmienialo znak. Kierunek jest kontrarianski: gdy wiekszosc kont siedzi na long, zwroty w kolejnej dobie sa nizsze. Uzywamy tego jednostronnie - nie wchodzimy w maksymalnie wylewarowany rynek. Tak samo jak filtr funding: sygnal moze sie odwrocic, wiec nie budujemy na nim kierunku, tylko unikamy skrajnosci. ZASTRZEZENIE: efekt slabnie. -76 bps w pierwszej polowie proby, -14 w drugiej. """ from __future__ import annotations from pathlib import Path import freqtrade.vendor.qtpylib.indicators as qtpylib import pandas as pd import talib.abstract as ta from pandas import DataFrame from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy WEB = Path(__file__).resolve().parents[2] / "research/webdata" class EmaCrossFlow(IStrategy): INTERFACE_VERSION = 3 timeframe = "1h" can_short = False process_only_new_candles = True startup_candle_count = 1300 minimal_roi = {"0": 10} stoploss = -0.15 trailing_stop = True ema_period = IntParameter(300, 1300, default=600, space="buy", optimize=True) exit_threshold = DecimalParameter(0.3, 3.0, default=2.0, decimals=1, space="sell", optimize=True) funding_max_pct = IntParameter(50, 100, default=55, space="buy", optimize=True) crowd_max_pct = IntParameter(50, 100, default=100, space="buy", optimize=True) _funding = None _crowd = None @classmethod def _load(cls) -> None: if cls._funding is None: f = pd.read_feather(WEB / "funding.feather") f["date"] = pd.to_datetime(f["date"], utc=True) cls._funding = f.set_index("date").sort_index()["funding"] if cls._crowd is None: o = pd.read_feather(WEB / "orderflow.feather") o["date"] = pd.to_datetime(o["date"], utc=True) cls._crowd = o.set_index("date").sort_index()["count_long_short_ratio"] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: self._load() dataframe["ema"] = ta.EMA(dataframe, timeperiod=int(self.ema_period.value)) dataframe["ema_exit"] = dataframe["ema"] * (100 - float(self.exit_threshold.value)) / 100 idx = pd.to_datetime(dataframe["date"], utc=True) # ffill = ostatnia OPUBLIKOWANA wartosc, nigdy przyszla f = self._funding.reindex(idx, method="ffill") dataframe["funding_pct"] = ( pd.Series(f.rolling(72, min_periods=24).mean().to_numpy()) .rolling(24 * 180, min_periods=24 * 30).rank(pct=True) * 100).to_numpy() c = self._crowd.reindex(idx, method="ffill") dataframe["crowd_pct"] = ( pd.Series(c.rolling(24, min_periods=6).mean().to_numpy()) .rolling(24 * 180, min_periods=24 * 30).rank(pct=True) * 100).to_numpy() return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ (qtpylib.crossed_above(dataframe["close"], dataframe["ema"])) & (dataframe["volume"] > 0) & ((dataframe["funding_pct"] < float(self.funding_max_pct.value)) | dataframe["funding_pct"].isna()) & ((dataframe["crowd_pct"] < float(self.crowd_max_pct.value)) | dataframe["crowd_pct"].isna()), ["enter_long", "enter_tag"], ] = (1, "ema_flow_ok") return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ qtpylib.crossed_below(dataframe["close"], dataframe["ema_exit"]), ["exit_long", "exit_tag"], ] = (1, "ema_cross_down") return dataframe