MultiMa
Tested 1 Jun 2023 – 30 May 2025
Rank 3 of 27 by return
MultiMa turned $1,000 into $1,978 (+97.8%) over 729 days, while holding Bitcoin returned +288.1% and an equal mix of the ten coins +194.8%. Its worst drawdown was a relatively contained 11.1%. It won 67% of trades for a profit factor of 2.43.
How MultiMa trades
MultiMa builds a ladder of triple-exponential moving averages on the 4-hour chart and enters when the averages are stacked in a particular order (four averages, 15 periods apart). It exits when a longer ladder of twelve averages flips the other way.
Slow by design: few trades, long holding times and a 34.5% stoploss. Results depend heavily on a small number of large trends.
Rules at a glance
- Indicators
- Stacked TEMAs
- Take-profit (minimal ROI)
- 52.3% from entry; 12.3% after 1d 1h; 7.6% after 1d 14h; 0% after 2d 4h
- Stoploss
- −34.5%
- Trailing stop
- Off
- Exit signal
- Used
Results by month
Profit or loss in USDT for trades closed in each month.
Results by coin
| Pair | Trades | Win rate | Profit (USDT) | Avg. hold |
|---|---|---|---|---|
| XRP/USDT | 22 | 68.2% | +215.81 | 2d 7h |
| DOGE/USDT | 29 | 62.1% | +178.22 | 2d 15h |
| SOL/USDT | 29 | 72.4% | +136.78 | 2d 6h |
| ADA/USDT | 28 | 71.4% | +136.25 | 2d 21h |
| AVAX/USDT | 26 | 61.5% | +97.50 | 3d 4h |
| LINK/USDT | 20 | 80.0% | +87.82 | 3d 12h |
| ETH/USDT | 20 | 75.0% | +53.91 | 4d 20h |
| DOT/USDT | 27 | 70.4% | +44.79 | 3d 18h |
| BNB/USDT | 26 | 53.8% | +21.89 | 3d 23h |
| BTC/USDT | 18 | 61.1% | +5.07 | 5d 6h |
Why trades closed
| Exit reason | Trades | Win rate | Profit (USDT) |
|---|---|---|---|
| Take-profit (ROI) | 198 | 83.3% | +1,662.86 |
| Exit signal | 47 | 0.0% | −684.82 |
The code
MultiMa comes from the community freqtrade-strategies repository (GPL-3.0). These are the parts that decide when it trades; the full file is 99 lines.
Entry rules
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] # I used range(self.buy_ma_count.value) instade of self.buy_ma_count.range # Cuz it returns range(7,8) but we need range(8) for all modes hyperopt, backtest and etc for ma_count in range(self.buy_ma_count.value): key = ma_count*self.buy_ma_gap.value past_key = (ma_count-1)*self.buy_ma_gap.value if past_key > 1 and key in dataframe.keys() and past_key in dataframe.keys(): conditions.append(dataframe[key] < dataframe[past_key]) if conditions: dataframe.loc[reduce(lambda x, y: x & y, conditions), "enter_long"] = 1 return dataframe
Exit rules
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: conditions = [] for ma_count in range(self.sell_ma_count.value): key = ma_count*self.sell_ma_gap.value past_key = (ma_count-1)*self.sell_ma_gap.value if past_key > 1 and key in dataframe.keys() and past_key in dataframe.keys(): conditions.append(dataframe[key] > dataframe[past_key]) if conditions: dataframe.loc[reduce(lambda x, y: x | y, conditions), "exit_long"] = 1 return dataframe
Indicators
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: for count in range(self.count_max): for gap in range(self.gap_max): if count*gap > 1 and count*gap not in dataframe.keys(): dataframe[count*gap] = ta.TEMA( dataframe, timeperiod=int(count*gap) ) print(" ", metadata['pair'], end="\t\r") return dataframe
Reproduce this test
With freqtrade installed (see our Docker guide), download the data and run the same backtest:
freqtrade download-data --exchange binance --pairs ADA/USDT AVAX/USDT BNB/USDT BTC/USDT DOGE/USDT DOT/USDT ETH/USDT LINK/USDT SOL/USDT XRP/USDT --timeframes 4h --timerange 20230501-20250530
freqtrade backtesting --strategy MultiMa --timeframe 4h \
--timerange 20230601-20250530 --dry-run-wallet 1000 --stake-amount 200 --max-open-trades 5 --fee 0.001
Results can differ slightly with a different data source or freqtrade version. See how we test.