在 GitHub 上查看
专家级一目均衡表策略
概述
本策略基于原始的 MQL5 "Expert Ichimoku" 智能交易系统,使用 StockSharp 高层 API 重写。系统属于趋势跟随型策略,通过组合一目均衡表的多个组件、前一根 K 线的价格行为以及可选的马丁格尔仓位管理来寻找入场机会。
策略在设定的时间框架上仅对已完成的 K 线进行计算。多空信号互斥,策略始终保持单一净仓位:如需翻仓,会先平掉原方向头寸再开立新的反向仓位。指标所需数据全部来自订阅的蜡烛序列,无需额外数据源。
核心逻辑
指标配置
- Tenkan-sen(转换线):用于捕捉快速均线的突破。
- Kijun-sen(基准线):与转换线组合的慢速均线。
- Senkou Span A/B(先行 A/B 线):使用上一根 K 线的数值确认价格是否位于云层之上或之下。
- Chikou Span(迟行线):通过与历史价格比较确认动量突破。
默认参数与原始 EA 一致(9 / 26 / 52),用户可以自由调整。
入场规则
多头条件:
- 动量触发(满足其一):
- 最新一根完成的 K 线上,Tenkan-sen 上穿 Kijun-sen(Tenkant-1 ≤ Kijunt-1 且 Tenkant > Kijunt),或
- 迟行线突破历史收盘价(Chikout-1 ≤ Closet-11 且 Chikout > Closet-10);
- 云层过滤:当前收盘价高于上一根 K 线的先行 A/B 线,即价格完全在云层之上;
- 价格行为过滤:上一根 K 线为阳线(Closet-1 > Opent-1);
- 仓位过滤:当前不存在多头仓位。如有空头仓位,先市价平仓,再进入多头。
空头条件与上述规则完全对称:
- Tenkan-sen 下穿 Kijun-sen,或迟行线跌破历史开盘价(Chikout-1 ≥ Opent-11 且 Chikout < Opent-10);
- 当前收盘价低于上一根 K 线的先行 A/B 线(价格在云层之下);
- 上一根 K 线为阴线;
- 如持有多头,先平多再开空。
仓位与马丁格尔
- 基础下单量等于策略的
Volume 属性。
- 若启用 Use Martingale,上一笔交易亏损时,下一次入场的下单量会翻倍;盈利或打平会将倍数重置。
- 实际下单量受
Volume × Max Position Multiplier 上限限制,对应原始 EA 中“最多持仓数量”的保护机制。
风险控制
- 固定止损 / 止盈:以绝对价格偏移量设定。若收盘价触及止损或止盈,则立即市价平仓。
- 移动止损:当
Trailing Stop Offset 和 Trailing Step 均大于 0 时,只有当价格较入场价至少上涨(或下跌)offset + step 后才会移动止损,完全复刻 EA 中的阶梯式追踪逻辑。
- 策略仅维护一个净仓位。平仓后会计算实际盈亏,用于决定下一个信号是否应用马丁格尔加仓。
参数说明
| 参数 |
说明 |
默认值 |
| Tenkan Period |
Tenkan-sen 长度。 |
9 |
| Kijun Period |
Kijun-sen 长度。 |
26 |
| Senkou Span B Period |
Senkou Span B 长度。 |
52 |
| Stop Loss Offset |
入场价与止损价之间的绝对距离,0 表示禁用。 |
0 |
| Take Profit Offset |
入场价与止盈价之间的绝对距离,0 表示禁用。 |
0 |
| Trailing Stop Offset |
移动止损的基础距离。 |
0 |
| Trailing Step |
每次上调移动止损所需的额外价格变动。 |
0 |
| Max Position Multiplier |
有效下单量的最大倍数(基于 Volume)。 |
5 |
| Use Martingale |
是否在亏损后翻倍下一次仓位。 |
true |
| Candle Type |
用于计算的蜡烛类型/时间框架。 |
1 小时时间框架 |
实战提示
- 策略至少需要 12 根已完成 K 线后才能评估全部条件(迟行线比较会引用最远 11 根之前的价格)。
- 由于 StockSharp 使用净仓位模型,
Max Position Multiplier 通过限制单次下单量来模拟原策略中的持仓数量上限。
- 当
Trailing Stop Offset 或 Trailing Step 为 0 时,移动止损功能自动关闭;两者都大于 0 时,只有当价格突破 offset + step 后才会收紧止损。
- 策略在日志中记录所有进出场事件,便于回测和复盘。
使用步骤
- 在策略容器或可视化设计器中配置交易品种与所需的蜡烛时间框架。
- 设置基础
Volume,并根据品种波动性将原 EA 里的“点数”转换为价格偏移量,填写到止损/止盈/移动止损参数中。
- 启动策略。当指标累积到足够历史数据后,会在每根完成的 K 线上检查交叉与迟行线突破,并自动执行风控与马丁格尔逻辑。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Ichimoku strategy using Tenkan/Kijun crossover (midline of short/long channels).
/// </summary>
public class ExpertIchimokuStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private decimal? _prevTenkan;
private decimal? _prevKijun;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
public ExpertIchimokuStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Short channel period", "Indicators");
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Long channel period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevTenkan = null;
_prevKijun = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevTenkan = null;
_prevKijun = null;
// Tenkan: midline of short highest/lowest
var tenkanHigh = new Highest { Length = TenkanPeriod };
var tenkanLow = new Lowest { Length = TenkanPeriod };
// Kijun: midline of long highest/lowest
var kijunHigh = new Highest { Length = KijunPeriod };
var kijunLow = new Lowest { Length = KijunPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(tenkanHigh, tenkanLow, kijunHigh, kijunLow, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal tHigh, decimal tLow, decimal kHigh, decimal kLow)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var tenkan = (tHigh + tLow) / 2;
var kijun = (kHigh + kLow) / 2;
if (_prevTenkan == null || _prevKijun == null)
{
_prevTenkan = tenkan;
_prevKijun = kijun;
return;
}
// Tenkan crosses above Kijun → buy
if (_prevTenkan.Value <= _prevKijun.Value && tenkan > kijun)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
// Tenkan crosses below Kijun → sell
else if (_prevTenkan.Value >= _prevKijun.Value && tenkan < kijun)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
_prevTenkan = tenkan;
_prevKijun = kijun;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class expert_ichimoku_strategy(Strategy):
def __init__(self):
super(expert_ichimoku_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetDisplay("Tenkan Period", "Short channel period", "Indicators")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetDisplay("Kijun Period", "Long channel period", "Indicators")
self._prev_tenkan = None
self._prev_kijun = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def TenkanPeriod(self):
return self._tenkan_period.Value
@property
def KijunPeriod(self):
return self._kijun_period.Value
def OnReseted(self):
super(expert_ichimoku_strategy, self).OnReseted()
self._prev_tenkan = None
self._prev_kijun = None
def OnStarted2(self, time):
super(expert_ichimoku_strategy, self).OnStarted2(time)
self._prev_tenkan = None
self._prev_kijun = None
tenkan_high = Highest()
tenkan_high.Length = self.TenkanPeriod
tenkan_low = Lowest()
tenkan_low.Length = self.TenkanPeriod
kijun_high = Highest()
kijun_high.Length = self.KijunPeriod
kijun_low = Lowest()
kijun_low.Length = self.KijunPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(tenkan_high, tenkan_low, kijun_high, kijun_low, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, t_high_value, t_low_value, k_high_value, k_low_value):
if candle.State != CandleStates.Finished:
return
th = float(t_high_value)
tl = float(t_low_value)
kh = float(k_high_value)
kl = float(k_low_value)
tenkan = (th + tl) / 2.0
kijun = (kh + kl) / 2.0
if self._prev_tenkan is None or self._prev_kijun is None:
self._prev_tenkan = tenkan
self._prev_kijun = kijun
return
# Tenkan crosses above Kijun
if self._prev_tenkan <= self._prev_kijun and tenkan > kijun:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
# Tenkan crosses below Kijun
elif self._prev_tenkan >= self._prev_kijun and tenkan < kijun:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._prev_tenkan = tenkan
self._prev_kijun = kijun
def CreateClone(self):
return expert_ichimoku_strategy()