在 GitHub 上查看
市场捕捉策略
概述
市场捕捉策略复刻了原始 MetaTrader 5 专家的思路。算法围绕一个随价格移动的中心价位构建动态网格,只要价格在中心附近来回摆动就建立对冲式的仓位。头寸分布在中心之上和之下,并设置固定的盈利目标;同时根据账户权益的里程碑来决定何时平掉亏损最重的仓位。
交易规则
- 中心线:策略保存一个内部中心价位,从处理的第一根 K 线收盘价开始。当市场偏离中心超过设定的网格间距时,会按步长逐级移动中心。
- 初始空单:为了与 MQL 脚本一致,可以在启动后立即开出一笔可选的空单。
- 做多条件:当最新收盘价高于中心且上一根 K 线曾向下穿过中心时允许做多,同时检查附近是否已经存在多单,避免在同一价位重复建仓。
- 做空条件:当最新收盘价低于中心且上一根 K 线曾向上穿过中心时允许做空,附近存在相同方向仓位时不会重复建仓。
- 止盈机制:每笔交易都会记录一个目标价,该目标等于入场价加减若干个价格步长。当 K 线最高价(多单)或最低价(空单)触及目标时,以市价平仓。
- 权益管理:策略持续监控投资组合权益。达到设定的盈利百分比后,会平掉部分浮亏最大的仓位锁定收益;达到设定的回撤百分比后,同样会减持亏损仓位以降低风险。每次触发阈值后都会重新计算新的权益基准。
参数
Enable Long / Enable Short:分别控制是否允许做多或做空。
Grid Steps:网格间距,以价格步长表示。
Take Profit Steps:止盈距离,以价格步长表示。
Open Initial Short:是否在启动时先行卖出一笔空单。
Use Equity Target:启用盈利阈值下的亏损仓位裁剪规则。
Track Drawdown:启用回撤阈值下的亏损仓位裁剪规则。
Equity Gain % / Equity Loss %:触发上述规则的权益变动百分比。
Loss Trades Up / Loss Trades Down:每次触发时最多关闭的亏损仓位数量。
Candle Type:用于决策过程的时间框架或自定义 K 线类型。
Volume(策略属性):每笔市价单的交易数量。
说明
- 策略内部维护开仓记录,在 StockSharp 的净持仓模式下尽量模拟原脚本的对冲行为。
- 距离相关的参数会乘以品种的价格步长,请确保标的提供有效的
PriceStep 数据。
- 策略仅在 K 线收盘后做出决策,选择与目标交易周期相匹配的 K 线类型即可。
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>
/// Market capture strategy using EMA crossover to capture market direction changes.
/// </summary>
public class MarketCaptureStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast;
private decimal? _prevSlow;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
public MarketCaptureStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null;
_prevSlow = null;
var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fast;
_prevSlow = slow;
return;
}
if (_prevFast == null || _prevSlow == null)
{
_prevFast = fast;
_prevSlow = slow;
return;
}
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast;
_prevSlow = slow;
if (!prevAbove && currAbove)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (prevAbove && !currAbove)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
}
}
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 market_capture_strategy(Strategy):
def __init__(self):
super(market_capture_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 8) \
.SetDisplay("Fast Period", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 21) \
.SetDisplay("Slow Period", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(market_capture_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(market_capture_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, slow_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return market_capture_strategy()