Supertrend Put Call Ratio
The Supertrend Put Call Ratio strategy is built around Supertrend Put Call Ratio.
Testing indicates an average annual return of about 112%. It performs best in the forex market.
Signals trigger when its indicators confirms trend changes on intraday (15m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like Period, Multiplier. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
Period = 10Multiplier = 3mPCRPeriod = 20PCRMultiplier = 2mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: multiple indicators
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (15m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Supertrend with Put/Call Ratio strategy.
/// Entry condition:
/// Long: Price > Supertrend && PCR < Avg(PCR, N) - k*StdDev(PCR, N)
/// Short: Price < Supertrend && PCR > Avg(PCR, N) + k*StdDev(PCR, N)
/// Exit condition:
/// Long: Price < Supertrend
/// Short: Price > Supertrend
/// </summary>
public class SupertrendWithPutCallRatioStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<int> _pcrPeriod;
private readonly StrategyParam<decimal> _pcrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _pcrHistory = [];
private decimal _pcrAverage;
private decimal _pcrStdDev;
private bool _isLong;
private bool _isShort;
// Simulated PCR value (in real implementation this would come from market data)
private decimal _currentPcr;
/// <summary>
/// Supertrend period.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Supertrend multiplier.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// PCR averaging period.
/// </summary>
public int PCRPeriod
{
get => _pcrPeriod.Value;
set => _pcrPeriod.Value = value;
}
/// <summary>
/// PCR standard deviation multiplier for thresholds.
/// </summary>
public decimal PCRMultiplier
{
get => _pcrMultiplier.Value;
set => _pcrMultiplier.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor with default parameters.
/// </summary>
public SupertrendWithPutCallRatioStrategy()
{
_period = Param(nameof(Period), 10)
.SetGreaterThanZero()
.SetDisplay("Supertrend Period", "Supertrend ATR period", "Supertrend Settings")
.SetOptimize(5, 20, 3);
_multiplier = Param(nameof(Multiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Supertrend Multiplier", "Supertrend ATR multiplier", "Supertrend Settings")
.SetOptimize(2m, 4m, 0.5m);
_pcrPeriod = Param(nameof(PCRPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("PCR Period", "Put/Call Ratio averaging period", "PCR Settings")
.SetOptimize(10, 30, 5);
_pcrMultiplier = Param(nameof(PCRMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("PCR Std Dev Multiplier", "Multiplier for PCR standard deviation", "PCR Settings")
.SetOptimize(1m, 3m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_pcrHistory.Clear();
_isLong = default;
_isShort = default;
_currentPcr = default;
_pcrAverage = default;
_pcrStdDev = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create Supertrend indicator
var supertrend = new SuperTrend { Length = Period, Multiplier = Multiplier };
// Subscribe to candles and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(supertrend, ProcessCandle)
.Start();
// Create chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, supertrend);
DrawOwnTrades(area);
}
}
/// <summary>
/// Process each candle and Supertrend value.
/// </summary>
private void ProcessCandle(ICandleMessage candle, decimal supertrendValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Update PCR value (in a real system, this would come from market data)
UpdatePCR(candle);
// Calculate PCR thresholds based on historical data
var bullishPcrThreshold = _pcrAverage - PCRMultiplier * _pcrStdDev;
var bearishPcrThreshold = _pcrAverage + PCRMultiplier * _pcrStdDev;
var price = candle.ClosePrice;
var priceAboveSupertrend = price > supertrendValue;
var priceBelowSupertrend = price < supertrendValue;
// Trading logic
// Entry conditions
// Long entry: Price > Supertrend && PCR < bullish threshold (bullish PCR)
if (priceAboveSupertrend && _currentPcr < bullishPcrThreshold && !_isLong && Position <= 0)
{
LogInfo($"Long signal: Price {price} > Supertrend {supertrendValue}, PCR {_currentPcr} < Threshold {bullishPcrThreshold}");
BuyMarket(Volume);
_isLong = true;
_isShort = false;
}
// Short entry: Price < Supertrend && PCR > bearish threshold (bearish PCR)
else if (priceBelowSupertrend && _currentPcr > bearishPcrThreshold && !_isShort && Position >= 0)
{
LogInfo($"Short signal: Price {price} < Supertrend {supertrendValue}, PCR {_currentPcr} > Threshold {bearishPcrThreshold}");
SellMarket(Volume);
_isShort = true;
_isLong = false;
}
// Exit conditions (based only on Supertrend, not PCR)
// Exit long: Price < Supertrend
if (_isLong && priceBelowSupertrend && Position > 0)
{
LogInfo($"Exit long: Price {price} < Supertrend {supertrendValue}");
SellMarket(Math.Abs(Position));
_isLong = false;
}
// Exit short: Price > Supertrend
else if (_isShort && priceAboveSupertrend && Position < 0)
{
LogInfo($"Exit short: Price {price} > Supertrend {supertrendValue}");
BuyMarket(Math.Abs(Position));
_isShort = false;
}
}
/// <summary>
/// Update Put/Call Ratio value.
/// In a real implementation, this would fetch data from market.
/// </summary>
private void UpdatePCR(ICandleMessage candle)
{
// Base PCR on candle pattern with some randomness
decimal pcr;
// Bullish candle tends to have lower PCR
if (candle.ClosePrice > candle.OpenPrice)
{
pcr = 0.7m + (decimal)(RandomGen.GetDouble() * 0.3);
}
// Bearish candle tends to have higher PCR
else
{
pcr = 1.0m + (decimal)(RandomGen.GetDouble() * 0.5);
}
_currentPcr = pcr;
// Add to history
_pcrHistory.Add(_currentPcr);
if (_pcrHistory.Count > PCRPeriod)
{
_pcrHistory.RemoveAt(0);
}
// Calculate average
decimal sum = 0;
foreach (var value in _pcrHistory)
{
sum += value;
}
_pcrAverage = _pcrHistory.Count > 0
? sum / _pcrHistory.Count
: 1.0m; // Default to neutral (1.0)
// Calculate standard deviation
if (_pcrHistory.Count > 1)
{
decimal sumSquaredDiffs = 0;
foreach (var value in _pcrHistory)
{
var diff = value - _pcrAverage;
sumSquaredDiffs += diff * diff;
}
_pcrStdDev = (decimal)Math.Sqrt((double)(sumSquaredDiffs / (_pcrHistory.Count - 1)));
}
else
{
_pcrStdDev = 0.1m; // Default value until we have enough data
}
LogInfo($"PCR: {_currentPcr}, Avg: {_pcrAverage}, StdDev: {_pcrStdDev}");
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("Ecng.Common")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from Ecng.Common import RandomGen
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SuperTrend
from StockSharp.Algo.Strategies import Strategy
class supertrend_put_call_ratio_strategy(Strategy):
"""
Supertrend with Put/Call Ratio strategy.
"""
def __init__(self):
super(supertrend_put_call_ratio_strategy, self).__init__()
self._period = self.Param("Period", 10) \
.SetGreaterThanZero() \
.SetDisplay("Supertrend Period", "Supertrend ATR period", "Supertrend Settings")
self._multiplier = self.Param("Multiplier", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Supertrend Multiplier", "Supertrend ATR multiplier", "Supertrend Settings")
self._pcr_period = self.Param("PCRPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("PCR Period", "Put/Call Ratio averaging period", "PCR Settings")
self._pcr_multiplier = self.Param("PCRMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("PCR Std Dev Multiplier", "Multiplier for PCR standard deviation", "PCR Settings")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._pcr_history = []
self._pcr_average = 0.0
self._pcr_std_dev = 0.0
self._is_long = False
self._is_short = False
self._current_pcr = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(supertrend_put_call_ratio_strategy, self).OnReseted()
self._pcr_history = []
self._is_long = False
self._is_short = False
self._current_pcr = 0.0
self._pcr_average = 0.0
self._pcr_std_dev = 0.0
def OnStarted2(self, time):
super(supertrend_put_call_ratio_strategy, self).OnStarted2(time)
supertrend = SuperTrend()
supertrend.Length = int(self._period.Value)
supertrend.Multiplier = Decimal(float(self._multiplier.Value))
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(supertrend, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, supertrend)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, supertrend_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
self.UpdatePCR(candle)
pcr_mult = float(self._pcr_multiplier.Value)
bullish_threshold = self._pcr_average - pcr_mult * self._pcr_std_dev
bearish_threshold = self._pcr_average + pcr_mult * self._pcr_std_dev
price = float(candle.ClosePrice)
st = float(supertrend_value)
price_above = price > st
price_below = price < st
if price_above and self._current_pcr < bullish_threshold and not self._is_long and self.Position <= 0:
self.BuyMarket(self.Volume)
self._is_long = True
self._is_short = False
elif price_below and self._current_pcr > bearish_threshold and not self._is_short and self.Position >= 0:
self.SellMarket(self.Volume)
self._is_short = True
self._is_long = False
if self._is_long and price_below and self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self._is_long = False
elif self._is_short and price_above and self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._is_short = False
def UpdatePCR(self, candle):
if candle.ClosePrice > candle.OpenPrice:
pcr = 0.7 + RandomGen.GetDouble() * 0.3
else:
pcr = 1.0 + RandomGen.GetDouble() * 0.5
self._current_pcr = pcr
self._pcr_history.append(self._current_pcr)
pcr_period = int(self._pcr_period.Value)
if len(self._pcr_history) > pcr_period:
self._pcr_history.pop(0)
total = 0.0
for v in self._pcr_history:
total += v
if len(self._pcr_history) > 0:
self._pcr_average = total / len(self._pcr_history)
else:
self._pcr_average = 1.0
if len(self._pcr_history) > 1:
sum_sq = 0.0
for v in self._pcr_history:
diff = v - self._pcr_average
sum_sq += diff * diff
self._pcr_std_dev = Math.Sqrt(sum_sq / (len(self._pcr_history) - 1))
else:
self._pcr_std_dev = 0.1
self.LogInfo("PCR: {0}, Avg: {1}, StdDev: {2}".format(self._current_pcr, self._pcr_average, self._pcr_std_dev))
def CreateClone(self):
return supertrend_put_call_ratio_strategy()