在 GitHub 上查看
Et4 MTC v1 策略(StockSharp 版本)
概述
- 来源:MetaTrader 4 专家顾问
et4_MTC_v1.mq4。
- 目标:在 StockSharp 框架下重建原始脚本的账户管理函数、交易节流机制和参数接口,方便在此基础上追加自定义规则。
- 交易风格:模板型策略——默认不会自动开仓,仅提供完整的结构骨架与控制逻辑。
主要特性
- 参数一一对应
- 提供
TakeProfit、StopLoss、Slippage、Lots、EnableLogging 等属性,与 MQL4 中的 extern 变量保持一致。
- 新增
TradeCooldown 参数,描述脚本中固定的 30 秒交易冷却时间。
- 通过
CandleType 暴露图表数据上下文,模拟 MT4 “当前时间周期” 的行为。
- 基于余额的下单手数
- 当
Lots 为负值时,根据账户权益动态计算下单手数:floor((balance / 1000 * |Lots|) / 10) / 10,最低 0.1 手。
- 交易冷却控制
- 参考
CurTime() - LastTradeTime < 30 的原始逻辑,在任意订单操作(下单、修改、撤单、成交)后强制等待一段时间。
- 新 K 线检测
- 复刻
CheckLevels 方法的效果,通过比较连续完成蜡烛的时间来判断是否进入新柱。
- 高层 API 实现
- 使用
SubscribeCandles().Bind(...) 订阅数据,满足项目中“优先高层 API”的要求。
- 启动时调用
StartProtection() 进行仓位保护设置。
参数说明
| 参数 |
默认值 |
可优化 |
说明 |
TakeProfit |
150 |
✔️ |
盈利目标(点数),供未来扩展使用。 |
Lots |
-10 |
✔️ |
≥ 0 时固定手数;< 0 时按账户余额动态计算。 |
StopLoss |
50 |
✔️ |
止损距离(点数),预留给扩展逻辑。 |
Slippage |
3 |
✖️ |
可接受滑点(点数),保持兼容性。 |
EnableLogging |
false |
✖️ |
当因冷却限制拒绝交易时输出提示。 |
TradeCooldown |
30 秒 |
✖️ |
两次操作之间的最短间隔。 |
CandleType |
1 分钟蜡烛 |
✖️ |
决定订阅的数据类型。 |
执行流程
- 启动阶段
- 根据账户余额计算初始下单手数。
- 订阅指定蜡烛并启动风险保护。
- 蜡烛收盘
- 仅在蜡烛状态为
Finished 时继续处理。
- 更新内部的新蜡烛标记
_isNewCandle。
- 调用
IsFormedAndOnlineAndAllowTrading() 确认策略已准备好交易。
- 若冷却期尚未结束则直接返回,可选择打印提示信息。
- 依次执行
OpenPosition、ManagePosition、ClosePosition 钩子;当前版本均为空实现,用于保留结构。
- 订单与成交回调
- 在
OnOrderRegistered、OnOrderChanged、OnOrderCanceled、OnNewMyTrade 中更新 _lastTradeTime,保证任何一次操作都会刷新冷却时间。
扩展建议
- 在
OpenPosition 中编写开仓条件,并在提交订单后返回 true,防止同一根柱多次执行逻辑。
- 在
ManagePosition 中实现移动止损或保本控制。
- 在
ClosePosition 中添加离场判据。
- 如需每根新柱仅执行一次,可结合
_isNewCandle 使用。
迁移说明
- 原始 EA 仅包含函数框架,没有具体交易策略,因此本转换侧重保留辅助结构。
- 所有代码注释均为英文,符合项目规范。
- 按照
AGENTS.md 的要求统一使用制表符缩进。
- 根据任务要求,不提供 Python 版本或目录。
使用步骤
- 在 StockSharp 项目中引用
Et4MtcV1Strategy,并提前设置 Security 与 Portfolio。
- 通过属性或 UI 调整各项参数,尤其是
Lots。
- 继承该类或覆写三个钩子方法,植入自定义策略逻辑。
- 启动策略;冷却机制会阻止 30 秒内的连续操作。
测试
- 原脚本缺少可执行的交易规则,因此本转换未附带自动化测试。后续若增加实际策略,可再编写相应测试。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class Et4MtcV1Strategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevMom;
private bool _hasPrev;
private int _cooldown;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Et4MtcV1Strategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA trend filter", "Indicators");
_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMom = default;
_hasPrev = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var mom = new Momentum { Length = MomentumPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, mom, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema, decimal mom)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevMom = mom; _hasPrev = true; return; }
if (_cooldown > 0)
{
_cooldown--;
_prevMom = mom;
return;
}
if (close > ema && _prevMom <= 0 && mom > 0 && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 2;
}
else if (close < ema && _prevMom >= 0 && mom < 0 && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 2;
}
_prevMom = mom;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, Momentum
from StockSharp.Algo.Strategies import Strategy
class et4_mtc_v1_strategy(Strategy):
"""
EMA + Momentum crossover strategy.
Buys when price > EMA and momentum crosses above 0.
Sells when price < EMA and momentum crosses below 0.
"""
def __init__(self):
super(et4_mtc_v1_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._momentum_period = self.Param("MomentumPeriod", 14) \
.SetDisplay("Momentum", "Momentum period", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_mom = 0.0
self._has_prev = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(et4_mtc_v1_strategy, self).OnReseted()
self._prev_mom = 0.0
self._has_prev = False
self._cooldown = 0
def OnStarted2(self, time):
super(et4_mtc_v1_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
mom = Momentum()
mom.Length = self._momentum_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, mom, self._process_candle).Start()
def _process_candle(self, candle, ema_val, mom_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = float(candle.ClosePrice)
ema_val = float(ema_val)
mom_val = float(mom_val)
if not self._has_prev:
self._prev_mom = mom_val
self._has_prev = True
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_mom = mom_val
return
if close > ema_val and self._prev_mom <= 0 and mom_val > 0 and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 2
elif close < ema_val and self._prev_mom >= 0 and mom_val < 0 and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = 2
self._prev_mom = mom_val
def CreateClone(self):
return et4_mtc_v1_strategy()