ホーム
/
戦略のサンプル
GitHub で見る
Session Order Sentiment Strategy
Overview
This strategy trades based on the imbalance between buy and sell orders observed in the order book. It measures ratios of order counts and total volumes for both sides of the book and opens a position when the dominance of one side exceeds configurable thresholds. Trading is allowed only during a specified time window.
After a position is opened, the thresholds are reduced to monitor the opposite side. If the opposite side grows beyond these reduced thresholds, the position is closed. A stop loss and take profit are also applied using absolute price points.
Trading Rules
Long Entry : Buy when
BUY volume / SELL volume >= DiffVolumesEx and BUY orders / SELL orders >= DiffTradersEx
Either side meets MinTraders and MinVolume
Current time passes CheckTradingTime
Short Entry : Sell when the above logic is mirrored for the sell side.
Exit :
Close the long when SELL volume / BUY volume > 1 / DiffVolumes or SELL orders / BUY orders > 1 / DiffTraders
Close the short when SELL volume / BUY volume < DiffVolumes or SELL orders / BUY orders < DiffTraders
Close all positions outside of trading hours
Stops : Uses Stop Loss and Take Profit measured in price points.
Parameters
MinVolume – minimum total volume required on either side of the book (default: 20000)
MinTraders – minimum number of orders on either side (default: 1000)
DiffVolumesEx – volume ratio required for entry (default: 2.0)
DiffTradersEx – order count ratio required for entry (default: 1.5)
MinDiffVolumesEx – volume ratio used after position entry (default: 1.5)
MinDiffTradersEx – order count ratio used after position entry (default: 1.3)
SleepMinutes – delay between order book checks in minutes (default: 5)
TpPips – take profit in price points (default: 500)
SlPips – stop loss in price points (default: 500)
Notes
The strategy does not include a Python version.
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 trading on volume sentiment using candle data.
/// Compares bullish vs bearish volume over a lookback period.
/// </summary>
public class SessionOrderSentimentStrategy : Strategy
{
private readonly StrategyParam<decimal> _volumeRatio;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<decimal> _stopLossPct;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal vol, bool isBull)> _volumeHistory = new();
private decimal _entryPrice;
/// <summary>
/// Volume ratio required for entry.
/// </summary>
public decimal VolumeRatio
{
get => _volumeRatio.Value;
set => _volumeRatio.Value = value;
}
/// <summary>
/// Lookback period in candles.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPct
{
get => _stopLossPct.Value;
set => _stopLossPct.Value = value;
}
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="SessionOrderSentimentStrategy"/>.
/// </summary>
public SessionOrderSentimentStrategy()
{
_volumeRatio = Param(nameof(VolumeRatio), 1.5m)
.SetDisplay("Volume Ratio", "Bull/bear volume ratio for entry", "General")
.SetGreaterThanZero();
_lookback = Param(nameof(Lookback), 10)
.SetDisplay("Lookback", "Number of candles to look back", "General")
.SetGreaterThanZero();
_stopLossPct = Param(nameof(StopLossPct), 1m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var isBull = candle.ClosePrice >= candle.OpenPrice;
_volumeHistory.Add((candle.TotalVolume, isBull));
if (_volumeHistory.Count > Lookback)
_volumeHistory.RemoveAt(0);
if (_volumeHistory.Count < Lookback)
return;
var bullVolume = 0m;
var bearVolume = 0m;
foreach (var (vol, bull) in _volumeHistory)
{
if (bull)
bullVolume += vol;
else
bearVolume += vol;
}
if (bearVolume == 0) bearVolume = 1;
if (bullVolume == 0) bullVolume = 1;
var bullBearRatio = bullVolume / bearVolume;
var bearBullRatio = bearVolume / bullVolume;
var close = candle.ClosePrice;
// Check stop loss
if (Position > 0 && close <= _entryPrice * (1m - StopLossPct / 100m))
{
SellMarket();
return;
}
if (Position < 0 && close >= _entryPrice * (1m + StopLossPct / 100m))
{
BuyMarket();
return;
}
// Bullish sentiment
if (bullBearRatio >= VolumeRatio)
{
if (Position < 0)
{
BuyMarket();
}
if (Position <= 0)
{
_entryPrice = close;
BuyMarket();
}
}
// Bearish sentiment
else if (bearBullRatio >= VolumeRatio)
{
if (Position > 0)
{
SellMarket();
}
if (Position >= 0)
{
_entryPrice = close;
SellMarket();
}
}
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeHistory.Clear();
_entryPrice = 0m;
}
}
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.Strategies import Strategy
class session_order_sentiment_strategy(Strategy):
def __init__(self):
super(session_order_sentiment_strategy, self).__init__()
self._volume_ratio = self.Param("VolumeRatio", 1.5) \
.SetDisplay("Volume Ratio", "Bull/bear volume ratio for entry", "General")
self._lookback = self.Param("Lookback", 10) \
.SetDisplay("Lookback", "Number of candles to look back", "General")
self._stop_loss_pct = self.Param("StopLossPct", 1.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._volume_history = []
self._entry_price = 0.0
@property
def volume_ratio(self):
return self._volume_ratio.Value
@property
def lookback(self):
return self._lookback.Value
@property
def stop_loss_pct(self):
return self._stop_loss_pct.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(session_order_sentiment_strategy, self).OnReseted()
self._volume_history = []
self._entry_price = 0.0
def OnStarted2(self, time):
super(session_order_sentiment_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
is_bull = float(candle.ClosePrice) >= float(candle.OpenPrice)
vol = float(candle.TotalVolume)
lb = int(self.lookback)
self._volume_history.append((vol, is_bull))
if len(self._volume_history) > lb:
self._volume_history.pop(0)
if len(self._volume_history) < lb:
return
bull_volume = 0.0
bear_volume = 0.0
for v, b in self._volume_history:
if b:
bull_volume += v
else:
bear_volume += v
if bear_volume == 0:
bear_volume = 1.0
if bull_volume == 0:
bull_volume = 1.0
bull_bear_ratio = bull_volume / bear_volume
bear_bull_ratio = bear_volume / bull_volume
close = float(candle.ClosePrice)
sl = float(self.stop_loss_pct)
vr = float(self.volume_ratio)
# Check stop loss
if self.Position > 0 and close <= self._entry_price * (1.0 - sl / 100.0):
self.SellMarket()
return
if self.Position < 0 and close >= self._entry_price * (1.0 + sl / 100.0):
self.BuyMarket()
return
# Bullish sentiment
if bull_bear_ratio >= vr:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self._entry_price = close
self.BuyMarket()
# Bearish sentiment
elif bear_bull_ratio >= vr:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self._entry_price = close
self.SellMarket()
def CreateClone(self):
return session_order_sentiment_strategy()