macd
♡
Basics
mode: spot
outdated
Settings
stoploss: -0.275
process only new candles: false
startup candle count: 96
hyperopt
Indicators
MACD
talib
Other
core
pytz
strategies
15 related strategies (⧉ identical code, ≈ similar name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | import datetime import os from pandas import DataFrame import pytz import talib import pandas as pd from strategies.istrategy import IStrategy pd.options.mode.chained_assignment = None # default='warn' from functools import reduce import numpy as np from core.logger import logger from typing import Literal class macd(IStrategy): # NOTE: settings as of the 25th july 21 # Buy hyperspace params: buy_params = {} # Sell hyperspace params: # NOTE: was 15m but kept bailing out in dryrun sell_params = {} # Stoploss: stoploss = -0.275 startup_candle_count = 96 process_only_new_candles = False trailing_stop = False # trailing_stop_positive = 0.002 # trailing_stop_positive_offset = 0.025 # trailing_only_offset_is_reached = True use_sell_signal = True sell_profit_only = False def populate_indicators(self, df: DataFrame) -> DataFrame: close_prices = df["close"].values # 获取收盘价的数据 fast_period = 12 slow_period = 26 signal_period = 9 macd, signal, hist = talib.MACD( close_prices, fast_period, slow_period, signal_period ) df["dif"] = np.around(macd, decimals=6) df["dea"] = np.around(signal, decimals=6) df["macd"] = np.around(hist, decimals=6) # golden cross df["cross"] = np.where( (df["dif"] > df["dea"]) & (df["dif"].shift(1) < df["dea"].shift(1)), 1, 0, ) # dead cross df["cross"] = np.where( (df["dif"] < df["dea"]) & (df["dif"].shift(1) > df["dea"].shift(1)), -1, df["cross"], ) df["close_pct_change"] = df["close"].pct_change() return df def condition_early_close_seconds(self, df, conditions): condition_early_close_seconds = int( os.getenv("CONDITION_EARLY_CLOSE_SECONDS", 0) ) timeframe_seconds = df.index.unique().to_series().diff().min().total_seconds() threshold = condition_early_close_seconds last_date = df.index[-1] next_date = last_date + datetime.timedelta(seconds=timeframe_seconds) now_date = datetime.datetime.now(tz=pytz.timezone("Asia/Shanghai")) # early close of last candle if next_date - now_date > datetime.timedelta(seconds=threshold): conditions.append(df.index < last_date) df.loc[df.index >= last_date, "signal_by"] = pd.Series( "removed_by_early_close", index=df.index[df.index >= last_date], ) def condition_dea(self, df, conditions, signal: Literal["buy", "sell"]): condition_dea = os.getenv("CONDITION_DEA", "false") == "true" if condition_dea: if signal == "buy": conditions.append(df["dea"] < 0) else: conditions.append(df["dea"] > 0) def populate_buy_trend(self, df: DataFrame) -> DataFrame: if "signal" not in df: df["signal"] = pd.Series(dtype="str") df["signal_by"] = pd.Series(dtype="str") conditions = [] conditions.append(df["cross"] == 1) self.condition_dea(df, conditions, "buy") self.condition_early_close_seconds(df, conditions) if conditions: df.loc[reduce(lambda x, y: x & y, conditions), "buy"] = 1 # update signals that prices have big rise conditions.append(df["close_pct_change"] <= 0.004) df.loc[reduce(lambda x, y: x & y, conditions), "signal"] = "buy" df["signal_by"] = np.where( (df["buy"] == 1) & (df["close_pct_change"] > 0.004), "removed_buy_rise", df["signal_by"], ) return df def populate_sell_trend(self, df: DataFrame) -> DataFrame: if "signal" not in df: df["signal"] = pd.Series(dtype="str") df["signal_by"] = pd.Series(dtype="str") conditions = [] conditions.append(df["cross"] == -1) self.condition_dea(df, conditions, "sell") self.condition_early_close_seconds(df, conditions) if conditions: df.loc[reduce(lambda x, y: x & y, conditions), "sell"] = 1 conditions.append(df["close_pct_change"] >= -0.004) df.loc[reduce(lambda x, y: x & y, conditions), "signal"] = "sell" # update signals that prices have big fall df["signal_by"] = np.where( (df["sell"] == 1) & (df["close_pct_change"] < -0.004), "removed_sell_fall", df["signal_by"], ) return df def populate_close_position(self, df: DataFrame) -> DataFrame: df["take_profit"] = pd.Series(dtype="str") df["stop_loss"] = pd.Series(dtype="str") # macd三连跌止盈止损 fall_nums = 0 before_macd = 0 # 开仓信号 open_signal = None # 开仓价格 open_price = 0 # 平仓收益 profit = 0 # fee fee_rate = 0.0012 fee = 0 conditions = [] self.condition_early_close_seconds(df, conditions) for index, row in ( df.loc[reduce(lambda x, y: x & y, conditions)].iterrows() if len(conditions) > 0 else df.iterrows() ): # 如果发现信号,就重置计数器 if pd.notnull(row["signal"]): # set open data open_signal = row["signal"] open_price = float(row["close"]) fee = open_price * fee_rate fall_nums = 0 before_macd = abs(row["macd"]) continue if abs(row["macd"]) < before_macd: fall_nums += 1 else: fall_nums = 0 before_macd = abs(row["macd"]) if fall_nums == 4: if open_signal == "buy": profit = float(row["close"]) - open_price - fee elif open_signal == "sell": profit = open_price - float(row["close"]) - fee logger.debug( f"{'take profit' if profit > 0 else 'stop loss'} [macd_fall_4]: {index}, [{open_signal} {open_price} {row['close']}], {profit}" ) if profit > 0: df.loc[index, "take_profit"] = open_signal df.loc[index, "profit"] = profit else: df.loc[index, "stop_loss"] = open_signal df.loc[index, "profit"] = profit return df def run(self, df, ex, args): df = self.populate_indicators(df) df = self.populate_buy_trend(df) df = self.populate_sell_trend(df) df = self.populate_close_position(df) self.trade(ex, df, args) return df |
Strategy League — fixed backtest that feeds the ranking
The fixed-params backtest (33 pairs · 20210101-20260101) — the only run that feeds the Strategy League ranking.
Backtests — over a market period
Backtest this strategy over a chosen crypto-cycle period. These don't affect the League ranking, and need that period's candle data downloaded.
Log in or sign up to run backtests.
| Period | Range | Total % | Win % | Max DD | Trades | |
|---|---|---|---|---|---|---|
| 2020 · DeFi Summer & Pre-Halving Rally | 20200101-20210101 | not run | ||||
| 2021 · Institutional Bull Market | 20210101-20220101 | not run | ||||
| 2022 · Post-Bull Crash & Macro Tightening | 20220101-20230101 | not run | ||||
| 2023–2024 · Recovery & ETF Anticipation | 20230101-20250101 | not run | ||||
| 2025–2026 · Current Cycle | 20250101-20260101 | not run | ||||
Walk forward
Out-of-sample backtest on recent data · 33 pairs · 20260101-20260701.
Backtest trust check
Static source analysis — instant, does not run the strategy. Flags future-data leaks, backtest-realism problems, and indicators worth a second look.
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.