在 GitHub 上查看
趋势剥头皮策略(API/3858)
概览
TrendScalperStrategy 是 MetaTrader 4 专家顾问 Currencyprofits_01_1.mq4 的 StockSharp 移植版本。原始脚本是一套轻量的趋势型剥头皮系统:通过 EMA/SMA 交叉确认趋势,并利用最近几个高点和低点的突破来寻找入场机会。本移植版本保持原有交易逻辑,同时使用 StockSharp 的高级蜡烛订阅与指标管线实现。
交易逻辑
- 指标
- 快速 EMA(默认 6)基于收盘价。
- 慢速 SMA(默认 12)基于收盘价。
- 最近 N 根 K 线的最高价与最低价(默认窗口 6)。
- 入场条件
- 做多:价格触及最近的低点带(
Lowest Low),且快速 EMA 高于慢速 SMA。策略按照资金管理规则下达市价买单。
- 做空:价格触及最近的高点带(
Highest High),且快速 EMA 低于慢速 SMA。策略发送市价卖单。
- 持仓期间不会再开新仓,与 MQL 版本的“单笔持仓”行为保持一致。
- 出场条件
- 多单平仓:当 K 线高点突破
Highest High 时,立即市价卖出平仓。
- 空单平仓:当 K 线低点跌破
Lowest Low 时,市价买入平仓。
- 若
StopLossPoints > 0,StartProtection 会在开仓时附加平台托管的止损单。
资金管理
与原始 EA 相同,提供三个仓位管理模式:
| 模式 |
说明 |
移植版本的行为 |
0 |
固定手数(LotsIfNoMM)。 |
直接返回 FixedVolume。 |
<0 |
根据账户余额和风险因子计算的分数手。 |
计算 ceil(balance * risk / 10000) / 10,最大不超过 100 手。 |
>0 |
根据余额与风险因子的整数手。 |
采用同一公式,但结果向上取整为整数,最少 1 手、最多 100 手。 |
账户余额优先使用 Portfolio.CurrentValue,若不可用则退回到 BeginValue。若依旧无法获取,则使用固定手数以保证回测可运行。
风险控制
- 止损:
StopLossPoints 以价格点(pip)表示,在 OnStarted 中乘以 Security.PriceStep 后传入 StartProtection,由框架自动管理保护性订单。
- 单笔持仓:只有在
Position == 0 时才允许开新仓,完全复刻原脚本的防重叠仓位逻辑。
参数
| 名称 |
默认值 |
说明 |
CandleType |
15 分钟 |
计算指标与产生信号所用的蜡烛序列。 |
FastLength |
6 |
快速 EMA 周期。 |
SlowLength |
12 |
慢速 SMA 周期。 |
BreakoutWindow |
6 |
统计最高/最低价时参考的蜡烛数量。 |
FixedVolume |
0.1 手 |
在关闭资金管理或缺少余额信息时使用的手数。 |
MoneyManagementMode |
0 |
选择固定、分数或整数手的模式。 |
MoneyManagementRisk |
40 |
余额驱动仓位大小的风险系数。 |
StopLossPoints |
50 |
止损距离(点数),会在启动时换算成绝对价格。 |
实现细节
- 通过
SubscribeCandles().Bind(...) 串联指标,不需要自行维护历史价格缓存。
- 代码中的注释全部使用英文,以符合仓库规范。
- 未改动任何测试文件,本次提交仅包含策略移植与文档。
使用建议
- 根据原策略的定位选择合适的短周期(剥头皮)K 线。
- 确保标的证券的
PriceStep 已正确设置,以便止损距离能从点数准确换算为价格。
- 调整
MoneyManagementRisk 时需谨慎,值越大意味着通过 ceil(balance * risk / 10000) 公式计算出的仓位越大。
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend Scalper strategy - EMA crossover with Highest/Lowest breakout confirmation.
/// Buys when fast EMA crosses above slow EMA and close is near highest.
/// Sells when fast EMA crosses below slow EMA and close is near lowest.
/// </summary>
public class TrendScalperStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public TrendScalperStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 8)
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21)
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
protected override void OnReseted() { base.OnReseted(); _prevFast = 0m; _prevSlow = 0m; _hasPrev = false; }
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (!_hasPrev)
{
_prevFast = fast;
_prevSlow = slow;
_hasPrev = true;
return;
}
if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevFast = fast;
_prevSlow = slow;
}
}
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
from StockSharp.Algo.Strategies import Strategy
class trend_scalper_strategy(Strategy):
def __init__(self):
super(trend_scalper_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 8).SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21).SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._channel_period = self.Param("ChannelPeriod", 20).SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False
@property
def fast_period(self): return self._fast_period.Value
@property
def slow_period(self): return self._slow_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(trend_scalper_strategy, self).OnReseted()
self._prev_fast = 0.0; self._prev_slow = 0.0; self._has_prev = False
def OnStarted2(self, time):
super(trend_scalper_strategy, self).OnStarted2(time)
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.fast_period
slow = ExponentialMovingAverage()
slow.Length = self.slow_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, self.process_candle).Start()
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished: return
f = float(fast); s = float(slow)
if not self._has_prev:
self._prev_fast = f; self._prev_slow = s; self._has_prev = True; return
if self._prev_fast <= self._prev_slow and f > s and self.Position <= 0:
if self.Position < 0: self.BuyMarket()
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and f < s and self.Position >= 0:
if self.Position > 0: self.SellMarket()
self.SellMarket()
self._prev_fast = f; self._prev_slow = s
def CreateClone(self): return trend_scalper_strategy()