Price Extreme 策略
概述
Price Extreme Strategy 是将 MetaTrader 专家顾问 Price_Extreme_Strategy 移植到 StockSharp 高级 API 的版本。策略使用最近若干根已完成 K 线的最高价和最低价构建一个动态通道。当指定的参考 K 线收盘价突破通道上轨或下轨时触发交易信号。可选的 Reverse Signals 参数允许将突破逻辑反向应用于反趋势入场。
策略以事件驱动方式运行,每当 K 线收盘时立即评估并下单,与原始 MQL 程序在下一根 K 线开盘瞬间处理信号的节奏一致。
指标逻辑
通道边界使用 StockSharp 自带的 Highest 与 Lowest 指标实时更新:
Highest追踪最近 N 根 K 线的最高价。Lowest追踪最近 N 根 K 线的最低价。
这两个指标重现了原始专家顾问中的 Price_Extreme_Indicator。参数 Level Length 决定窗口长度。Signal Shift 指定用于判定突破的历史 K 线(1 表示刚刚收盘的 K 线,较大的值会使用更早的 K 线进行确认)。
交易规则
- 每当一根 K 线收盘,重新计算通道上下边界。
- 从内部历史缓存中取出由 Signal Shift 指定的参考 K 线。
- 生成突破意图:
- 向上突破:参考 K 线的收盘价高于上轨。
- 向下突破:参考 K 线的收盘价低于下轨。
- 如果启用了 Reverse Signals,则对突破方向进行反向处理(向上突破触发做空,向下突破触发做多)。
- 检查 Enable Long 与 Enable Short,确保方向被允许。
- 开仓前如有相反方向持仓,先平仓后再建立新仓位,保持净持仓唯一。
风险控制
- Stop Loss 与 Take Profit 均以价格步长 (
Security.PriceStep) 表示,0表示关闭该功能。 - 每当净持仓数量发生变化时重新计算止损和止盈价位。
- 若完成 K 线的价格区间触及保护价位(多头触及止损则最低价低于止损价等),策略会以市价单平仓并清除目标。
- 在
OnStarted中调用StartProtection(),启用 StockSharp 的内置保护机制。
参数
| 参数 | 说明 | 默认值 | 分组 |
|---|---|---|---|
LevelLength |
计算通道所使用的历史 K 线数量。 | 5 | Indicator |
SignalShift |
判定突破时参考的已收盘 K 线序号(1 = 最近一根)。 | 1 | Indicator |
EnableLong |
是否允许做多。 | true |
Trading |
EnableShort |
是否允许做空。 | true |
Trading |
ReverseSignals |
是否反转突破信号方向。 | false |
Trading |
OrderVolume |
每次市价单的交易量。 | 1 | Trading |
StopLossPoints |
止损距离(价格步长数)。 | 0 | Risk |
TakeProfitPoints |
止盈距离(价格步长数)。 | 0 | Risk |
CandleType |
使用的主时间周期。 | 5 分钟 | Data |
所有参数均通过 StrategyParam<T> 暴露,可在 StockSharp Designer 中展示与优化。
使用建议
- 选择交易品种,并将 Candle Type 设置为希望的周期,与原始 MQL 设置保持一致。
- 调整 Level Length 控制通道宽度。数值越大通道越平滑,越小反应越灵敏。
- 根据需求设置 Signal Shift,决定是否等待更多 K 线确认。
- 通过 Enable Long、Enable Short 与 Reverse Signals 定义允许的交易方向及逻辑。
- 设定 Order Volume、Stop Loss 和 Take Profit,注意它们的单位是价格步长。
- 启动策略后,如存在图表区域,会自动绘制 K 线、指标通道以及实际成交。
备注
- 策略始终保持单一净持仓,完全复制原专家顾问在开仓前平掉反向单的行为。
- 止损与止盈在 K 线收盘时检查,实盘中可近似理解为服务器端的保护单。
- 本次仅提供 C# 版本,按要求未创建 Python 代码及其目录。
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>
/// Breakout strategy based on the Price Extreme indicator.
/// </summary>
public class PriceExtremeStrategy : Strategy
{
private readonly StrategyParam<int> _levelLength;
private readonly StrategyParam<int> _signalShift;
private readonly StrategyParam<bool> _enableLong;
private readonly StrategyParam<bool> _enableShort;
private readonly StrategyParam<bool> _reverseSignals;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<ICandleMessage> _history = new();
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _prevPosition;
private decimal _entryPrice;
private decimal _prevUpper;
private decimal _prevLower;
/// <summary>
/// Number of candles used to build extreme levels.
/// </summary>
public int LevelLength
{
get => _levelLength.Value;
set => _levelLength.Value = value;
}
/// <summary>
/// Shift in candles used for the breakout signal.
/// </summary>
public int SignalShift
{
get => _signalShift.Value;
set => _signalShift.Value = value;
}
/// <summary>
/// Enable long trades.
/// </summary>
public bool EnableLong
{
get => _enableLong.Value;
set => _enableLong.Value = value;
}
/// <summary>
/// Enable short trades.
/// </summary>
public bool EnableShort
{
get => _enableShort.Value;
set => _enableShort.Value = value;
}
/// <summary>
/// Reverse long and short signals.
/// </summary>
public bool ReverseSignals
{
get => _reverseSignals.Value;
set => _reverseSignals.Value = value;
}
/// <summary>
/// Order volume in lots.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="PriceExtremeStrategy"/>.
/// </summary>
public PriceExtremeStrategy()
{
_levelLength = Param(nameof(LevelLength), 20)
.SetGreaterThanZero()
.SetDisplay("Level Length", "Number of candles for price extremes", "Indicator")
.SetOptimize(3, 30, 1);
_signalShift = Param(nameof(SignalShift), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Shift", "Closed candles used for breakout", "Indicator");
_enableLong = Param(nameof(EnableLong), true)
.SetDisplay("Enable Long", "Allow buying trades", "Trading");
_enableShort = Param(nameof(EnableShort), true)
.SetDisplay("Enable Short", "Allow selling trades", "Trading");
_reverseSignals = Param(nameof(ReverseSignals), false)
.SetDisplay("Reverse Signals", "Invert breakout direction", "Trading");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume sent with market orders", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0)
.SetDisplay("Stop Loss", "Protective stop in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0)
.SetDisplay("Take Profit", "Profit target in price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_highs.Clear();
_lows.Clear();
_prevUpper = 0m;
_prevLower = 0m;
_entryPrice = 0m;
_prevPosition = 0m;
ResetTargets();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(3, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool CanOpenLong => EnableLong && OrderVolume > 0m;
private bool CanOpenShort => EnableShort && OrderVolume > 0m;
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_history.Add(candle);
var maxHistory = Math.Max(LevelLength + SignalShift + 2, 10);
if (_history.Count > maxHistory)
{
var removeCount = _history.Count - maxHistory;
_history.RemoveRange(0, removeCount);
_highs.RemoveRange(0, removeCount);
_lows.RemoveRange(0, removeCount);
}
if (_highs.Count < LevelLength)
return;
var upper = decimal.MinValue;
var lower = decimal.MaxValue;
for (var i = _highs.Count - LevelLength; i < _highs.Count; i++)
{
if (_highs[i] > upper) upper = _highs[i];
if (_lows[i] < lower) lower = _lows[i];
}
if (_history.Count < SignalShift)
return;
var signalCandle = _history[_history.Count - SignalShift];
var breakoutUp = candle.ClosePrice > _prevUpper && _prevUpper > 0;
var breakoutDown = candle.ClosePrice < _prevLower && _prevLower > 0;
_prevUpper = upper;
_prevLower = lower;
var wantLong = ReverseSignals ? breakoutDown : breakoutUp;
var wantShort = ReverseSignals ? breakoutUp : breakoutDown;
if (wantLong && CanOpenLong && Position == 0)
{
BuyMarket();
}
else if (wantShort && CanOpenShort && Position == 0)
{
SellMarket();
}
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Trade == null) return;
if (Position != 0 && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0)
_entryPrice = 0m;
}
private void UpdateTargets()
{
_stopPrice = null;
_takePrice = null;
var step = Security?.PriceStep ?? 0m;
if (step <= 0m || Position == 0m)
return;
if (Position > 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice - StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice + TakeProfitPoints * step;
}
else if (Position < 0m)
{
if (StopLossPoints > 0)
_stopPrice = _entryPrice + StopLossPoints * step;
if (TakeProfitPoints > 0)
_takePrice = _entryPrice - TakeProfitPoints * step;
}
}
private void ApplyRiskManagement(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
else if (Position < 0m)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
ResetTargets();
}
}
}
private void ResetTargets()
{
_stopPrice = null;
_takePrice = null;
_prevPosition = Position;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class price_extreme_strategy(Strategy):
def __init__(self):
super(price_extreme_strategy, self).__init__()
self._level_length = self.Param("LevelLength", 20)
self._signal_shift = self.Param("SignalShift", 1)
self._enable_long = self.Param("EnableLong", True)
self._enable_short = self.Param("EnableShort", True)
self._reverse_signals = self.Param("ReverseSignals", False)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LevelLength(self):
return self._level_length.Value
@property
def SignalShift(self):
return self._signal_shift.Value
@property
def EnableLong(self):
return self._enable_long.Value
@property
def EnableShort(self):
return self._enable_short.Value
@property
def ReverseSignals(self):
return self._reverse_signals.Value
def OnStarted2(self, time):
super(price_extreme_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
self.StartProtection(
Unit(3, UnitTypes.Percent),
Unit(2, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
self._highs.append(h)
self._lows.append(l)
self._history.append(c)
max_hist = max(self.LevelLength + self.SignalShift + 2, 10)
while len(self._history) > max_hist:
self._history.pop(0)
self._highs.pop(0)
self._lows.pop(0)
if len(self._highs) < self.LevelLength:
return
upper = max(self._highs[-self.LevelLength:])
lower = min(self._lows[-self.LevelLength:])
if len(self._history) < self.SignalShift:
self._prev_upper = upper
self._prev_lower = lower
return
breakout_up = c > self._prev_upper and self._prev_upper > 0
breakout_down = c < self._prev_lower and self._prev_lower > 0
self._prev_upper = upper
self._prev_lower = lower
want_long = breakout_down if self.ReverseSignals else breakout_up
want_short = breakout_up if self.ReverseSignals else breakout_down
if want_long and self.EnableLong and self.Position == 0:
self.BuyMarket()
elif want_short and self.EnableShort and self.Position == 0:
self.SellMarket()
def OnReseted(self):
super(price_extreme_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._history = []
self._prev_upper = 0.0
self._prev_lower = 0.0
def CreateClone(self):
return price_extreme_strategy()