OBVダイバージェンス戦略
On-Balance Volume(OBV)は、出来高が価格に先行するという考えのもと、累積取引量を追跡します。価格が新しい高値を形成してもOBVが確認しない場合、またはその逆の場合、リバーサルが起きている可能性があります。この戦略はその乖離(ダイバージェンス)を使って持続不可能な動きに逆張りします。
テストでは年平均リターンが約112%であることが示されています。外国為替市場で最も良いパフォーマンスを発揮します。
各ローソク足ごとにOBVが更新され、前回の読みと比較されます。価格がより低い安値を付ける一方でOBVがより高い安値を記録する場合に強気シグナルが発生します。価格がより高い高値まで上昇するがOBVが遅れる場合に弱気シグナルが発生します。移動平均が決済ポイントを提供し、パーセントストップが損失を制御します。
このアプローチは出来高の枯渇後の平均回帰を捉えることを試み、通常は価格が平均線を再び越えるまでポジションを保持します。
詳細
- エントリー条件: 価格/OBVのダイバージェンス。
- ロング/ショート: 両方。
- エグジット条件: 価格が移動平均を越えるか、ストップロス。
- ストップ: はい、パーセントベース。
- デフォルト値:
DivergencePeriod= 5MAPeriod= 20CandleType= 5 minuteStopLossPercent= 2
- フィルター:
- カテゴリ: ダイバージェンス
- 方向: 両方
- インジケーター: OBV, MA
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: はい
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// OBV (On-Balance Volume) Divergence strategy.
/// Bullish divergence: the low falls below the lows of the previous DivergencePeriod candles while OBV stays above its value
/// at the earlier low. Bearish divergence: the high rises above their highs while OBV stays below its value at the earlier high.
/// A divergence opens a position while flat; it closes when the close crosses back over the moving average or at the percent stop.
/// </summary>
public class ObvDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _divergencePeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal High, decimal Low, decimal Obv)> _history = [];
private decimal _obv;
private decimal? _prevClose;
/// <summary>
/// Number of previous candles the new extreme is compared with.
/// </summary>
public int DivergencePeriod
{
get => _divergencePeriod.Value;
set => _divergencePeriod.Value = value;
}
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ObvDivergenceStrategy()
{
_divergencePeriod = Param(nameof(DivergencePeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Divergence Period", "Previous candles the new extreme is compared with", "Indicators");
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA exit signal", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_obv = 0;
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_history.Clear();
_obv = 0;
_prevClose = null;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_prevClose is decimal prevClose)
{
if (close > prevClose)
_obv += candle.TotalVolume;
else if (close < prevClose)
_obv -= candle.TotalVolume;
}
_prevClose = close;
// Compare this candle with the DivergencePeriod candles before it.
var previous = _history.ToArray();
_history.Add((candle.HighPrice, candle.LowPrice, _obv));
if (_history.Count > DivergencePeriod)
_history.RemoveAt(0);
if (previous.Length < DivergencePeriod || !smaValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ma = smaValue.GetValue<decimal>();
if (Position > 0)
{
if (close > ma)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (close < ma)
BuyMarket(-Position);
return;
}
var lowest = previous.MinBy(c => c.Low);
var highest = previous.MaxBy(c => c.High);
var bullish = candle.LowPrice < lowest.Low && _obv > lowest.Obv;
var bearish = candle.HighPrice > highest.High && _obv < highest.Obv;
if (bullish && !bearish)
BuyMarket(Volume);
else if (bearish && !bullish)
SellMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class obv_divergence_strategy(Strategy):
"""
OBV (On-Balance Volume) Divergence strategy.
Bullish divergence: the low falls below the lows of the previous DivergencePeriod candles while OBV stays above its value
at the earlier low. Bearish divergence: the high rises above their highs while OBV stays below its value at the earlier high.
A divergence opens a position while flat; it closes when the close crosses back over the moving average or at the percent stop.
"""
def __init__(self):
super(obv_divergence_strategy, self).__init__()
self._divergence_period = self.Param("DivergencePeriod", 5).SetGreaterThanZero().SetDisplay("Divergence Period", "Previous candles the new extreme is compared with", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period for SMA exit signal", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._history = []
self._obv = Decimal(0)
self._prev_close = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(obv_divergence_strategy, self).OnReseted()
self._history = []
self._obv = Decimal(0)
self._prev_close = None
def OnStarted2(self, time):
super(obv_divergence_strategy, self).OnStarted2(time)
self._history = []
self._obv = Decimal(0)
self._prev_close = None
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
if self._prev_close is not None:
if close > self._prev_close:
self._obv += candle.TotalVolume
elif close < self._prev_close:
self._obv -= candle.TotalVolume
self._prev_close = close
# Compare this candle with the DivergencePeriod candles before it.
period = self._divergence_period.Value
previous = list(self._history)
self._history.append((candle.HighPrice, candle.LowPrice, self._obv))
if len(self._history) > period:
self._history.pop(0)
if len(previous) < period or not sma_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ma = sma_value.GetValue[Decimal](None)
if self.Position > 0:
if close > ma:
self.SellMarket(self.Position)
return
if self.Position < 0:
if close < ma:
self.BuyMarket(-self.Position)
return
lowest = min(previous, key=lambda c: c[1])
highest = max(previous, key=lambda c: c[0])
bullish = candle.LowPrice < lowest[1] and self._obv > lowest[2]
bearish = candle.HighPrice > highest[0] and self._obv < highest[2]
if bullish and not bearish:
self.BuyMarket(self.Volume)
elif bearish and not bullish:
self.SellMarket(self.Volume)
def CreateClone(self):
return obv_divergence_strategy()