GitHub で見る
バックボーン バスケット戦略 (StockSharp)
概要
バックボーン バスケット戦略 は、元の MetaTrader 4「Backbone.mq4」エキスパート アドバイザを StockSharp の高レベル API に移植します。システムは、ビッド/アスクの極値を収集して最初の取引方向を決定し、ロングバスケットとショートバスケットを交互に切り替えます。各バスケットは徐々に構築され、設定された MaxTrades カウントに達するか、保護注文がポジションを閉じるまで、完了したローソク足ごとに 1 つの成行注文が追加されます。リスク管理は、口座額とストップロス距離によって取引量をスケールするフラクショナルリスクモデルを通じて維持されます。
マーケットデータの流れ
- キャンドル (
CandleType) – 完了したキャンドルは意思決定のペースを速めます。 MT4 スクリプトとまったく同様に、完成したバーごとに 1 つの注文のみを発行できます。
- 注文帳のスナップショット – トレーリングストップの計算と最初の「極端な」検出ロジックを再現するために、最良の買い値と売り値が追跡されます。
- 戦略状態 – StockSharp
Strategy ベースは、実行中のポジション、平均エントリー価格、保護注文の管理に使用される損益を維持します。
取引ロジック
- 初期調整 – 方向性は定義されていませんが、戦略は確認された最高入札額と最低売値を記録します。価格がこれらの極端な値から
TrailingStopPoints * PriceStep だけ戻ると、最初のバスケットの方向が選択されます。
- 注文の順序付け –
- 最後に完了した取引が短期 (
_lastPositionDirection == -1) で、オープン取引がない場合は、新しい 成行買い注文が送信されます。
- 前回の取引が長く(
_lastPositionDirection == 1)、バスケットにまだ容量がある場合、後続のローソク足で追加の買い注文が送信されます。
- 対称ルールは、最後の取引がロングだった場合の売り注文に適用されます。
- ボリュームサイジング – すべての新しい注文は、MT4 からインスピレーションを得た
Vol() アナログを呼び出します。利用可能な口座価値 (現在価値 → 残高 → 開始残高) に MaxRisk を乗算し、PriceStepCost を使用して金額に換算したストップロス距離で割ります。結果は VolumeStep と一致し、MinVolume/MaxVolume によって制限され、最小取引サイズを下回る場合は拒否されます。
- 保護注文 – 取引が実行されると、戦略はバスケット全体をカバーする単一のストップロス注文と利食い注文を発行します。 MQL バージョンと同様に、距離は「ポイント」(価格ステップ)で表されます。
- トレーリングストップ –
StopLossPoints と TrailingStopPoints の両方がプラスの場合、価格が記録されたエントリー価格を超えてトレーリング距離を超えて変動するたびに、利益を確定するためにストップ注文が再発行されます。ロング バスケットでは、最高入札額が参照として使用されます。短いバスケットはベストアスクを使用します。
- バスケットの完了 – ストップロス注文またはテイクプロフィット注文が実行されると、すべての内部カウンターがリセットされ、
LastPosition は変更されないままになるため、次のローソク足が逆方向にバスケットを開始し、元の EA の動作を反映します。
資金管理
- MQL エキスパートと同じ分数式
1 / (MaxTrades / MaxRisk - openTrades) を使用します。
- リスク資本は
Portfolio.CurrentValue から推定され、その後、CurrentBalance または BeginBalance に戻ります。
VolumeStep に調整した後、計算されたサイズが楽器の MinVolume を下回る場合、ボリュームは破棄されます。
- ストップロス注文とテイクプロフィット注文は出来高が変化するたびに再作成されるため、常にバスケット全体が保護されます。
パラメーター
| 名前 |
デフォルト |
説明 |
CandleType |
15分の時間枠 |
新しい決定をトリガーするために使用されるローソク足の間隔。 |
MaxRisk |
0.5 |
次の注文のサイジング時に考慮されるポートフォリオの割合。ポジティブである必要があります。 |
MaxTrades |
10 |
現在のバスケットに蓄積できる取引の最大数。 |
TakeProfitPoints |
170 |
価格ステップで測定される利食い距離。無効にするには、0 に設定します。 |
StopLossPoints |
40 |
価格ステップで測定されるストップロス距離。トレーリングとポジションのサイジングに必要です。 |
TrailingStopPoints |
300 |
価格ステップにおけるトレーリングストップ距離。静的な停止を維持するには、0 に設定します。 |
変換メモ
- 元の EA は各注文を個別に変更します。 StockSharp バージョンでは、StockSharp のポジションがデフォルトでネッティングされているため、バスケットごとに 1 つの集約されたストップロスとテイクプロフィットを管理します。
- ボリュームのサイジングは
Security.PriceStepCost に依存します。コネクタがこの値を提供しない場合、戦略は構成された Volume プロパティに戻ります。
- トレーリング更新は、新しいローソク足が到着すると適用され、MT4 スクリプトの「バーごとに 1 回」の動作 (
Bars > PrevBars の場合にのみ動作します) と一致します。
- 交互ロジックは、最後に実行された方向を
_lastPositionDirection に保持するため、バスケットが閉じられると、ソース コードと同様に、次のローソク足が自動的に反対方向にバスケットを開きます。
- C# 実装のみが提供されます。このディレクトリには Python ポートはありません。
使用のヒント
- 正確な
PriceStep、PriceStepCost、およびボリュームのメタデータを楽器に割り当てて、現実的な位置サイズを取得します。
- バックテストを行うときは、トレーリングストップ ロジックが最適な買値/売値にアクセスできるように、注文ブック フィードが利用可能であることを確認してください。
- アグレッシブ スケーリングを無効にするには、
MaxTrades を増やすか、MaxRisk を減らして、Vol() の置換で返されるボリュームが小さくなるようにします。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Backbone strategy that alternates between long and short based on retracements from recent extremes.
/// Uses ATR to detect when price has pulled back enough from its high/low to enter a new position.
/// </summary>
public class BackboneBasketStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _retraceMultiplier;
private decimal _highestPrice;
private decimal _lowestPrice;
private decimal _entryPrice;
private int _lastDirection; // 1 = long, -1 = short, 0 = none
public BackboneBasketStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for analysis.", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR indicator.", "Indicators");
_retraceMultiplier = Param(nameof(RetraceMultiplier), 2m)
.SetDisplay("Retrace Multiplier", "ATR multiplier for retracement threshold.", "Signals");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public decimal RetraceMultiplier
{
get => _retraceMultiplier.Value;
set => _retraceMultiplier.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highestPrice = 0;
_lowestPrice = decimal.MaxValue;
_entryPrice = 0;
_lastDirection = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highestPrice = 0;
_lowestPrice = decimal.MaxValue;
_entryPrice = 0;
_lastDirection = 0;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue <= 0)
return;
var close = candle.ClosePrice;
// Track extremes
if (close > _highestPrice)
_highestPrice = close;
if (close < _lowestPrice)
_lowestPrice = close;
var threshold = atrValue * RetraceMultiplier;
// Exit existing positions on retracement
if (Position > 0 && close < _highestPrice - threshold)
{
SellMarket();
_entryPrice = 0;
_lastDirection = 1;
_lowestPrice = close;
}
else if (Position < 0 && close > _lowestPrice + threshold)
{
BuyMarket();
_entryPrice = 0;
_lastDirection = -1;
_highestPrice = close;
}
// Entry logic - alternate direction
if (Position == 0)
{
if (_lastDirection != 1 && close < _highestPrice - threshold)
{
// Price pulled back from high - sell
SellMarket();
_entryPrice = close;
_lastDirection = -1;
_lowestPrice = close;
}
else if (_lastDirection != -1 && close > _lowestPrice + threshold)
{
// Price bounced from low - buy
BuyMarket();
_entryPrice = close;
_lastDirection = 1;
_highestPrice = close;
}
}
}
}
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 backbone_basket_strategy(Strategy):
def __init__(self):
super(backbone_basket_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe used for analysis.", "General")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR indicator.", "Indicators")
self._retrace_multiplier = self.Param("RetraceMultiplier", 2.0) \
.SetDisplay("Retrace Multiplier", "ATR multiplier for retracement threshold.", "Signals")
self._highest_price = 0.0
self._lowest_price = float('inf')
self._entry_price = 0.0
self._last_direction = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
@property
def atr_period(self):
return self._atr_period.Value
@atr_period.setter
def atr_period(self, value):
self._atr_period.Value = value
@property
def retrace_multiplier(self):
return self._retrace_multiplier.Value
@retrace_multiplier.setter
def retrace_multiplier(self, value):
self._retrace_multiplier.Value = value
def OnReseted(self):
super(backbone_basket_strategy, self).OnReseted()
self._highest_price = 0.0
self._lowest_price = float('inf')
self._entry_price = 0.0
self._last_direction = 0
def OnStarted2(self, time):
super(backbone_basket_strategy, self).OnStarted2(time)
self._highest_price = 0.0
self._lowest_price = float('inf')
self._entry_price = 0.0
self._last_direction = 0
atr = AverageTrueRange()
atr.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def OnProcess(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
if atr_value <= 0:
return
close = float(candle.ClosePrice)
if close > self._highest_price:
self._highest_price = close
if close < self._lowest_price:
self._lowest_price = close
threshold = float(atr_value) * self.retrace_multiplier
# Exit existing positions on retracement
if self.Position > 0 and close < self._highest_price - threshold:
self.SellMarket()
self._entry_price = 0.0
self._last_direction = 1
self._lowest_price = close
elif self.Position < 0 and close > self._lowest_price + threshold:
self.BuyMarket()
self._entry_price = 0.0
self._last_direction = -1
self._highest_price = close
# Entry logic - alternate direction
if self.Position == 0:
if self._last_direction != 1 and close < self._highest_price - threshold:
self.SellMarket()
self._entry_price = close
self._last_direction = -1
self._lowest_price = close
elif self._last_direction != -1 and close > self._lowest_price + threshold:
self.BuyMarket()
self._entry_price = close
self._last_direction = 1
self._highest_price = close
def CreateClone(self):
return backbone_basket_strategy()