在 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)驱动信号计算。
交易逻辑
多头流程
- 蜡烛必须从下方穿越 Kijun:可以是开盘在下、收盘在上,或上一根收盘在下且本根收盘在上,或者本根最低价触及该水平。
- 当前 Kijun 与两根之前相比保持平坦或上升,避免在基线明显下行时做多。
- LWMA 至少低于 Kijun
MaFilterPips(换算为价格单位),复刻 EA 中“均线需位于基线下若干点”的限制。
- LWMA 斜率为正,即本根值大于上一根。
- 满足条件后会记录一个待触发的多头信号,同一时间只允许一个方向处于待触发状态,对应 MQL 的
longcross/shortcross 标志。
- 当所有条件满足且当前没有净多头头寸时,以市价买入,并记录入场价用于后续止损、保本和跟踪止损管理。
空头流程
条件完全镜像:
- 蜡烛从上方穿越 Kijun(开盘在上、收盘在下,或上一根收盘在上但本根收盘在下,或最高价触及该水平)。
- Kijun 与两根之前相比保持平坦或下行。
- LWMA 至少高于 Kijun
MaFilterPips。
- LWMA 斜率为负,即当前值低于上一根。
- 仅跟踪一个待触发的空头信号,一旦出现多头信号即被清除,与原 EA 一致。
- 条件满足且当前没有净空头持仓时,执行市价卖出。
出场规则与风险控制
- 交易时间:仅当蜡烛开盘时间处于
[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 选择限价或市价,这在蜡烛回测中不可复现。
使用步骤
- 在 StockSharp 中创建策略实例,设置
Security、Portfolio、Volume,然后启动。
- 可根据交易品种调整点数参数,例如套用 MQL 注释中针对 GBPUSD、EURUSD 的优化配置。
- 通过日志观察信号、保本与跟踪止损的调整以及紧急离场,这些都会通过
LogInfo 输出。
- 在设计器或测试器中加载自动创建的图表区域(蜡烛、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;
}
}
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 ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ks_robot_v15_strategy(Strategy):
def __init__(self):
super(ks_robot_v15_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA lookback", "Indicators")
self._sma_period = self.Param("SmaPeriod", 50) \
.SetDisplay("SMA Period", "SMA filter", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 200) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def sma_period(self):
return self._sma_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ks_robot_v15_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ks_robot_v15_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
self._cooldown_remaining = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
sma = SimpleMovingAverage()
sma.Length = self.sma_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, sma, self.process_candle).Start()
def process_candle(self, candle, ema, sma):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema_val = float(ema)
sma_val = float(sma)
if not self._has_prev:
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_close = close
self._prev_ema = ema_val
return
if self._prev_close <= self._prev_ema and close > ema_val and close > sma_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_close >= self._prev_ema and close < ema_val and close < sma_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_close = close
self._prev_ema = ema_val
def CreateClone(self):
return ks_robot_v15_strategy()