在 GitHub 上查看
该策略将 1 MINUTE SCALPER MetaTrader 4 智能交易系统迁移到 StockSharp 的高级 API。策略保留了原始 EA 的多级趋势确认、跨周期动量筛选以及月度 MACD 过滤,同时按照 StockSharp 的净持仓模式重写风险控制。
核心逻辑
- LWMA 梯队 —— 13 条线性加权移动平均线(3/5/8/10/12/15/30/35/40/45/50/55/200)必须严格按顺序排列。做多要求周期越短的均线依次位于下一条之上,做空条件相反。
- 主趋势过滤 —— 额外的一条快速 LWMA(默认 6)必须位于慢速 LWMA(默认 85)之上方可做多,做空则要求快速线位于慢速线下方。
- K 线结构 —— 仅当出现与原脚本一致的重叠结构时才允许进场:多头需要两根 K 线之前的最低价低于上一根 K 线的最高价;空头则要求上一根最低价跌破前两根的最高价。
- 动量过滤 —— 在更高周期(默认 15 分钟)上计算的 14 周期 Momentum 指标,最近三个值任意一个相对 100 的绝对偏差必须超过设定阈值,对应原代码中的
MomLevelB/MomLevelS 判断。
- 月度 MACD 偏向 —— 在
MacdCandleType(默认 30 天,近似月线)上计算的 MACD 主线必须位于信号线之上才能做多,反之做空。
仓位管理
- 初始保护 —— 止损与止盈使用价格步长表示。开仓后根据
Security.PriceStep 转换为绝对价格。
- 保本移动 —— 当浮动利润达到
BreakEvenTriggerSteps(以步长计)时,将止损移动到开仓价并加入 BreakEvenOffsetSteps 的额外空间,空头逻辑镜像处理。
- 步长追踪 ——
TrailingStopSteps 大于零时,止损会跟随自入场以来的最高/最低价,距离为指定步长。
- 资金追踪 —— 浮动盈亏超过
MoneyTrailTarget(账户货币)后记录利润峰值,一旦回撤达到 MoneyTrailStop 即平仓。
- 资金/百分比目标 —— 可选的绝对或百分比止盈在浮动盈亏达到目标时关闭仓位。百分比目标基于策略启动时的账户价值计算。
- 权益止损 —— 跟踪账户权益高点(投资组合价值 + 未实现盈亏)。若回撤超过
EquityRiskPercent,立即清空仓位,对应 EA 中的 AccountEquityHigh() 保护。
参数
| 参数 |
说明 |
Volume |
开仓手数。切换方向时会加上当前净头寸,实现一步反向。 |
FastMaPeriod / SlowMaPeriod |
主趋势过滤的 LWMA 长度。 |
MomentumPeriod |
高周期 Momentum 的长度。 |
MomentumBuyThreshold / MomentumSellThreshold |
判定做多/做空所需的最低动量偏离度。 |
MacdFastLength / MacdSlowLength / MacdSignalLength |
MACD 在 MacdCandleType 上的三组参数。 |
StopLossSteps / TakeProfitSteps |
止损、止盈距离(价格步长,0 表示禁用)。 |
TrailingStopSteps |
步长追踪的距离(0 表示关闭)。 |
BreakEvenTriggerSteps / BreakEvenOffsetSteps |
移动到保本所需的利润距离及额外偏移。 |
UseMoneyTakeProfit, MoneyTakeProfit |
启用并设置资金止盈。 |
UsePercentTakeProfit, PercentTakeProfit |
启用并设置相对初始权益的百分比止盈。 |
EnableMoneyTrailing, MoneyTrailTarget, MoneyTrailStop |
资金追踪的触发与回撤参数。 |
UseEquityStop, EquityRiskPercent |
启用权益止损并指定最大回撤百分比。 |
CandleType |
主交易周期(默认 1 分钟)。 |
MomentumCandleType |
Momentum 指标使用的周期(默认 15 分钟)。 |
MacdCandleType |
MACD 指标使用的周期(默认 30 天 ≈ 月线)。 |
与 MT4 原版的差异
- StockSharp 采用净持仓模式,因此策略始终只维护一个总仓,而不是最多
Max_Trades 个独立订单。反向时会先平掉当前仓位再开反向仓。
PercentTakeProfit 以策略启动时的账户价值为基准,而不是 MetaTrader 中随时变化的 AccountBalance(),可避免外部交易导致的误触发。
Take_Profit_In_Money 与 TRAIL_PROFIT_IN_MONEY2 在 StockSharp 中基于平均开仓价计算实时浮动盈亏,实现方式与原 EA 一致但遵循平台的保护单机制。
- 需要确保数据源能够提供
CandleType、MomentumCandleType、MacdCandleType 对应的 K 线周期,否则指标无法形成。
请根据交易品种的波动性调整参数。对于高噪声或点差较大的市场,可适当放宽步长距离或提高动量阈值,以减少噪声信号。
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>
/// One-minute scalper strategy using fast/slow WMA crossover.
/// Buys on bullish crossover, sells on bearish crossover.
/// </summary>
public class OneMinuteScalperStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private WeightedMovingAverage _fast;
private WeightedMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// Fast WMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow WMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="OneMinuteScalperStrategy"/> class.
/// </summary>
public OneMinuteScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast WMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 85)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow WMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 500)
.SetNotNegative()
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fast = null;
_slow = null;
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new WeightedMovingAverage { Length = FastPeriod };
_slow = new WeightedMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fast.IsFormed || !_slow.IsFormed)
{
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
// Check SL/TP
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
{
SellMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 60;
_prevFast = fastValue;
_prevSlow = slowValue;
return;
}
}
// WMA crossover
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 60;
}
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 60;
}
_prevFast = fastValue;
_prevSlow = slowValue;
}
}
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 WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class one_minute_scalper_strategy(Strategy):
def __init__(self):
super(one_minute_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 20).SetGreaterThanZero().SetDisplay("Fast Period", "Fast WMA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 85).SetGreaterThanZero().SetDisplay("Slow Period", "Slow WMA period", "Indicator")
self._sl_points = self.Param("StopLossPoints", 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._tp_points = self.Param("TakeProfitPoints", 500).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
def OnReseted(self):
super(one_minute_scalper_strategy, self).OnReseted()
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
def OnStarted2(self, time):
super(one_minute_scalper_strategy, self).OnStarted2(time)
self._prev_fast = 0
self._prev_slow = 0
self._entry_price = 0
self._cooldown = 0
self._fast = WeightedMovingAverage()
self._fast.Length = self._fast_period.Value
self._slow = WeightedMovingAverage()
self._slow.Length = self._slow_period.Value
sub = self.SubscribeCandles(tf(5))
sub.Bind(self._fast, self._slow, self.OnProcess).Start()
def OnProcess(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = candle.ClosePrice
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
step = float(self.Security.PriceStep)
# Check SL/TP
if self.Position > 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close <= self._entry_price - self._sl_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close >= self._entry_price + self._tp_points.Value * step:
self.SellMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self._sl_points.Value > 0 and close >= self._entry_price + self._sl_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._tp_points.Value > 0 and close <= self._entry_price - self._tp_points.Value * step:
self.BuyMarket()
self._entry_price = 0
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
return
# WMA crossover
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 60
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 60
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return one_minute_scalper_strategy()