在 GitHub 上查看

KSRobot 1.5 策略

概述

KSRobot 1.5 策略 是将 MetaTrader 4 专家顾问 KSRobot_1_5_h1_v1.mq4 迁移到 StockSharp 高级 API 的 C# 版本。策略保留了原始 EA 的核心思想:价格穿越 Kijun-sen 线时,在 20 周期线性加权移动平均线(LWMA)的趋势确认下入场,同时通过限定交易时间和层级化的风控规则保护仓位。默认使用 30 分钟 K 线,但可以通过参数调整时间框架。

数据与指标

  • Ichimoku 指标,Tenkan/Kijun/Senkou Span B 的默认周期为 6/12/24。
  • 线性加权移动平均线 (LWMA),默认周期 20,用于判断坡度和最小距离过滤。
  • CandleType 指定的时间框架蜡烛(默认 M30)驱动信号计算。

交易逻辑

多头流程

  1. 蜡烛必须从下方穿越 Kijun:可以是开盘在下、收盘在上,或上一根收盘在下且本根收盘在上,或者本根最低价触及该水平。
  2. 当前 Kijun 与两根之前相比保持平坦或上升,避免在基线明显下行时做多。
  3. LWMA 至少低于 Kijun MaFilterPips(换算为价格单位),复刻 EA 中“均线需位于基线下若干点”的限制。
  4. LWMA 斜率为正,即本根值大于上一根。
  5. 满足条件后会记录一个待触发的多头信号,同一时间只允许一个方向处于待触发状态,对应 MQL 的 longcross/shortcross 标志。
  6. 当所有条件满足且当前没有净多头头寸时,以市价买入,并记录入场价用于后续止损、保本和跟踪止损管理。

空头流程

条件完全镜像:

  1. 蜡烛从上方穿越 Kijun(开盘在上、收盘在下,或上一根收盘在上但本根收盘在下,或最高价触及该水平)。
  2. Kijun 与两根之前相比保持平坦或下行。
  3. LWMA 至少高于 Kijun MaFilterPips
  4. LWMA 斜率为负,即当前值低于上一根。
  5. 仅跟踪一个待触发的空头信号,一旦出现多头信号即被清除,与原 EA 一致。
  6. 条件满足且当前没有净空头持仓时,执行市价卖出。

出场规则与风险控制

  • 交易时间:仅当蜡烛开盘时间处于 [TradingStartHour, TradingEndHour)(默认 07:00–19:00,使用交易所时区)时才允许新建仓位。
  • 初始止损:在入场价下方/上方 StopLossPips 的距离放置止损(根据品种的价格步长换算)。设为 0 则不追踪初始止损。
  • 保本移动:一旦浮盈超过 BreakEvenPips,止损移动到入场价上方一个点(多头)或下方一个点(空头),通过 _breakEvenStep 模拟 MT4 的 “BE+1” 行为。
  • 跟踪止损:当价格向有利方向推进 TrailingStopPips 后,止损以相同距离跟随,只朝有利方向移动。
  • 止盈TakeProfitPips 提供可选的固定止盈距离,设为 0 可禁用。
  • 斜率退出:如果在止损尚未越过入场价前 LWMA 转向不利方向,立即平仓,复现 EA 中的 “MA 方向错误” 逻辑。
  • 优先级:当同一根 K 线同时触及止损与止盈时,优先执行止损,以在缺乏盘中数据时保持保守。

参数

参数 默认值 描述
TenkanPeriod 6 Ichimoku Tenkan-sen 周期。
KijunPeriod 12 Ichimoku Kijun-sen 周期(核心触发)。
SenkouSpanBPeriod 24 Senkou Span B 周期。
LwmaPeriod 20 LWMA 确认均线周期。
MaFilterPips 6 LWMA 与 Kijun 之间的最小点数距离。
StopLossPips 50 初始止损距离。
BreakEvenPips 9 触发保本移动所需的盈利。
TrailingStopPips 10 触发跟踪止损的距离。
TakeProfitPips 120 固定止盈距离(可选)。
TradingStartHour 7 开始允许开仓的小时(含)。
TradingEndHour 19 停止开仓的小时(不含)。
CandleType 30 分钟周期 用于订阅的蜡烛数据类型。

所有以点数表示的参数都会通过 Security.PriceStep(或 MinPriceStep)转换为价格单位。对于三位或五位小数报价的品种,会自动乘以 10 以匹配外汇常规的 pip 定义。

实现说明

  • 通过一次 SubscribeCandles().BindEx(...) 绑定 Ichimoku 与 LWMA,使指标数值直接从管道传入,无需手动维护集合。
  • 待触发的价位变量重现了 EA 中的 longcross/shortcross 状态,触发交易后即被清空。
  • 入场后会缓存止损、止盈、保本与跟踪距离,保证在仅有蜡烛数据的情况下依旧可以逐根更新保护逻辑。
  • StartProtection 以零距离调用,因为所有防护机制均在策略内部手动实现,保持与 MQL 脚本一致。
  • 仅使用市价单;原 EA 根据 Bid/Ask 选择限价或市价,这在蜡烛回测中不可复现。

使用步骤

  1. 在 StockSharp 中创建策略实例,设置 SecurityPortfolioVolume,然后启动。
  2. 可根据交易品种调整点数参数,例如套用 MQL 注释中针对 GBPUSD、EURUSD 的优化配置。
  3. 通过日志观察信号、保本与跟踪止损的调整以及紧急离场,这些都会通过 LogInfo 输出。
  4. 在设计器或测试器中加载自动创建的图表区域(蜡烛、Ichimoku、LWMA、自有成交),便于直观监控策略行为。

仅提供 C# 版本,按要求未创建 Python 文件夹。

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class KsRobotV15Strategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevEma;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public KsRobotV15Strategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA lookback", "Indicators");
		_smaPeriod = Param(nameof(SmaPeriod), 50).SetDisplay("SMA Period", "SMA filter", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_prevEma = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevClose = 0;
		_prevEma = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var sma = new SimpleMovingAverage { Length = SmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, sma, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal ema, decimal sma)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevClose = close; _prevEma = ema; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevClose = close;
			_prevEma = ema;
			return;
		}

		if (_prevClose <= _prevEma && close > ema && close > sma && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevClose >= _prevEma && close < ema && close < sma && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevClose = close;
		_prevEma = ema;
	}
}