FmOne Scalping Strategy
概述
FmOne Scalping Strategy 是 FMOneEA MetaTrader 4 专家顾问的简化移植。该策略结合快慢指数移动平均线与 MACD 指标,在任何时间框架上捕捉短期动量。
工作原理
- 快速 EMA 与慢速 EMA 定义当前趋势。
- MACD 柱状图确认趋势方向的动量。
- 当快速 EMA 位于慢速 EMA 之上且 MACD 柱状图为正时开多单。
- 当快速 EMA 位于慢速 EMA 之下且 MACD 柱状图为负时开空单。
- 每个仓位都通过可配置的止损和止盈保护,可选的追踪止损可以跟随盈利移动。
参数
- FastMaPeriod – 快速 EMA 周期。
- SlowMaPeriod – 慢速 EMA 周期。
- MacdSignalPeriod – MACD 信号线周期。
- StopLossPercent – 以入场价百分比表示的止损。
- TakeProfitPercent – 以入场价百分比表示的止盈。
- EnableTrailingStop – 是否启用追踪止损。
- CandleType – 使用的K线时间框架。
注意
此移植仅实现原始 EA 的核心逻辑。原版中的赎回循环与保本功能被省略,以保持示例简洁。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// FmOne Scalping Strategy combining EMA crossover and MACD confirmation.
/// </summary>
public class FmOneScalpingStrategy : Strategy
{
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _macdSignalPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<bool> _enableTrailingStop;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastMa;
private ExponentialMovingAverage _slowMa;
private MovingAverageConvergenceDivergence _macd;
private decimal _prevFast;
private decimal _prevSlow;
private int _barsSinceTrade;
private bool _isInitialized;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastMaPeriod
{
get => _fastMaPeriod.Value;
set => _fastMaPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowMaPeriod
{
get => _slowMaPeriod.Value;
set => _slowMaPeriod.Value = value;
}
/// <summary>
/// MACD signal line period.
/// </summary>
public int MacdSignalPeriod
{
get => _macdSignalPeriod.Value;
set => _macdSignalPeriod.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Take-profit percentage.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Enable trailing stop.
/// </summary>
public bool EnableTrailingStop
{
get => _enableTrailingStop.Value;
set => _enableTrailingStop.Value = value;
}
/// <summary>
/// Minimum number of bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FmOneScalpingStrategy()
{
_fastMaPeriod = Param(nameof(FastMaPeriod), 12)
.SetRange(1, 100)
.SetDisplay("Fast EMA Period", "Period for fast EMA", "Indicators")
;
_slowMaPeriod = Param(nameof(SlowMaPeriod), 26)
.SetRange(1, 200)
.SetDisplay("Slow EMA Period", "Period for slow EMA", "Indicators")
;
_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
.SetRange(1, 50)
.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators")
;
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetRange(0.1m, 5m)
.SetDisplay("Stop Loss %", "Stop loss as percent of entry price", "Risk")
;
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetRange(0.1m, 5m)
.SetDisplay("Take Profit %", "Take profit as percent of entry price", "Risk")
;
_enableTrailingStop = Param(nameof(EnableTrailingStop), true)
.SetDisplay("Trailing Stop", "Enable trailing stop", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = default;
_slowMa = default;
_macd = default;
_prevFast = 0m;
_prevSlow = 0m;
_barsSinceTrade = CooldownBars;
_isInitialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
_macd = new MovingAverageConvergenceDivergence(
new ExponentialMovingAverage { Length = SlowMaPeriod },
new ExponentialMovingAverage { Length = FastMaPeriod });
_barsSinceTrade = CooldownBars;
_isInitialized = false;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastMa, _slowMa, _macd, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(TakeProfitPercent, UnitTypes.Percent),
stopLoss: new Unit(StopLossPercent, UnitTypes.Percent),
isStopTrailing: EnableTrailingStop
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawOwnTrades(area);
}
var macdArea = CreateChartArea();
if (macdArea != null)
DrawIndicator(macdArea, _macd);
}
private void ProcessCandle(ICandleMessage candle, decimal fastMa, decimal slowMa, decimal macdValue)
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceTrade++;
if (!_isInitialized)
{
_prevFast = fastMa;
_prevSlow = slowMa;
_isInitialized = true;
return;
}
var longSignal = _prevFast <= _prevSlow && fastMa > slowMa && macdValue > 0m;
var shortSignal = _prevFast >= _prevSlow && fastMa < slowMa && macdValue < 0m;
if (longSignal && Position <= 0 && _barsSinceTrade >= CooldownBars)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
else if (shortSignal && Position >= 0 && _barsSinceTrade >= CooldownBars)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
_prevFast = fastMa;
_prevSlow = slowMa;
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, MovingAverageConvergenceDivergence
from StockSharp.Algo.Strategies import Strategy
class fm_one_scalping_strategy(Strategy):
def __init__(self):
super(fm_one_scalping_strategy, self).__init__()
self._fast_ma_period = self.Param("FastMaPeriod", 12) \
.SetDisplay("Fast EMA Period", "Period for fast EMA", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 26) \
.SetDisplay("Slow EMA Period", "Period for slow EMA", "Indicators")
self._macd_signal_period = self.Param("MacdSignalPeriod", 9) \
.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss as percent of entry price", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0) \
.SetDisplay("Take Profit %", "Take profit as percent of entry price", "Risk")
self._enable_trailing_stop = self.Param("EnableTrailingStop", True) \
.SetDisplay("Trailing Stop", "Enable trailing stop", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for analysis", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._bars_since_trade = 0
self._is_initialized = False
@property
def FastMaPeriod(self):
return self._fast_ma_period.Value
@FastMaPeriod.setter
def FastMaPeriod(self, value):
self._fast_ma_period.Value = value
@property
def SlowMaPeriod(self):
return self._slow_ma_period.Value
@SlowMaPeriod.setter
def SlowMaPeriod(self, value):
self._slow_ma_period.Value = value
@property
def MacdSignalPeriod(self):
return self._macd_signal_period.Value
@MacdSignalPeriod.setter
def MacdSignalPeriod(self, value):
self._macd_signal_period.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
@property
def TakeProfitPercent(self):
return self._take_profit_percent.Value
@TakeProfitPercent.setter
def TakeProfitPercent(self, value):
self._take_profit_percent.Value = value
@property
def EnableTrailingStop(self):
return self._enable_trailing_stop.Value
@EnableTrailingStop.setter
def EnableTrailingStop(self, value):
self._enable_trailing_stop.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(fm_one_scalping_strategy, self).OnStarted2(time)
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.FastMaPeriod
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.SlowMaPeriod
slow_inner = ExponentialMovingAverage()
slow_inner.Length = self.SlowMaPeriod
fast_inner = ExponentialMovingAverage()
fast_inner.Length = self.FastMaPeriod
macd = MovingAverageConvergenceDivergence(slow_inner, fast_inner)
self._bars_since_trade = self.CooldownBars
self._is_initialized = False
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(fast_ma, slow_ma, macd, self.ProcessCandle) \
.Start()
self.StartProtection(
takeProfit=Unit(self.TakeProfitPercent, UnitTypes.Percent),
stopLoss=Unit(self.StopLossPercent, UnitTypes.Percent),
isStopTrailing=self.EnableTrailingStop)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, fast_ma, slow_ma, macd_value):
if candle.State != CandleStates.Finished:
return
self._bars_since_trade += 1
fast = float(fast_ma)
slow = float(slow_ma)
macd_val = float(macd_value)
if not self._is_initialized:
self._prev_fast = fast
self._prev_slow = slow
self._is_initialized = True
return
long_signal = self._prev_fast <= self._prev_slow and fast > slow and macd_val > 0.0
short_signal = self._prev_fast >= self._prev_slow and fast < slow and macd_val < 0.0
pos = self.Position
if long_signal and pos <= 0 and self._bars_since_trade >= self.CooldownBars:
self.BuyMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
elif short_signal and pos >= 0 and self._bars_since_trade >= self.CooldownBars:
self.SellMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
self._prev_fast = fast
self._prev_slow = slow
def OnReseted(self):
super(fm_one_scalping_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._bars_since_trade = self.CooldownBars
self._is_initialized = False
def CreateClone(self):
return fm_one_scalping_strategy()