在 GitHub 上查看
TenKijun 交叉提醒策略 (ID 3562)
概述
该策略基于 MetaTrader 专家顾问 TenKijun.mq4,使用 StockSharp 高级 API 重新实现。原始 EA 只在一目均衡表的转折线(Tenkan-sen)与基准线(Kijun-sen)发生交叉时发送推送通知,不会下单。本 C# 版本保持“提醒而非交易”的设计,同时加入了 StockSharp 的订阅、图表和参数系统,便于在 Designer/Optimizer 中使用。
策略在可配置周期的收盘 K 线处工作。当有新 K 线在设定的交易时段内收盘时,利用经典的 9/26/52 参数计算一目均衡表指标,并记录最新的转折线与基准线值:
- 如果转折线自下而上穿越基准线,记录一条看涨交叉的日志信息;
- 如果转折线自上而下跌破基准线,记录一条看跌交叉的日志信息;
- 不执行买卖操作,方便用于信号提醒或与外部自动化结合。
指标与数据流程
- 指标:使用 StockSharp 的
Ichimoku 指标,可分别设置 Tenkan、Kijun 与 Senkou Span B 的周期,保持与原始脚本一致。
- 数据订阅:通过
SubscribeCandles 订阅蜡烛图,默认采用 30 分钟周期,可改成任意 TimeSpan 周期。
- 绑定方式:使用
BindEx 获取强类型的 IchimokuValue,无需调用 GetValue 系列方法,符合项目的编码规范。
- 图表展示:若有图表区域可用,会自动绘制蜡烛图与一目均衡表曲线,便于直观验证提醒。
交易时段过滤
原始脚本允许设置允许提醒的小时区间。移植版本通过两个参数实现同样的控制:
StartHour:交易时段的开始小时(含),默认 0。
LastHour:交易时段的结束小时(含),默认 20。
若 StartHour ≤ LastHour,提醒只在该时间区间内触发;若 StartHour > LastHour,则视为跨夜区间(例如 20 → 6 表示晚间到次日凌晨)。
参数说明
| 参数 |
描述 |
默认值 |
备注 |
StartHour |
允许提醒的起始小时 |
0 |
0-23 之间的整数 |
LastHour |
允许提醒的结束小时 |
20 |
0-23 之间的整数 |
TenkanPeriod |
转折线回看长度 |
9 |
支持优化 |
KijunPeriod |
基准线回看长度 |
26 |
支持优化 |
SenkouSpanBPeriod |
领先线 B 回看长度 |
52 |
为完整起见提供,提醒逻辑未使用云图 |
CandleType |
指标使用的蜡烛类型 |
30 分钟 K 线 |
可换成任意时间框架 |
提醒逻辑
- 等待首根完成的蜡烛,用于初始化上一根 Tenkan/Kijun 值。
- 每当有新的蜡烛在交易时段内收盘时:
- 从
IchimokuValue 中提取 Tenkan 与 Kijun。
- 若上一根 Tenkan ≤ 上一根 Kijun 且当前 Tenkan > 当前 Kijun,则识别为看涨交叉并写日志。
- 若上一根 Tenkan ≥ 上一根 Kijun 且当前 Tenkan < 当前 Kijun,则识别为看跌交叉并写日志。
- 更新保存的上一根指标数值,等待下一次比较。
使用建议
- 可订阅策略日志或扩展
ProcessCandle,把提示转发到邮件、声音或即时通讯服务。
- 如需自动下单,可在该类基础上派生子类,在交叉发生时调用
BuyMarket / SellMarket 或其他下单方法。
- 请将时间框架调整为与 MetaTrader 图表一致,以便收到相同的交叉提醒。
与原始 EA 的差异
- 使用 StockSharp 的日志体系代替 MetaTrader 的
SendNotification,功能等效。
- 提供完整的参数元数据(显示名称、范围、可优化标记),便于在图形界面中配置。
- 自动把蜡烛和指标绘制到 StockSharp 图表,无需额外脚本。
文件列表
CS/TenKijunCrossStrategy.cs – 策略的 C# 实现。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Tenkan/Kijun cross strategy based on Ichimoku indicator.
/// Buys when Tenkan crosses above Kijun, sells when Tenkan crosses below Kijun.
/// Uses SMA proxies for Tenkan (short) and Kijun (long) since Ichimoku complex type
/// requires BindEx and special value handling.
/// </summary>
public class TenKijunCrossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly Queue<decimal> _highsTenkan = new();
private readonly Queue<decimal> _lowsTenkan = new();
private readonly Queue<decimal> _highsKijun = new();
private readonly Queue<decimal> _lowsKijun = new();
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 TenKijunCrossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for Ichimoku calculations", "General");
_tenkanPeriod = Param(nameof(TenkanPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Tenkan-sen conversion line period", "Indicators");
_kijunPeriod = Param(nameof(KijunPeriod), 34)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Kijun-sen base line period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevTenkan = null;
_prevKijun = null;
_highsTenkan.Clear();
_lowsTenkan.Clear();
_highsKijun.Clear();
_lowsKijun.Clear();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Compute Tenkan-sen = (highest high + lowest low) / 2 over TenkanPeriod
_highsTenkan.Enqueue(candle.HighPrice);
_lowsTenkan.Enqueue(candle.LowPrice);
if (_highsTenkan.Count > TenkanPeriod)
{
_highsTenkan.Dequeue();
_lowsTenkan.Dequeue();
}
// Compute Kijun-sen = (highest high + lowest low) / 2 over KijunPeriod
_highsKijun.Enqueue(candle.HighPrice);
_lowsKijun.Enqueue(candle.LowPrice);
if (_highsKijun.Count > KijunPeriod)
{
_highsKijun.Dequeue();
_lowsKijun.Dequeue();
}
if (_highsTenkan.Count < TenkanPeriod || _highsKijun.Count < KijunPeriod)
return;
var highsTenkan = _highsTenkan.ToArray();
var lowsTenkan = _lowsTenkan.ToArray();
var highsKijun = _highsKijun.ToArray();
var lowsKijun = _lowsKijun.ToArray();
var tenkan = (Max(highsTenkan) + Min(lowsTenkan)) / 2;
var kijun = (Max(highsKijun) + Min(lowsKijun)) / 2;
if (_prevTenkan is null || _prevKijun is null)
{
_prevTenkan = tenkan;
_prevKijun = kijun;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
var crossUp = _prevTenkan.Value <= _prevKijun.Value && tenkan > kijun;
var crossDown = _prevTenkan.Value >= _prevKijun.Value && tenkan < kijun;
if (crossUp)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
else if (crossDown)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevTenkan = tenkan;
_prevKijun = kijun;
}
private static decimal Max(IEnumerable<decimal> values)
{
decimal max = decimal.MinValue;
foreach (var v in values)
if (v > max) max = v;
return max;
}
private static decimal Min(IEnumerable<decimal> values)
{
decimal min = decimal.MaxValue;
foreach (var v in values)
if (v < min) min = v;
return min;
}
/// <inheritdoc />
protected override void OnReseted()
{
_prevTenkan = null;
_prevKijun = null;
_highsTenkan.Clear();
_lowsTenkan.Clear();
_highsKijun.Clear();
_lowsKijun.Clear();
base.OnReseted();
}
}
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.Strategies import Strategy
class ten_kijun_cross_strategy(Strategy):
def __init__(self):
super(ten_kijun_cross_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._tenkan_period = self.Param("TenkanPeriod", 12)
self._kijun_period = self.Param("KijunPeriod", 34)
self._highs_tenkan = []
self._lows_tenkan = []
self._highs_kijun = []
self._lows_kijun = []
self._prev_tenkan = None
self._prev_kijun = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def TenkanPeriod(self):
return self._tenkan_period.Value
@TenkanPeriod.setter
def TenkanPeriod(self, value):
self._tenkan_period.Value = value
@property
def KijunPeriod(self):
return self._kijun_period.Value
@KijunPeriod.setter
def KijunPeriod(self, value):
self._kijun_period.Value = value
def OnReseted(self):
super(ten_kijun_cross_strategy, self).OnReseted()
self._highs_tenkan = []
self._lows_tenkan = []
self._highs_kijun = []
self._lows_kijun = []
self._prev_tenkan = None
self._prev_kijun = None
def OnStarted2(self, time):
super(ten_kijun_cross_strategy, self).OnStarted2(time)
self._highs_tenkan = []
self._lows_tenkan = []
self._highs_kijun = []
self._lows_kijun = []
self._prev_tenkan = None
self._prev_kijun = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
tenkan_period = self.TenkanPeriod
kijun_period = self.KijunPeriod
self._highs_tenkan.append(high)
self._lows_tenkan.append(low)
while len(self._highs_tenkan) > tenkan_period:
self._highs_tenkan.pop(0)
self._lows_tenkan.pop(0)
self._highs_kijun.append(high)
self._lows_kijun.append(low)
while len(self._highs_kijun) > kijun_period:
self._highs_kijun.pop(0)
self._lows_kijun.pop(0)
if len(self._highs_tenkan) < tenkan_period or len(self._highs_kijun) < kijun_period:
return
tenkan = (max(self._highs_tenkan) + min(self._lows_tenkan)) / 2.0
kijun = (max(self._highs_kijun) + min(self._lows_kijun)) / 2.0
if self._prev_tenkan is None or self._prev_kijun is None:
self._prev_tenkan = tenkan
self._prev_kijun = kijun
return
cross_up = self._prev_tenkan <= self._prev_kijun and tenkan > kijun
cross_down = self._prev_tenkan >= self._prev_kijun and tenkan < kijun
if cross_up:
if self.Position <= 0:
self.BuyMarket()
elif cross_down:
if self.Position >= 0:
self.SellMarket()
self._prev_tenkan = tenkan
self._prev_kijun = kijun
def CreateClone(self):
return ten_kijun_cross_strategy()