Mikul's Ichimoku Cloud v2 戦略
オプションの移動平均フィルターを備えたIchimoku Cloudを使用したブレイクアウト戦略。ポジションはトレーリングストップ(ATR、パーセント、またはIchimokuルール)とオプションのテイクプロフィットで管理されます。
詳細
- エントリー条件: 価格が雲の上にある状態でTenkan-senがKijun-senを上抜け、または緑の雲の上への強いブレイクアウト。
- ロング/ショート: ロングのみ。
- エグジット条件: トレーリングストップまたはIchimokuリバーサル、オプションのテイクプロフィット。
- ストップ: トレーリング。
- デフォルト値:
TrailSource=LowsHighsTrailMethod=AtrTrailPercent= 10SwingLookback= 7AtrPeriod= 14AtrMultiplier= 1AddIchiExit= falseUseTakeProfit= falseTakeProfitPercent= 25UseMaFilter= falseMaType=EmaMaLength= 200TenkanPeriod= 9KijunPeriod= 26SenkouBPeriod= 52Displacement= 26CandleType= TimeSpan.FromHours(1)
- フィルター:
- カテゴリ: トレンド
- 方向: ロング
- インジケーター: Ichimoku、ATR
- ストップ: トレーリング
- 複雑さ: 中程度
- 時間軸: イントラデイ (1h)
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Mikul's Ichimoku Cloud v2 strategy.
/// Goes long when Tenkan-sen crosses above Kijun-sen with the close above the cloud, or when the close breaks above a green cloud,
/// optionally only above a moving average. The long is protected by a trailing stop built from ATR or a percent below the recent
/// swing low (or the close), and can also exit on an Ichimoku reversal and a percent take profit.
/// </summary>
public class MikulsIchimokuCloudV2Strategy : Strategy
{
/// <summary>
/// Price the trailing stop is measured from.
/// </summary>
public enum TrailSources
{
/// <summary>
/// Lowest low of the last SwingLookback candles.
/// </summary>
LowsHighs,
/// <summary>
/// Candle close.
/// </summary>
Close,
}
/// <summary>
/// Distance of the trailing stop.
/// </summary>
public enum TrailMethods
{
/// <summary>
/// ATR multiplied by AtrMultiplier.
/// </summary>
Atr,
/// <summary>
/// TrailPercent of the source price.
/// </summary>
Percent,
}
/// <summary>
/// Moving average types for the filter.
/// </summary>
public enum MaTypes
{
/// <summary>
/// Simple moving average.
/// </summary>
Sma,
/// <summary>
/// Exponential moving average.
/// </summary>
Ema,
/// <summary>
/// Weighted moving average.
/// </summary>
Wma,
}
private readonly StrategyParam<TrailSources> _trailSource;
private readonly StrategyParam<TrailMethods> _trailMethod;
private readonly StrategyParam<decimal> _trailPercent;
private readonly StrategyParam<int> _swingLookback;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<bool> _addIchiExit;
private readonly StrategyParam<bool> _useTakeProfit;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<bool> _useMaFilter;
private readonly StrategyParam<MaTypes> _maType;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouBPeriod;
private readonly StrategyParam<int> _displacement;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
private readonly List<decimal> _senkouA = [];
private readonly List<decimal> _senkouB = [];
private decimal? _prevTenkan;
private decimal? _prevKijun;
private decimal? _prevClose;
private decimal? _prevCloudTop;
private decimal? _trailStop;
private decimal _entryPrice;
/// <summary>
/// Price the trailing stop is measured from.
/// </summary>
public TrailSources TrailSource
{
get => _trailSource.Value;
set => _trailSource.Value = value;
}
/// <summary>
/// Trailing stop method.
/// </summary>
public TrailMethods TrailMethod
{
get => _trailMethod.Value;
set => _trailMethod.Value = value;
}
/// <summary>
/// Trailing distance in percent for the Percent method.
/// </summary>
public decimal TrailPercent
{
get => _trailPercent.Value;
set => _trailPercent.Value = value;
}
/// <summary>
/// Candles used for the swing low.
/// </summary>
public int SwingLookback
{
get => _swingLookback.Value;
set => _swingLookback.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for the trailing stop.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Exit on an Ichimoku reversal.
/// </summary>
public bool AddIchiExit
{
get => _addIchiExit.Value;
set => _addIchiExit.Value = value;
}
/// <summary>
/// Use the percent take profit.
/// </summary>
public bool UseTakeProfit
{
get => _useTakeProfit.Value;
set => _useTakeProfit.Value = value;
}
/// <summary>
/// Take profit in percent from the entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Require the close above the moving average for entries.
/// </summary>
public bool UseMaFilter
{
get => _useMaFilter.Value;
set => _useMaFilter.Value = value;
}
/// <summary>
/// Moving average type of the filter.
/// </summary>
public MaTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Moving average length of the filter.
/// </summary>
public int MaLength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
/// <summary>
/// Tenkan-sen period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B period.
/// </summary>
public int SenkouBPeriod
{
get => _senkouBPeriod.Value;
set => _senkouBPeriod.Value = value;
}
/// <summary>
/// Forward shift of the cloud in candles.
/// </summary>
public int Displacement
{
get => _displacement.Value;
set => _displacement.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MikulsIchimokuCloudV2Strategy()
{
_trailSource = Param(nameof(TrailSource), TrailSources.LowsHighs)
.SetDisplay("Trail Source", "Price the trailing stop is measured from", "Risk");
_trailMethod = Param(nameof(TrailMethod), TrailMethods.Atr)
.SetDisplay("Trail Method", "Trailing stop method", "Risk");
_trailPercent = Param(nameof(TrailPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Trail %", "Trailing distance in percent for the Percent method", "Risk");
_swingLookback = Param(nameof(SwingLookback), 7)
.SetGreaterThanZero()
.SetDisplay("Swing Lookback", "Candles used for the swing low", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 1m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "ATR multiplier for the trailing stop", "Risk");
_addIchiExit = Param(nameof(AddIchiExit), false)
.SetDisplay("Ichimoku Exit", "Exit on an Ichimoku reversal", "Risk");
_useTakeProfit = Param(nameof(UseTakeProfit), false)
.SetDisplay("Use Take Profit", "Use the percent take profit", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 25m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take profit in percent from the entry price", "Risk");
_useMaFilter = Param(nameof(UseMaFilter), false)
.SetDisplay("Use MA Filter", "Require the close above the moving average", "Filters");
_maType = Param(nameof(MaType), MaTypes.Ema)
.SetDisplay("MA Type", "Moving average type of the filter", "Filters");
_maLength = Param(nameof(MaLength), 200)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving average length of the filter", "Filters");
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Tenkan-sen period", "Ichimoku");
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Kijun-sen period", "Ichimoku");
_senkouBPeriod = Param(nameof(SenkouBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou B Period", "Senkou Span B period", "Ichimoku");
_displacement = Param(nameof(Displacement), 26)
.SetGreaterThanZero()
.SetDisplay("Displacement", "Forward shift of the cloud in candles", "Ichimoku");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_highs.Clear();
_lows.Clear();
_senkouA.Clear();
_senkouB.Clear();
_prevTenkan = null;
_prevKijun = null;
_prevClose = null;
_prevCloudTop = null;
_trailStop = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = AtrPeriod };
IIndicator ma = MaType switch
{
MaTypes.Sma => new SimpleMovingAverage { Length = MaLength },
MaTypes.Wma => new WeightedMovingAverage { Length = MaLength },
_ => new ExponentialMovingAverage { Length = MaLength },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
if (UseMaFilter)
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private static decimal Midpoint(List<decimal> highs, List<decimal> lows, int length)
{
var start = highs.Count - length;
return (highs.Skip(start).Max() + lows.Skip(start).Min()) / 2m;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue maValue)
{
if (candle.State != CandleStates.Finished)
return;
var maxLength = Math.Max(SwingLookback, Math.Max(TenkanPeriod, Math.Max(KijunPeriod, SenkouBPeriod)));
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > maxLength)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_highs.Count < maxLength)
return;
var close = candle.ClosePrice;
var tenkan = Midpoint(_highs, _lows, TenkanPeriod);
var kijun = Midpoint(_highs, _lows, KijunPeriod);
_senkouA.Add((tenkan + kijun) / 2m);
_senkouB.Add(Midpoint(_highs, _lows, SenkouBPeriod));
if (_senkouA.Count > Displacement)
{
_senkouA.RemoveAt(0);
_senkouB.RemoveAt(0);
}
var prevTenkan = _prevTenkan;
var prevKijun = _prevKijun;
var prevClose = _prevClose;
var prevCloudTop = _prevCloudTop;
_prevTenkan = tenkan;
_prevKijun = kijun;
_prevClose = close;
// The cloud plotted on this candle was calculated Displacement - 1 candles earlier.
if (_senkouA.Count < Displacement || !atrValue.IsFormed || !maValue.IsFormed)
return;
var cloudA = _senkouA[0];
var cloudB = _senkouB[0];
var cloudTop = Math.Max(cloudA, cloudB);
var cloudBottom = Math.Min(cloudA, cloudB);
_prevCloudTop = cloudTop;
var atr = atrValue.GetValue<decimal>();
var ma = maValue.GetValue<decimal>();
if (Position > 0)
{
var exit = _trailStop is decimal stop && candle.LowPrice <= stop;
if (!exit && UseTakeProfit && candle.HighPrice >= _entryPrice * (1m + TakeProfitPercent / 100m))
exit = true;
if (!exit && AddIchiExit)
{
var bearishCross = prevTenkan is decimal pt && prevKijun is decimal pk && pt >= pk && tenkan < kijun;
exit = bearishCross || close < cloudBottom;
}
if (exit)
{
SellMarket(Position);
_trailStop = null;
return;
}
var candidate = GetTrailCandidate(close, atr);
if (_trailStop is not decimal current || candidate > current)
_trailStop = candidate;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
var tkCross = prevTenkan is decimal t && prevKijun is decimal k && t <= k && tenkan > kijun && close > cloudTop;
var breakout = prevClose is decimal pc && prevCloudTop is decimal pct && pc <= pct && close > cloudTop && cloudA > cloudB;
var maOk = !UseMaFilter || close > ma;
if (Position == 0 && (tkCross || breakout) && maOk)
{
BuyMarket(Volume);
_entryPrice = close;
_trailStop = GetTrailCandidate(close, atr);
}
}
private decimal GetTrailCandidate(decimal close, decimal atr)
{
var source = TrailSource == TrailSources.LowsHighs
? _lows.Skip(_lows.Count - SwingLookback).Min()
: close;
return TrailMethod == TrailMethods.Atr
? source - atr * AtrMultiplier
: source * (1m - TrailPercent / 100m);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage, ExponentialMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class mikuls_ichimoku_cloud_v2_strategy(Strategy):
"""
Mikul's Ichimoku Cloud v2 strategy.
Goes long when Tenkan-sen crosses above Kijun-sen with the close above the cloud, or when the close breaks above a green cloud,
optionally only above a moving average. The long is protected by a trailing stop built from ATR or a percent below the recent
swing low (or the close), and can also exit on an Ichimoku reversal and a percent take profit.
"""
def __init__(self):
super(mikuls_ichimoku_cloud_v2_strategy, self).__init__()
self._trail_source = self.Param("TrailSource", "LowsHighs").SetDisplay("Trail Source", "Price the trailing stop is measured from: LowsHighs or Close", "Risk")
self._trail_method = self.Param("TrailMethod", "Atr").SetDisplay("Trail Method", "Trailing stop method: Atr or Percent", "Risk")
self._trail_percent = self.Param("TrailPercent", 10.0).SetGreaterThanZero().SetDisplay("Trail %", "Trailing distance in percent for the Percent method", "Risk")
self._swing_lookback = self.Param("SwingLookback", 7).SetGreaterThanZero().SetDisplay("Swing Lookback", "Candles used for the swing low", "Risk")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 1.0).SetNotNegative().SetDisplay("ATR Multiplier", "ATR multiplier for the trailing stop", "Risk")
self._add_ichi_exit = self.Param("AddIchiExit", False).SetDisplay("Ichimoku Exit", "Exit on an Ichimoku reversal", "Risk")
self._use_take_profit = self.Param("UseTakeProfit", False).SetDisplay("Use Take Profit", "Use the percent take profit", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 25.0).SetGreaterThanZero().SetDisplay("Take Profit %", "Take profit in percent from the entry price", "Risk")
self._use_ma_filter = self.Param("UseMaFilter", False).SetDisplay("Use MA Filter", "Require the close above the moving average", "Filters")
self._ma_type = self.Param("MaType", "Ema").SetDisplay("MA Type", "Moving average type of the filter: Sma, Ema or Wma", "Filters")
self._ma_length = self.Param("MaLength", 200).SetGreaterThanZero().SetDisplay("MA Length", "Moving average length of the filter", "Filters")
self._tenkan_period = self.Param("TenkanPeriod", 9).SetGreaterThanZero().SetDisplay("Tenkan Period", "Tenkan-sen period", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26).SetGreaterThanZero().SetDisplay("Kijun Period", "Kijun-sen period", "Ichimoku")
self._senkou_b_period = self.Param("SenkouBPeriod", 52).SetGreaterThanZero().SetDisplay("Senkou B Period", "Senkou Span B period", "Ichimoku")
self._displacement = self.Param("Displacement", 26).SetGreaterThanZero().SetDisplay("Displacement", "Forward shift of the cloud in candles", "Ichimoku")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._highs = []
self._lows = []
self._senkou_a = []
self._senkou_b = []
self._prev_tenkan = None
self._prev_kijun = None
self._prev_close = None
self._prev_cloud_top = None
self._trail_stop = None
self._entry_price = Decimal(0)
def OnReseted(self):
super(mikuls_ichimoku_cloud_v2_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(mikuls_ichimoku_cloud_v2_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
ma_type = str(self._ma_type.Value)
if ma_type == "Sma":
ma = SimpleMovingAverage()
elif ma_type == "Wma":
ma = WeightedMovingAverage()
else:
ma = ExponentialMovingAverage()
ma.Length = self._ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
if self._use_ma_filter.Value:
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _midpoint(self, length):
return (max(self._highs[-length:]) + min(self._lows[-length:])) / Decimal(2)
def _process_candle(self, candle, atr_value, ma_value):
if candle.State != CandleStates.Finished:
return
max_length = max(self._swing_lookback.Value, self._tenkan_period.Value, self._kijun_period.Value, self._senkou_b_period.Value)
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
if len(self._highs) > max_length:
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < max_length:
return
close = candle.ClosePrice
tenkan = self._midpoint(self._tenkan_period.Value)
kijun = self._midpoint(self._kijun_period.Value)
displacement = self._displacement.Value
self._senkou_a.append((tenkan + kijun) / Decimal(2))
self._senkou_b.append(self._midpoint(self._senkou_b_period.Value))
if len(self._senkou_a) > displacement:
self._senkou_a.pop(0)
self._senkou_b.pop(0)
prev_tenkan = self._prev_tenkan
prev_kijun = self._prev_kijun
prev_close = self._prev_close
prev_cloud_top = self._prev_cloud_top
self._prev_tenkan = tenkan
self._prev_kijun = kijun
self._prev_close = close
# The cloud plotted on this candle was calculated Displacement - 1 candles earlier.
if len(self._senkou_a) < displacement or not atr_value.IsFormed or not ma_value.IsFormed:
return
cloud_a = self._senkou_a[0]
cloud_b = self._senkou_b[0]
cloud_top = max(cloud_a, cloud_b)
cloud_bottom = min(cloud_a, cloud_b)
self._prev_cloud_top = cloud_top
atr = atr_value.GetValue[Decimal](None)
ma = ma_value.GetValue[Decimal](None)
if self.Position > 0:
exit_now = self._trail_stop is not None and candle.LowPrice <= self._trail_stop
if not exit_now and self._use_take_profit.Value and \
candle.HighPrice >= self._entry_price * (Decimal(1) + Decimal(self._take_profit_percent.Value) / Decimal(100)):
exit_now = True
if not exit_now and self._add_ichi_exit.Value:
bearish_cross = prev_tenkan is not None and prev_kijun is not None and prev_tenkan >= prev_kijun and tenkan < kijun
exit_now = bearish_cross or close < cloud_bottom
if exit_now:
self.SellMarket(self.Position)
self._trail_stop = None
return
candidate = self._get_trail_candidate(close, atr)
if self._trail_stop is None or candidate > self._trail_stop:
self._trail_stop = candidate
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
tk_cross = prev_tenkan is not None and prev_kijun is not None and prev_tenkan <= prev_kijun and tenkan > kijun and close > cloud_top
breakout = prev_close is not None and prev_cloud_top is not None and prev_close <= prev_cloud_top and close > cloud_top and cloud_a > cloud_b
ma_ok = not self._use_ma_filter.Value or close > ma
if self.Position == 0 and (tk_cross or breakout) and ma_ok:
self.BuyMarket(self.Volume)
self._entry_price = close
self._trail_stop = self._get_trail_candidate(close, atr)
def _get_trail_candidate(self, close, atr):
if str(self._trail_source.Value) == "LowsHighs":
source = min(self._lows[-self._swing_lookback.Value:])
else:
source = close
if str(self._trail_method.Value) == "Atr":
return source - atr * Decimal(self._atr_multiplier.Value)
return source * (Decimal(1) - Decimal(self._trail_percent.Value) / Decimal(100))
def CreateClone(self):
return mikuls_ichimoku_cloud_v2_strategy()