Supertrend Put Call Ratio
Supertrend Put Call Ratio 策略基于 Supertrend Put Call Ratio。
测试表明年均收益约为 112%,该策略在外汇市场表现最佳。
当 its indicators confirms trend changes 在日内(15m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 Period, Multiplier 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
Period = 10Multiplier = 3mPCRPeriod = 20PCRMultiplier = 2mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: multiple indicators
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (15m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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()