OTT and Variable Moving Average Crossovers in a Freqtrade Strategy
Summary
This Freqtrade strategy uses a variable moving average and an OTT trailing-line calculation to generate directional entries on a one-hour chart. It enters long when the variable average crosses above OTT and short when it crosses below. The OTT function derives a variable average using a Chande-style momentum ratio, builds percentage-offset long and short stops, tracks direction changes, and shifts the resulting OTT line by two candles. ADX is also calculated, and readings above 60 trigger exits for either side.
The code sets a tiered return-on-investment table, a fixed stop loss, and a trailing stop with specified thresholds. Its comments say some parameters came from hyperparameter optimization, while encouraging users to find settings suited to their needs and risk controls. The document includes implementation details but no backtest results, market universe, or evidence that the signals are profitable. The parameter choices and indicator behavior therefore require independent evaluation, including attention to timing, fees, and short-position availability on the intended exchange.
Key ideas
- Long and short entries are triggered by opposite crossovers between a variable average and the OTT line.
- The OTT calculation uses a momentum-weighted average and percentage-offset trailing stops to track direction.
- ADX above 60 is used as an exit condition for both long and short positions.
- The strategy also defines ROI targets, a stop loss, and trailing-stop settings.
- The code provides no backtest evidence, so its parameters and signal behavior need independent evaluation.
Tags
Full text
# FOttStrategy.py
```py
import logging
from numpy.lib import math
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import talib.abstract as ta
import numpy as np
import freqtrade.vendor.qtpylib.indicators as qtpylib
class FOttStrategy(IStrategy):
# Buy params, Sell params, ROI, Stoploss and Trailing Stop are values generated by 'freqtrade hyperopt --strategy Supertrend --hyperopt-loss ShortTradeDurHyperOptLoss --timerange=20210101- --timeframe=1h --spaces all'
# It's encourage you find the values that better suites your needs and risk management strategies
INTERFACE_VERSION: int = 3
# ROI table:
minimal_roi = {"0": 0.1, "30": 0.75, "60": 0.05, "120": 0.025}
# minimal_roi = {"0": 1}
# Stoploss:
stoploss = -0.265
# Trailing stop:
trailing_stop = True
trailing_stop_positive = 0.05
trailing_stop_positive_offset = 0.1
trailing_only_offset_is_reached = False
timeframe = "1h"
startup_candle_count = 18
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["ott"] = self.ott(dataframe)["OTT"]
dataframe["var"] = self.ott(dataframe)["VAR"]
dataframe["adx"] = ta.ADX(dataframe, timeperiod=14)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(qtpylib.crossed_above(dataframe["var"], dataframe["ott"])),
"enter_long",
] = 1
dataframe.loc[
(qtpylib.crossed_below(dataframe["var"], dataframe["ott"])),
"enter_short",
] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
dataframe["adx"]>60
),
"exit_long",
] = 1
dataframe.loc[
(
dataframe["adx"]>60
),
"exit_short",
] = 1
return dataframe
"""
Supertrend Indicator; adapted for freqtrade
from: https://github.com/freqtrade/freqtrade-strategies/issues/30
"""
def ott(self, dataframe: DataFrame):
df = dataframe.copy()
pds = 2
percent = 1.4
alpha = 2 / (pds + 1)
df["ud1"] = np.where(
df["close"] > df["close"].shift(1), (df["close"] - df["close"].shift()), 0
)
df["dd1"] = np.where(
df["close"] < df["close"].shift(1), (df["close"].shift() - df["close"]), 0
)
df["UD"] = df["ud1"].rolling(9).sum()
df["DD"] = df["dd1"].rolling(9).sum()
df["CMO"] = ((df["UD"] - df["DD"]) / (df["UD"] + df["DD"])).fillna(0).abs()
# df['Var'] = talib.EMA(df['close'], timeperiod=5)
df["Var"] = 0.0
for i in range(pds, len(df)):
df["Var"].iat[i] = (alpha * df["CMO"].iat[i] * df["close"].iat[i]) + (
1 - alpha * df["CMO"].iat[i]
) * df["Var"].iat[i - 1]
df["fark"] = df["Var"] * percent * 0.01
df["newlongstop"] = df["Var"] - df["fark"]
df["newshortstop"] = df["Var"] + df["fark"]
df["longstop"] = 0.0
df["shortstop"] = 999999999999999999
# df['dir'] = 1
for i in df["UD"]:
def maxlongstop():
df.loc[(df["newlongstop"] > df["longstop"].shift(1)), "longstop"] = df[
"newlongstop"
]
df.loc[(df["longstop"].shift(1) > df["newlongstop"]), "longstop"] = df[
"longstop"
].shift(1)
return df["longstop"]
def minshortstop():
df.loc[
(df["newshortstop"] < df["shortstop"].shift(1)), "shortstop"
] = df["newshortstop"]
df.loc[
(df["shortstop"].shift(1) < df["newshortstop"]), "shortstop"
] = df["shortstop"].shift(1)
return df["shortstop"]
df["longstop"] = np.where(
((df["Var"] > df["longstop"].shift(1))),
maxlongstop(),
df["newlongstop"],
)
df["shortstop"] = np.where(
((df["Var"] < df["shortstop"].shift(1))),
minshortstop(),
df["newshortstop"],
)
# get xover
df["xlongstop"] = np.where(
(
(df["Var"].shift(1) > df["longstop"].shift(1))
& (df["Var"] < df["longstop"].shift(1))
),
1,
0,
)
df["xshortstop"] = np.where(
(
(df["Var"].shift(1) < df["shortstop"].shift(1))
& (df["Var"] > df["shortstop"].shift(1))
),
1,
0,
)
df["trend"] = 0
df["dir"] = 0
for i in df["UD"]:
df["trend"] = np.where(
((df["xshortstop"] == 1)),
1,
(np.where((df["xlongstop"] == 1), -1, df["trend"].shift(1))),
)
df["dir"] = np.where(
((df["xshortstop"] == 1)),
1,
(np.where((df["xlongstop"] == 1), -1, df["dir"].shift(1).fillna(1))),
)
# get OTT
df["MT"] = np.where(df["dir"] == 1, df["longstop"], df["shortstop"])
df["OTT"] = np.where(
df["Var"] > df["MT"],
(df["MT"] * (200 + percent) / 200),
(df["MT"] * (200 - percent) / 200),
)
df["OTT"] = df["OTT"].shift(2)
return DataFrame(index=df.index, data={"OTT": df["OTT"], "VAR": df["Var"]})
```Shown in full with attribution under the source's licence. Licence: GPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.