在 GitHub 上查看
Proffessor v3 策略
概述
该策略是 MetaTrader 专家顾问 Proffessor v3 在 StockSharp 高层 API 中的完整实现。
核心思想保持不变:利用 ADX 判断市场状态,并在当前头寸周围构建保护与加仓网格。
- 指标:14 周期的平均趋向指数 (ADX) 以及 +DI/-DI 线。
- 模式:ADX 低于阈值视为盘整,高于阈值视为趋势。
- 委托:先建立一个市价仓位,再在价格附近布置对称的挂单以对冲、加仓或均值回归。
- 离场:未实现盈亏达到预设阈值时关闭全部仓位并撤销所有挂单。
- 时段:仅在指定的小时区间内允许开仓。
交易逻辑
状态判定
- 订阅所选蜡烛类型并计算 ADX。
- 将 ADX 信号按照
BarOffset 延后若干根收盘蜡烛,复刻 MQL 中
CopyBuffer(handle, shift) 的行为。
- 当没有仓位时,根据延后的 ADX 值做出判定:
- 盘整多头:
ADX < AdxFlatLevel 且 +DI > -DI。
- 盘整空头:
ADX < AdxFlatLevel 且 +DI < -DI。
- 趋势多头:
ADX ≥ AdxFlatLevel 且 +DI > -DI。
- 趋势空头:
ADX ≥ AdxFlatLevel 且 +DI < -DI。
网格布置
开仓后使用基础手数,在当前价格周围放置对称的挂单。所有距离均以“点”
表示,与原始 MQL 代码完全一致,并按品种最小价位进行缩放。
- 盘整多头:买入开仓,向下放置保护性 sell-stop,同时在下方挂买单、上方挂卖单。
- 盘整空头:卖出开仓,向上放置保护性 buy-stop,并在下方挂买单覆盖空头,在上方挂卖单再次做空。
- 趋势多头:买入开仓,sell-stop 用于对冲,buy-stop 用于突破加仓。
- 趋势空头:卖出开仓,sell-stop 顺势跟踪,buy-stop 防止急剧反转。
每个层级的距离为 GridStep + GridDeltaIncrement * level / 2,挂单手数依据
LotMultiplier 和 LotAddition 调整后,再根据交易所的数量步长与上下限进行规范化。
风险管理
- 未实现盈亏通过仓位的平均价与最新收盘价计算。
- 当盈亏超过
ProfitTarget 或低于 LossLimit(不为 0 时有效)即触发全平。
- 交易窗口之外会跳过所有信号,实现方式与原始
Time() 函数一致。
实现细节
- 挂单的买卖价使用最近一根蜡烛的收盘价加减半个价位来近似,
以便在蜡烛驱动的环境中复刻原始的逐笔逻辑。
- “点” 的数值依据品种价格步长进行缩放,并对三位或五位报价作出与
MQL 变量
m_adjusted_point 相同的修正。
- 所有委托在发送前都会按照交易所的价格步长、最小与最大数量进行规范化。
- 仅处理已完成的蜡烛,避免提前触发信号。
参数
| 参数 |
说明 |
Volume |
市价开仓的基础手数。 |
LotMultiplier |
对每个挂单手数应用的倍数。 |
LotAddition |
在倍数之后额外增加的手数。 |
MaxLevels |
每侧最多的网格层数。 |
GridDeltaIncrement |
深层网格的额外间隔(点)。 |
GridInitialOffset |
第一张保护性挂单的距离(点)。 |
GridStep |
相邻网格层之间的基础间隔(点)。 |
ProfitTarget |
触发全部平仓的未实现利润值。 |
LossLimit |
触发全部平仓的未实现亏损值(0 表示关闭此功能)。 |
AdxFlatLevel |
区分盘整与趋势的 ADX 阈值。 |
BarOffset |
ADX 信号延后的收盘蜡烛数量。 |
StartHour |
交易窗口开始的小时(UTC)。 |
EndHour |
交易窗口结束的小时(UTC)。 |
CandleType |
用于计算的蜡烛数据类型。 |
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>
/// Proffessor V3 strategy. Uses RSI 50-line crossover (period 10).
/// </summary>
public class ProffessorV3Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiPeriod;
private decimal? _prevRsi;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public ProffessorV3Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_rsiPeriod = Param(nameof(RsiPeriod), 10).SetGreaterThanZero().SetDisplay("RSI Period", "RSI lookback", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal rsiVal)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevRsi = rsiVal; return; }
if (_prevRsi == null) { _prevRsi = rsiVal; return; }
if (_prevRsi.Value < 45m && rsiVal >= 55m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (_prevRsi.Value > 55m && rsiVal <= 45m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
_prevRsi = rsiVal;
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class proffessor_v3_strategy(Strategy):
def __init__(self):
super(proffessor_v3_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._rsi_period = self.Param("RsiPeriod", 10) \
.SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._prev_rsi = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
def OnReseted(self):
super(proffessor_v3_strategy, self).OnReseted()
self._prev_rsi = None
def OnStarted2(self, time):
super(proffessor_v3_strategy, self).OnStarted2(time)
self._prev_rsi = None
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _on_process(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_value)
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_rsi = rv
return
if self._prev_rsi is None:
self._prev_rsi = rv
return
if self._prev_rsi < 45.0 and rv >= 55.0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_rsi > 55.0 and rv <= 45.0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_rsi = rv
def CreateClone(self):
return proffessor_v3_strategy()