IchiV1 Fixed Strategy - Ichimoku Cloud with Multi-Timeframe EMA Trend Analysis
Timeframe
5m
Direction
Long Only
Stoploss
-27.5%
Trailing Stop
No
ROI
0m: 3.0%, 10m: 2.0%, 57m: 1.0%, 99m: 0.0%
Interface Version
N/A
Startup Candles
96
Indicators
4
freqtrade/freqtrade-strategies
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
import pandas as pd
pd.options.mode.chained_assignment = None
import technical.indicators as ftt
from functools import reduce
from datetime import datetime, timedelta
from freqtrade.strategy import merge_informative_pair
import numpy as np
from freqtrade.strategy import stoploss_from_open
class IchiV1_Fixed(IStrategy):
"""
IchiV1 Fixed Strategy - Ichimoku Cloud with Multi-Timeframe EMA Trend Analysis
This strategy combines Ichimoku Cloud analysis with multiple Exponential Moving Averages
(EMAs) across different timeframes to identify strong bullish trends and exit positions
when price crosses below a specific EMA level.
Entry Logic:
- EMAs must be above Ichimoku Cloud (strong trend confirmation)
- EMAs must be bullish (close > open) across multiple timeframes
- Fan magnitude must be increasing (trend acceleration)
- Fan magnitude must be greater than 1 (confirmed uptrend)
Exit Logic:
- Exit when price crosses below the selected EMA indicator
"""
# Buy signal parameters - optimized for more permissive entry conditions
buy_params = {
"buy_fan_magnitude_shift_value": 1, # Number of previous candles to check for increasing fan magnitude
"buy_min_fan_magnitude_gain": 1.001, # Minimum fan magnitude gain threshold (0.1% acceleration)
"buy_trend_above_senkou_level": 1, # Minimum number of EMAs that must be above Ichimoku Cloud
"buy_trend_bullish_level": 4, # Minimum number of EMAs that must be bullish (close > open)
}
# Sell signal parameters
sell_params = {
"sell_trend_indicator": "trend_close_30m", # EMA indicator used for exit signal
}
# ROI (Return on Investment) table - defines profit targets at different time intervals
# Format: {time_in_minutes: profit_ratio}
minimal_roi = {"0": 0.03, "10": 0.02, "57": 0.01, "99": 0}
# Stoploss - maximum loss percentage before exiting position
stoploss = -0.275 # -27.5%
# Strategy timeframe - base candle interval for analysis
timeframe = "5m"
# Number of candles required before strategy can start (needed for longest EMA calculation)
startup_candle_count = 96
# Process all historical candles or only new ones
process_only_new_candles = False
# Trailing stop configuration (currently disabled)
trailing_stop = False
# trailing_stop_positive = 0.002
# trailing_stop_positive_offset = 0.025
# trailing_only_offset_is_reached = True
# Exit signal configuration
use_exit_signal = True
exit_profit_only = False
ignore_roi_if_entry_signal = False
# Plot configuration for freqtrade UI visualization
plot_config = {
"main_plot": {
"senkou_a": {
"color": "green",
"fill_to": "senkou_b",
"fill_label": "Ichimoku Cloud",
"fill_color": "rgba(255,76,46,0.2)",
},
"senkou_b": {},
"trend_close_5m": {"color": "#FF5733"}, # Red
"trend_close_15m": {"color": "#FF8333"}, # Orange-red
"trend_close_30m": {"color": "#FFB533"}, # Orange
"trend_close_1h": {"color": "#FFE633"}, # Yellow-orange
"trend_close_2h": {"color": "#E3FF33"}, # Yellow-green
"trend_close_4h": {"color": "#C4FF33"}, # Yellow-green
"trend_close_6h": {"color": "#61FF33"}, # Yellow-green
"trend_close_8h": {"color": "#33FF7D"}, # Green
},
"subplots": {
"fan_magnitude": {"fan_magnitude": {}},
"fan_magnitude_gain": {"fan_magnitude_gain": {}},
},
}
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
"""
Calculate all technical indicators required for the strategy.
This method computes:
- Heikin Ashi candles (smoothed price action)
- Exponential Moving Averages (EMAs) for multiple timeframes
- Fan magnitude indicators (trend strength measurement)
- Ichimoku Cloud components
- Average True Range (ATR) for volatility measurement
Args:
dataframe: DataFrame with OHLCV data
Returns:
DataFrame with all calculated indicators added as columns
"""
# Convert to Heikin Ashi candles to smooth price action and reduce noise
# Heikin Ashi uses modified OHLC values based on previous candle
heikinashi = qtpylib.heikinashi(dataframe)
dataframe["open"] = heikinashi["open"]
dataframe["high"] = heikinashi["high"]
dataframe["low"] = heikinashi["low"]
# Note: 'close' remains original price for accurate trend analysis
# Calculate Exponential Moving Averages for closing prices across multiple timeframes
# These EMAs represent trend strength at different temporal horizons
# Naming convention: trend_close_Xm where X is the approximate timeframe
dataframe["trend_close_5m"] = dataframe["close"] # Current price (no smoothing)
dataframe["trend_close_15m"] = ta.EMA(
dataframe["close"], timeperiod=3
) # ~15 min equivalent
dataframe["trend_close_30m"] = ta.EMA(
dataframe["close"], timeperiod=6
) # ~30 min equivalent
dataframe["trend_close_1h"] = ta.EMA(
dataframe["close"], timeperiod=12
) # ~1 hour equivalent
dataframe["trend_close_2h"] = ta.EMA(
dataframe["close"], timeperiod=24
) # ~2 hours equivalent
dataframe["trend_close_4h"] = ta.EMA(
dataframe["close"], timeperiod=48
) # ~4 hours equivalent
dataframe["trend_close_6h"] = ta.EMA(
dataframe["close"], timeperiod=72
) # ~6 hours equivalent
dataframe["trend_close_8h"] = ta.EMA(
dataframe["close"], timeperiod=96
) # ~8 hours equivalent
# Calculate Exponential Moving Averages for opening prices
# Used to verify bullish trend (close > open) across multiple timeframes
dataframe["trend_open_5m"] = dataframe["open"]
dataframe["trend_open_15m"] = ta.EMA(dataframe["open"], timeperiod=3)
dataframe["trend_open_30m"] = ta.EMA(dataframe["open"], timeperiod=6)
dataframe["trend_open_1h"] = ta.EMA(dataframe["open"], timeperiod=12)
dataframe["trend_open_2h"] = ta.EMA(dataframe["open"], timeperiod=24)
dataframe["trend_open_4h"] = ta.EMA(dataframe["open"], timeperiod=48)
dataframe["trend_open_6h"] = ta.EMA(dataframe["open"], timeperiod=72)
dataframe["trend_open_8h"] = ta.EMA(dataframe["open"], timeperiod=96)
# Fan Magnitude: Ratio between fast EMA (1h) and slow EMA (8h)
# Values > 1 indicate bullish trend (fast EMA above slow EMA)
# Values < 1 indicate bearish trend (fast EMA below slow EMA)
# Higher values indicate stronger trend separation
dataframe["fan_magnitude"] = (
dataframe["trend_close_1h"] / dataframe["trend_close_8h"]
)
# Fan Magnitude Gain: Rate of change in fan magnitude
# Measures trend acceleration/deceleration
# Values > 1 indicate increasing trend strength (momentum building)
# Values < 1 indicate decreasing trend strength (momentum weakening)
dataframe["fan_magnitude_gain"] = dataframe["fan_magnitude"] / dataframe[
"fan_magnitude"
].shift(1)
# Calculate Ichimoku Cloud with custom parameters
# Ichimoku Cloud provides support/resistance levels and trend direction
ichimoku = ftt.ichimoku(
dataframe,
conversion_line_period=20, # Tenkan-sen (conversion line)
base_line_periods=60, # Kijun-sen (base line)
laggin_span=120, # Chikou Span (lagging span)
displacement=30, # Cloud displacement (forward projection)
)
# Extract Ichimoku components
# Note: Chikou Span is excluded to avoid lookahead bias (uses future data)
# dataframe['chikou_span'] = ichimoku['chikou_span'] # Excluded - causes lookahead bias
dataframe["tenkan_sen"] = ichimoku[
"tenkan_sen"
] # Conversion line (fast moving average)
dataframe["kijun_sen"] = ichimoku[
"kijun_sen"
] # Base line (slow moving average)
dataframe["senkou_a"] = ichimoku[
"senkou_span_a"
] # Cloud span A (upper cloud boundary)
dataframe["senkou_b"] = ichimoku[
"senkou_span_b"
] # Cloud span B (lower cloud boundary)
dataframe["leading_senkou_span_a"] = ichimoku[
"leading_senkou_span_a"
] # Leading span A
dataframe["leading_senkou_span_b"] = ichimoku[
"leading_senkou_span_b"
] # Leading span B
dataframe["cloud_green"] = ichimoku[
"cloud_green"
] # Bullish cloud indicator (senkou_a > senkou_b)
dataframe["cloud_red"] = ichimoku[
"cloud_red"
] # Bearish cloud indicator (senkou_a < senkou_b)
# Average True Range (ATR) - measures market volatility
# Can be used for dynamic stop loss or position sizing in future implementations
dataframe["atr"] = ta.ATR(dataframe)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Define entry conditions for long positions.
Entry signals are generated when ALL of the following conditions are met:
1. EMAs are above Ichimoku Cloud (strong trend confirmation)
2. EMAs are bullish (close > open) across multiple timeframes
3. Fan magnitude is increasing (trend acceleration confirmed)
4. Fan magnitude is greater than 1 (confirmed uptrend)
Args:
dataframe: DataFrame with calculated indicators
Returns:
DataFrame with 'enter_long' column set to 1 where conditions are met
"""
conditions = []
# ============================================================
# CONDITION 1: EMAs Above Ichimoku Cloud
# ============================================================
# Verify that EMAs are positioned above both cloud boundaries (senkou_a and senkou_b)
# Higher buy_trend_above_senkou_level requires more EMAs to be above the cloud
# This ensures stronger trend confirmation by requiring multiple timeframe agreement
if self.buy_params["buy_trend_above_senkou_level"] >= 1:
conditions.append(dataframe["trend_close_5m"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_5m"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 2:
conditions.append(dataframe["trend_close_15m"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_15m"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 3:
conditions.append(dataframe["trend_close_30m"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_30m"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 4:
conditions.append(dataframe["trend_close_1h"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_1h"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 5:
conditions.append(dataframe["trend_close_2h"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_2h"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 6:
conditions.append(dataframe["trend_close_4h"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_4h"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 7:
conditions.append(dataframe["trend_close_6h"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_6h"] > dataframe["senkou_b"])
if self.buy_params["buy_trend_above_senkou_level"] >= 8:
conditions.append(dataframe["trend_close_8h"] > dataframe["senkou_a"])
conditions.append(dataframe["trend_close_8h"] > dataframe["senkou_b"])
# ============================================================
# CONDITION 2: Bullish Trend Across Multiple Timeframes
# ============================================================
# Verify that Heikin Ashi candles are bullish (close > open) across timeframes
# Higher buy_trend_bullish_level requires more EMAs to show bullish structure
# This ensures consistent buying pressure across different time horizons
if self.buy_params["buy_trend_bullish_level"] >= 1:
conditions.append(dataframe["trend_close_5m"] > dataframe["trend_open_5m"])
if self.buy_params["buy_trend_bullish_level"] >= 2:
conditions.append(
dataframe["trend_close_15m"] > dataframe["trend_open_15m"]
)
if self.buy_params["buy_trend_bullish_level"] >= 3:
conditions.append(
dataframe["trend_close_30m"] > dataframe["trend_open_30m"]
)
if self.buy_params["buy_trend_bullish_level"] >= 4:
conditions.append(dataframe["trend_close_1h"] > dataframe["trend_open_1h"])
if self.buy_params["buy_trend_bullish_level"] >= 5:
conditions.append(dataframe["trend_close_2h"] > dataframe["trend_open_2h"])
if self.buy_params["buy_trend_bullish_level"] >= 6:
conditions.append(dataframe["trend_close_4h"] > dataframe["trend_open_4h"])
if self.buy_params["buy_trend_bullish_level"] >= 7:
conditions.append(dataframe["trend_close_6h"] > dataframe["trend_open_6h"])
if self.buy_params["buy_trend_bullish_level"] >= 8:
conditions.append(dataframe["trend_close_8h"] > dataframe["trend_open_8h"])
# ============================================================
# CONDITION 3: Fan Magnitude Analysis (Trend Acceleration)
# ============================================================
# Verify that trend momentum is increasing and confirmed
# Fan magnitude gain must exceed minimum threshold (e.g., 1.001 = 0.1% acceleration)
# This ensures trend is accelerating, not just maintaining
conditions.append(
dataframe["fan_magnitude_gain"]
>= self.buy_params["buy_min_fan_magnitude_gain"]
)
# Fan magnitude must be greater than 1 (fast EMA above slow EMA)
# This confirms overall uptrend structure
conditions.append(dataframe["fan_magnitude"] > 1)
# Verify fan magnitude has been increasing over N previous candles
# This ensures consistent trend acceleration rather than isolated spikes
for x in range(self.buy_params["buy_fan_magnitude_shift_value"]):
conditions.append(
dataframe["fan_magnitude"].shift(x + 1) < dataframe["fan_magnitude"]
)
# ============================================================
# ACTIVATE ENTRY SIGNAL
# ============================================================
# Set 'enter_long' to 1 where ALL conditions are simultaneously true
if conditions:
dataframe.loc[reduce(lambda x, y: x & y, conditions), "enter_long"] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Define exit conditions for long positions.
Exit signal is generated when:
- Price (trend_close_5m) crosses below the selected EMA indicator
The EMA indicator is configured via sell_params["sell_trend_indicator"] and can be
any of the calculated trend_close_Xm indicators. Faster EMAs (e.g., trend_close_15m)
provide earlier exits, while slower EMAs (e.g., trend_close_8h) provide later exits.
Args:
dataframe: DataFrame with calculated indicators
Returns:
DataFrame with 'exit_long' column set to 1 where exit conditions are met
"""
conditions = []
# ============================================================
# EXIT CONDITION: Price Crosses Below EMA
# ============================================================
# Exit when current price crosses below the configured EMA indicator
# This indicates trend weakening or reversal
conditions.append(
qtpylib.crossed_below(
dataframe["trend_close_5m"], # Current price (5m timeframe)
dataframe[
self.sell_params["sell_trend_indicator"]
], # Selected EMA indicator
)
)
# ============================================================
# ACTIVATE EXIT SIGNAL
# ============================================================
# Set 'exit_long' to 1 where exit condition is met
if conditions:
dataframe.loc[reduce(lambda x, y: x & y, conditions), "exit_long"] = 1
return dataframe