GitHub で見る
PLC戦略
概要
PLC戦略は、StockSharpの高レベルAPIを使用してMetaTraderのエキスパートアドバイザーPLC (barabashkakvn's edition)の動作を複製します。アルゴリズムはEntry Timeframeパラメーターで指定された高い時間軸で動作し、最後に完成したキャンドルの上下にブレイクアウトストップ注文を置きます。より低い時間軸のフラクタル(デフォルトではM5とH1)を使用して注文ボリュームを動的にスケールします。オープンポジションの含み益がすべて設定された閾値を超えると、戦略はポジション全体を清算して次のセットアップを待ちます。
取引ロジック
- 新しいキャンドルの処理 – 戦略はメインの時間軸でキャンドルが完全に閉じた場合のみ反応します。すべての計算は、チャートの再描画を避けるために閉じたバーのデータで実行されます。
- 注文/ポジション維持 – 新しいセットアップを評価する前に、アルゴリズムは削除予定の待機ストップ注文をキャンセルし、前のバーで利益目標に達したポジションを閉じます。
- 価格オフセット – 最後に完成したキャンドルの高値と安値は、
Shift OHLCで設定されたpips数だけシフトされます。pip サイズは3桁または5桁のFXシンボルに対して自動的に調整されます。
- フラクタル更新 – 専用サブスクリプションがM5とH1の時間軸でフラクタルパターンを追跡します。クラシックな5バーパターンが完成したとき、最新の上昇・下降フラクタル値が保存されます。
- 距離チェック – シフトされた高値が、オープンされているロング取引の最高エントリー価格より少なくとも
Shift Position pips高い場合、またはロング取引がなくアクティブな買いストップもない場合のみ、新しい買いストップが置かれます。反対の比較を持つ同じルールが売りストップに適用されます。
- 動的ロットサイジング – ストップレベルが対応するフラクタルを上回るとき、ベースボリューム(
Buy VolumeまたはSell Volume)にM5またはH1の乗数が掛けられます。乗数をゼロに設定すると、その時間軸のスケーリングが無効になります。
- 注文登録 – ストップ注文は
BuyStop/SellStopを通じて送信されます。後のキャンセルを簡略化するために、登録された注文への参照が追跡されます。
- 利益監視 – すべてのロングおよびショートロットのオープン利益の合計(インストゥルメントのステップ値を使用)を計算後、利益が
Minimum Profitを超えたとき、戦略はポジション閉鎖モードをトリガーします。次のバーで成行注文を使用して建玉をフラットにします。
- 取引フィードバック – 待機中のストップ注文が約定されると、元のMQLロジックを模倣するために、他のすべての待機ストップがキャンセルされます。
パラメーター
| パラメーター |
説明 |
Shift OHLC |
ストップアクティベーションレベルを決定するために最後のキャンドル高値の上とキャンドル安値の下に追加するpips数。 |
Minimum Profit |
すべてのオープンポジションのクローズをトリガーする利益(インストゥルメントの通貨)。 |
Shift Position |
新しいストップレベルと既存ポジションの極端なオープン価格の間の最小距離(pips)。以前のエントリーに近すぎる注文の積み上げを防ぎます。 |
Buy Volume / Sell Volume |
ベース注文サイズ(ロット)。フラクタル乗数が適用される前に使用されます。 |
M5 Multiplier / H1 Multiplier |
ストップ価格がそれぞれの時間軸の最新フラクタルより上(ロングの場合)または下(ショートの場合)にある場合に有効化されるボリューム乗数。スケーリングを無効にするには0を使用。 |
Entry Timeframe |
エントリーを生成するために使用されるメインの時間軸。この時間軸の完成した各キャンドルが新しい評価をトリガーします。 |
M5 Fractal Timeframe |
下位フラクタル検出器に供給される時間軸(デフォルト5分)。 |
H1 Fractal Timeframe |
上位フラクタル検出器に供給される時間軸(デフォルト1時間)。 |
ポジション管理
- キャンセル – 戦略はすべての待機ストップ注文への参照を保持します。ストップ注文が約定されると、残りの待機注文はすべて次の評価サイクルでキャンセルされます。
- フラットニング –
Minimum Profitが超過されると、成行注文を使用してネットポジションがフラットにされます(ロングにはSellMarket、ショートにはBuyMarket)。フラグはポジションサイズがゼロに戻ったらクリアされます。
- インベントリ追跡 – 約定された注文は個別のロットとして記録され、最高の買いエントリー価格と最低の売りエントリー価格を区別するMetaTraderの動作を複製します。
注意事項
- デフォルトパラメーターは元のエキスパートアドバイザーの設定を反映しています。インストゥルメントが異なるコンテキストウィンドウを必要とする場合は、
M5 Fractal TimeframeとH1 Fractal Timeframeパラメーターを編集してフラクタルの時間軸を変更できます。
- ボリュームは注文を送信する前に取引所のボリュームステップに切り捨てられます。結果の値がゼロの場合、注文はスキップされます。
- 利益計算は、単位でないティック値を持つインストゥルメントとの互換性を維持するために、インストゥルメントの価格とステップ値を使用します。
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()