在 GitHub 上查看
PLC 策略
概述
PLC 策略在 StockSharp 高级 API 中复刻了 MetaTrader 专家顾问 PLC (barabashkakvn's edition) 的运行方式。算法基于参数“Entry Timeframe”指定的主周期,在最近一根已完成 K 线的高点上方和低点下方分别挂入突破止损单。默认会订阅 M5 与 H1 两个周期的分形指标,以此动态放大下单手数。当持仓的浮动利润超过设定的阈值时,策略会清空持仓并重新等待下一个信号。
运行流程
- 新 K 线触发:只有当主周期的 K 线完全收盘后才会开始计算,避免信号重绘。
- 维护阶段:在评估新信号之前,策略会取消需要删除的止损单,并在上一根 K 线达到盈利目标时平掉仓位。
- 价格偏移:上一根 K 线的最高价和最低价会按照
Shift OHLC 参数向外偏移若干点(自动适配 3/5 位报价)。
- 分形更新:通过独立订阅追踪 M5 和 H1 周期的五根 K 线分形,记录最近一次上、下分形的价格。
- 距离判断:只有当新的买入止损价距离当前所有多单的最高开仓价至少
Shift Position 点,或当前没有多单且没有挂单时,才会挂出新的买入止损。卖出条件与之相反。
- 手数调整:当触发价突破对应周期的分形时,基础手数(
Buy Volume/Sell Volume)会乘以 M5 或 H1 的放大系数;系数设为 0 可关闭该周期的加仓功能。
- 下单管理:通过
BuyStop/SellStop 发送止损单,并保存返回的订单对象,便于后续取消。
- 盈利目标:依据品种的
PriceStep 和 StepPrice 计算持仓浮盈,当盈利超过 Minimum Profit 时设置关闭标志,在下一次循环用市价单平仓。
- 成交反馈:一旦某个止损单成交,其余止损单都会被取消,以与原始 MQL 逻辑保持一致。
参数
| 参数 |
说明 |
Shift OHLC |
相对于上一根 K 线高低点的偏移量(单位:点)。 |
Minimum Profit |
触发全部平仓的盈利阈值。 |
Shift Position |
新挂止损价与当前持仓开仓价之间的最小间距,防止密集加仓。 |
Buy Volume / Sell Volume |
未应用分形乘数之前的基础下单手数。 |
M5 Multiplier / H1 Multiplier |
当止损价突破对应周期分形时的手数乘数,0 表示关闭。 |
Entry Timeframe |
产生信号的主周期。 |
M5 Fractal Timeframe |
计算低周期分形所使用的周期(默认 5 分钟)。 |
H1 Fractal Timeframe |
计算高周期分形所使用的周期(默认 1 小时)。 |
仓位控制
- 止损单管理:策略保存所有挂出的止损单引用,当任一止损单成交后,其余止损单会在下一次检查时被取消。
- 盈利平仓:盈利达到阈值时,使用市价单平掉净头寸(多头用
SellMarket,空头用 BuyMarket),仓位归零后重置关闭标志。
- 持仓跟踪:每笔成交会记录为独立的仓位单元,用于还原最高买入价与最低卖出价,保持与 MetaTrader 相同的统计方式。
注意事项
- 默认参数按照原始 EA 设置,可以根据品种特性调整主周期和分形周期。
- 下单量会根据交易所的数量步长向下取整,如果结果为 0 则跳过下单。
- 盈利计算使用
PriceStep 与 StepPrice,适用于 tick 价值不为 1 的品种。
namespace StockSharp.Samples.Strategies;
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// PLC (Price Level Channel) strategy.
/// Buys when price breaks above the previous candle's high plus an offset,
/// sells when price breaks below the previous candle's low minus an offset.
/// Uses ATR to dynamically adjust the offset.
/// </summary>
public class PlcStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _shiftPips;
private readonly StrategyParam<int> _atrPeriod;
private decimal _prevHigh;
private decimal _prevLow;
private bool _initialized;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int ShiftPips
{
get => _shiftPips.Value;
set => _shiftPips.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public PlcStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_shiftPips = Param(nameof(ShiftPips), 15)
.SetGreaterThanZero()
.SetDisplay("Shift Pips", "Offset added to candle high/low for breakout", "Trading");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for volatility measurement", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0m;
_prevLow = 0m;
_initialized = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_initialized = false;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_initialized)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_initialized = true;
return;
}
var shift = atrValue * ShiftPips / 100m;
var buyLevel = _prevHigh + shift;
var sellLevel = _prevLow - shift;
// Buy breakout: close above previous high + shift
if (candle.ClosePrice > buyLevel && Position <= 0)
{
BuyMarket();
}
// Sell breakout: close below previous low - shift
else if (candle.ClosePrice < sellLevel && Position >= 0)
{
SellMarket();
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class plc_strategy(Strategy):
def __init__(self):
super(plc_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for calculations", "General")
self._shift_pips = self.Param("ShiftPips", 15) \
.SetDisplay("Shift Pips", "Offset added to candle high/low for breakout", "Trading")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for volatility measurement", "Indicators")
self._prev_high = 0.0
self._prev_low = 0.0
self._initialized = False
@property
def CandleType(self):
return self._candle_type.Value
@property
def ShiftPips(self):
return self._shift_pips.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
def OnReseted(self):
super(plc_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._initialized = False
def OnStarted2(self, time):
super(plc_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._initialized = False
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.Bind(atr, self._on_process) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def _on_process(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
av = float(atr_value)
if not self._initialized:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._initialized = True
return
shift = av * self.ShiftPips / 100.0
buy_level = self._prev_high + shift
sell_level = self._prev_low - shift
close = float(candle.ClosePrice)
if close > buy_level and self.Position <= 0:
self.BuyMarket()
elif close < sell_level and self.Position >= 0:
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return plc_strategy()