GitHub で見る
大バーサウンド戦略
概要
大バーサウンド戦略はMetaTraderのエキスパートアドバイザー「BigBarSound」の動作を再現します。アルゴリズムは設定可能な時間軸の完了したローソク足を監視し、ローソク足の範囲が「大バー」とみなされるほど広い場合に報告します。オーディオファイルを再生する代わりに、StockSharpがサポートする任意の通知サブシステムにルーティングできる詳細なログメッセージを書き込みます。
この戦略は純粋に情報提供目的であり、注文を送信したりポジションを管理したりしません。より大きな自動化または裁量取引ワークフロー内のアラートコンポーネントとして使用するよう設計されています。
動作
- 戦略は ローソク足タイプ パラメーターで指定されたローソク足シリーズを購読します。
- 完了したローソク足ごとに、選択された 差分モード に従ってバーサイズを測定します:
- OpenClose – 終値と始値の絶対差。
- HighLow – バーの高値と安値の絶対差。
- 測定された値は、銘柄の
PriceStep に乗じた ポイント閾値 と比較されます。バーサイズがこの閾値以上の場合、戦略は設定されたサウンドファイルの再生をシミュレートするログエントリを記録します。
- アラート表示 が有効な場合、イベントを強調表示するための追加のアラートスタイルのログメッセージが書き込まれます。
実装は完了したローソク足のみを処理するため、各バーは最大1回しかトリガーできず、元のMQLエキスパートアドバイザーの単発動作を反映しています。
パラメーター
- ポイント閾値 (
BarPoint) – アラートがトリガーされる前に超える必要がある価格ステップ数。デフォルト値の200は元のスクリプトと一致します。便宜上、最適化境界(50から500、ステップ50)が提供されています。
- 差分モード (
DifferenceMode) – ローソク足サイズの測定方法を選択します: 始値/終値の距離または完全な高値/安値レンジ。
- サウンドファイル (
SoundFile) – 再生するWAVファイルの名前。戦略はMetaTraderの PlaySound 呼び出しをエミュレートするためにこの値をログに記録するだけです。
- アラート表示 (
ShowAlert) – 有効にすると、戦略はMQLバージョンのオプションの Alert ポップアップを模倣するための追加ログメッセージを出力します。
- ローソク足タイプ (
CandleType) – 購読するローソク足データタイプ(時間軸)。デフォルトでは戦略は1分ローソク足を使用します。
アラートとログ
戦略はサウンドファイルが再生されたであろうことを告知するために LogInfo を使用し、別のアラートメッセージを提供するために AddInfoLog を使用します。これらのエントリには銘柄識別子、ローソク足のタイムスタンプ、測定されたバーサイズが含まれており、StockSharpのログビューアーや通知シンクとの統合が容易になります。
ブローカーが有効な PriceStep を提供しない場合、戦略が動作し続けるようにフォールバック値として 1 が使用されます。銘柄の実際のティックサイズを反映するように ポイント閾値 を調整してください。
使用上の注意
- ローソク足データを公開する任意の銘柄に戦略をアタッチしてください。アラートはFX、先物、株式、暗号資産で等しく機能します。
- ログ出力を購読するか、クラスを拡張してカスタムハンドラーにイベントを転送することで、他の取引戦略と組み合わせてください。
- 実装は注文を生成しないため、
Volume とポジション関連のパラメーターは無視されます。
- 可聴通知を生成するには、StockSharpのログサブシステムをサウンドノティファイアに接続するか、プラットフォーム固有のオーディオAPIを呼び出すようにコードを拡張してください。
元のMQLエキスパートアドバイザーとの違い
- 元のスクリプトはティックデータで動作し、バーの変化を手動で追跡していました。StockSharpのポートは完了したローソク足を直接処理し、別のトリガーフラグを維持せずにバーごとに正確に1つのアラートを保証します。
- オーディオ再生はログメッセージに置き換えられ、StockSharp環境内での動作がクロスプラットフォームなまま維持されます。
- パラメーター名はStockSharpの慣例に従いますが、同じセマンティクスを保持します: ポイント単位の閾値サイズ、測定モード、オプションのアラート、サウンド名。
要件
追加のインジケーターは不要です。戦略が処理のために完了したローソク足を受信できるよう、選択した CandleType が接続されたデータソースでサポートされていることを確認するだけです。
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>
/// Strategy that trades when a candle exceeds a configurable size threshold.
/// Based on the BigBarSound MetaTrader EA concept - trades in the direction of
/// large candles with ATR-based stop-loss and take-profit.
/// </summary>
public class BigBarSoundStrategy : Strategy
{
/// <summary>
/// Defines how the candle size is calculated.
/// </summary>
public enum BigBarDifferenceModes
{
/// <summary>
/// Measure the difference between close and open prices.
/// </summary>
OpenClose,
/// <summary>
/// Measure the distance between the high and low of the candle.
/// </summary>
HighLow,
}
private readonly StrategyParam<int> _barPoint;
private readonly StrategyParam<BigBarDifferenceModes> _differenceMode;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrStopMultiplier;
private readonly StrategyParam<decimal> _atrTpMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
private decimal _takeProfitPrice;
private int _direction;
/// <summary>
/// Number of price steps required to trigger the alert.
/// </summary>
public int BarPoint
{
get => _barPoint.Value;
set => _barPoint.Value = value;
}
/// <summary>
/// Defines how the candle size is calculated.
/// </summary>
public BigBarDifferenceModes DifferenceMode
{
get => _differenceMode.Value;
set => _differenceMode.Value = value;
}
/// <summary>
/// ATR period for stop/take-profit calculations.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for stop-loss distance.
/// </summary>
public decimal AtrStopMultiplier
{
get => _atrStopMultiplier.Value;
set => _atrStopMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier for take-profit distance.
/// </summary>
public decimal AtrTpMultiplier
{
get => _atrTpMultiplier.Value;
set => _atrTpMultiplier.Value = value;
}
/// <summary>
/// Candle type used to monitor the market.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="BigBarSoundStrategy"/> class.
/// </summary>
public BigBarSoundStrategy()
{
_barPoint = Param(nameof(BarPoint), 180)
.SetGreaterThanZero()
.SetDisplay("Point Threshold", "Number of price steps required to trigger entry", "General")
.SetOptimize(50, 500, 50);
_differenceMode = Param(nameof(DifferenceMode), BigBarDifferenceModes.OpenClose)
.SetDisplay("Difference Mode", "How the candle size is calculated", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 28, 7);
_atrStopMultiplier = Param(nameof(AtrStopMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Stop Mult", "ATR multiplier for stop-loss", "Risk")
.SetOptimize(1m, 3m, 0.5m);
_atrTpMultiplier = Param(nameof(AtrTpMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("ATR TP Mult", "ATR multiplier for take-profit", "Risk")
.SetOptimize(1m, 4m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to monitor", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = 0m;
_takeProfitPrice = 0m;
_direction = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
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;
// Manage existing position
if (Position > 0 && _direction > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takeProfitPrice)
{
SellMarket(Position);
_direction = 0;
_stopPrice = 0m;
_takeProfitPrice = 0m;
}
}
else if (Position < 0 && _direction < 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takeProfitPrice)
{
BuyMarket(Math.Abs(Position));
_direction = 0;
_stopPrice = 0m;
_takeProfitPrice = 0m;
}
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0)
return;
if (atrValue <= 0m)
return;
// Calculate candle size
var difference = DifferenceMode == BigBarDifferenceModes.OpenClose
? Math.Abs(candle.ClosePrice - candle.OpenPrice)
: candle.HighPrice - candle.LowPrice;
var priceStep = Security?.PriceStep;
var step = priceStep is null or <= 0m ? 1m : priceStep.Value;
var threshold = step * BarPoint;
if (difference < threshold)
return;
var isBullish = candle.ClosePrice > candle.OpenPrice;
var stopDist = atrValue * AtrStopMultiplier;
var tpDist = atrValue * AtrTpMultiplier;
if (isBullish)
{
BuyMarket(Volume);
_direction = 1;
_stopPrice = candle.ClosePrice - stopDist;
_takeProfitPrice = candle.ClosePrice + tpDist;
}
else
{
SellMarket(Volume);
_direction = -1;
_stopPrice = candle.ClosePrice + stopDist;
_takeProfitPrice = candle.ClosePrice - tpDist;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
OPEN_CLOSE = 0
HIGH_LOW = 1
class big_bar_sound_strategy(Strategy):
def __init__(self):
super(big_bar_sound_strategy, self).__init__()
self._bar_point = self.Param("BarPoint", 180)
self._difference_mode = self.Param("DifferenceMode", OPEN_CLOSE)
self._atr_period = self.Param("AtrPeriod", 14)
self._atr_stop_multiplier = self.Param("AtrStopMultiplier", 2.0)
self._atr_tp_multiplier = self.Param("AtrTpMultiplier", 3.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._stop_price = 0.0
self._take_profit_price = 0.0
self._direction = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(big_bar_sound_strategy, self).OnReseted()
self._stop_price = 0.0
self._take_profit_price = 0.0
self._direction = 0
def OnStarted2(self, time):
super(big_bar_sound_strategy, self).OnStarted2(time)
self._stop_price = 0.0
self._take_profit_price = 0.0
self._direction = 0
atr = AverageTrueRange()
atr.Length = int(self._atr_period.Value)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self.OnProcess).Start()
def OnProcess(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
pos = float(self.Position)
if pos > 0 and self._direction > 0:
if float(candle.LowPrice) <= self._stop_price or float(candle.HighPrice) >= self._take_profit_price:
self.SellMarket(pos)
self._direction = 0
self._stop_price = 0.0
self._take_profit_price = 0.0
elif pos < 0 and self._direction < 0:
if float(candle.HighPrice) >= self._stop_price or float(candle.LowPrice) <= self._take_profit_price:
self.BuyMarket(abs(pos))
self._direction = 0
self._stop_price = 0.0
self._take_profit_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position != 0:
return
atr_v = float(atr_value)
if atr_v <= 0:
return
close = float(candle.ClosePrice)
open_p = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if int(self._difference_mode.Value) == OPEN_CLOSE:
difference = abs(close - open_p)
else:
difference = high - low
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and sec.PriceStep > 0 else 1.0
threshold = step * float(self._bar_point.Value)
if difference < threshold:
return
is_bullish = close > open_p
stop_dist = atr_v * float(self._atr_stop_multiplier.Value)
tp_dist = atr_v * float(self._atr_tp_multiplier.Value)
vol = float(self.Volume)
if is_bullish:
self.BuyMarket(vol)
self._direction = 1
self._stop_price = close - stop_dist
self._take_profit_price = close + tp_dist
else:
self.SellMarket(vol)
self._direction = -1
self._stop_price = close + stop_dist
self._take_profit_price = close - tp_dist
def CreateClone(self):
return big_bar_sound_strategy()