TTM Squeeze戦略
TTM Squeeze戦略は、Bollinger BandsがKeltner Channels内に収縮する価格圧縮の期間を 探します。この「スクイーズ」は潜在的なボラティリティ拡大を示します。スクイーズの間、 戦略は方向を測るために線形回帰モメンタムオシレーターとRSIを監視します。スクイーズが 解放されてモメンタムが転換すると、動きの方向にポジションが取られます。
この手法は、静かなレンジからの爆発的なブレイクアウトを探します。取引はロングのセット アップにはRSIが30を超えた状態でモメンタムがゼロ以下から上昇することを要求し、ショート のセットアップにはRSIが70を下回った状態でモメンタムがプラス領域から下落することが必要 なようにフィルタリングされます。オプションのテイクプロフィットパラメーターは、事前定義 された利益で取引を自動的に閉じることができます。
詳細
- エントリー条件:
- スクイーズオフ(Bollinger BandsがKeltner Channelsの外側)。
- ロング: モメンタム < 0 かつ上昇中、RSI > 30。
- ショート: モメンタム > 0 かつ下降中、RSI < 70。
- ロング/ショート: 両方向。
- エグジット条件:
- 反対のシグナルまたは有効な場合はテイクプロフィット。
- ストップ: デフォルトはなし、オプションのテイクプロフィット。
- デフォルト値:
SqueezeLength= 20RsiLength= 14UseTP= FalseTpPercent= 1.2
- フィルター:
- カテゴリ: ボラティリティブレイクアウト
- 方向: 両方
- インジケーター: Bollinger Bands、Keltner Channels、RSI、線形回帰
- ストップ: オプション
- 複雑さ: 中
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// TTM Squeeze Strategy.
/// Bollinger Bands (2 deviations) and Keltner Channels (1.5 average true ranges) span SqueezeLength bars. When the squeeze is
/// off (the bands lie outside the channels) a long opens if the linear regression momentum is below zero and rising with RSI
/// above 30, and a short opens if momentum is above zero and falling with RSI below 70. An opposite signal reverses the
/// position; UseTP enables a TpPercent take profit.
/// </summary>
public class TtmSqueezeStrategy : Strategy
{
private const decimal BollingerWidth = 2m;
private const decimal KeltnerMultiplier = 1.5m;
private readonly StrategyParam<int> _squeezeLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<bool> _useTp;
private readonly StrategyParam<decimal> _tpPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _closeSma;
private SimpleMovingAverage _rangeSma;
private Highest _highest;
private Lowest _lowest;
private LinearReg _momentum;
private decimal? _prevClose;
private decimal? _prevMomentum;
/// <summary>
/// Length of the bands, the channels and the momentum.
/// </summary>
public int SqueezeLength
{
get => _squeezeLength.Value;
set => _squeezeLength.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Enable the take profit.
/// </summary>
public bool UseTP
{
get => _useTp.Value;
set => _useTp.Value = value;
}
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal TpPercent
{
get => _tpPercent.Value;
set => _tpPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public TtmSqueezeStrategy()
{
_squeezeLength = Param(nameof(SqueezeLength), 20)
.SetGreaterThanZero()
.SetDisplay("Squeeze Length", "Length of the bands, the channels and the momentum", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_useTp = Param(nameof(UseTP), false)
.SetDisplay("Use TP", "Enable the take profit", "Risk");
_tpPercent = Param(nameof(TpPercent), 1.2m)
.SetGreaterThanZero()
.SetDisplay("TP %", "Take profit percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = null;
_prevMomentum = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevMomentum = null;
var bollinger = new BollingerBands { Length = SqueezeLength, Width = BollingerWidth };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
_closeSma = new SimpleMovingAverage { Length = SqueezeLength };
_rangeSma = new SimpleMovingAverage { Length = SqueezeLength };
_highest = new Highest { Length = SqueezeLength };
_lowest = new Lowest { Length = SqueezeLength };
_momentum = new LinearReg { Length = SqueezeLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, rsi, ProcessCandle)
.Start();
if (UseTP)
StartProtection(new Unit(TpPercent, UnitTypes.Percent), new Unit(), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var time = candle.OpenTime;
var close = candle.ClosePrice;
var trueRange = _prevClose is decimal pc
? Math.Max(candle.HighPrice - candle.LowPrice, Math.Max(Math.Abs(candle.HighPrice - pc), Math.Abs(candle.LowPrice - pc)))
: candle.HighPrice - candle.LowPrice;
_prevClose = close;
var sma = _closeSma.Process(close, time, true).ToDecimal();
var rangeAverage = _rangeSma.Process(trueRange, time, true).ToDecimal();
var highest = _highest.Process(candle.HighPrice, time, true).ToDecimal();
var lowest = _lowest.Process(candle.LowPrice, time, true).ToDecimal();
if (!_closeSma.IsFormed || !_rangeSma.IsFormed || !_highest.IsFormed || !_lowest.IsFormed)
return;
// Momentum is the linear regression of the close's distance from the mean of the Donchian midline and the SMA.
var basis = ((highest + lowest) / 2m + sma) / 2m;
var momentumValue = _momentum.Process(close - basis, time, true);
if (!_momentum.IsFormed)
return;
var momentum = momentumValue.ToDecimal();
var prevMomentum = _prevMomentum;
_prevMomentum = momentum;
if (prevMomentum is not decimal previous)
return;
if (!bollingerValue.IsFormed || !rsiValue.IsFormed)
return;
if (bollingerValue is not IBollingerBandsValue { UpBand: decimal bbUpper, LowBand: decimal bbLower })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var kcUpper = sma + KeltnerMultiplier * rangeAverage;
var kcLower = sma - KeltnerMultiplier * rangeAverage;
var squeezeOff = bbUpper > kcUpper && bbLower < kcLower;
if (!squeezeOff)
return;
var rsi = rsiValue.GetValue<decimal>();
if (momentum < 0m && momentum > previous && rsi > 30m && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (momentum > 0m && momentum < previous && rsi < 70m && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex, SimpleMovingAverage, Highest, Lowest, LinearReg
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
BOLLINGER_WIDTH = 2.0
KELTNER_MULTIPLIER = 1.5
class ttm_squeeze_strategy(Strategy):
"""
TTM Squeeze Strategy.
Bollinger Bands (2 deviations) and Keltner Channels (1.5 average true ranges) span SqueezeLength bars. When the squeeze is
off (the bands lie outside the channels) a long opens if the linear regression momentum is below zero and rising with RSI
above 30, and a short opens if momentum is above zero and falling with RSI below 70. An opposite signal reverses the
position; UseTP enables a TpPercent take profit.
"""
def __init__(self):
super(ttm_squeeze_strategy, self).__init__()
self._squeeze_length = self.Param("SqueezeLength", 20).SetGreaterThanZero().SetDisplay("Squeeze Length", "Length of the bands, the channels and the momentum", "Indicators")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Indicators")
self._use_tp = self.Param("UseTP", False).SetDisplay("Use TP", "Enable the take profit", "Risk")
self._tp_percent = self.Param("TpPercent", 1.2).SetGreaterThanZero().SetDisplay("TP %", "Take profit percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._close_sma = None
self._range_sma = None
self._highest = None
self._lowest = None
self._momentum = None
self._prev_close = None
self._prev_momentum = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ttm_squeeze_strategy, self).OnReseted()
self._prev_close = None
self._prev_momentum = None
def OnStarted2(self, time):
super(ttm_squeeze_strategy, self).OnStarted2(time)
self._prev_close = None
self._prev_momentum = None
length = self._squeeze_length.Value
bollinger = BollingerBands()
bollinger.Length = length
bollinger.Width = Decimal(BOLLINGER_WIDTH)
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
self._close_sma = SimpleMovingAverage()
self._close_sma.Length = length
self._range_sma = SimpleMovingAverage()
self._range_sma.Length = length
self._highest = Highest()
self._highest.Length = length
self._lowest = Lowest()
self._lowest.Length = length
self._momentum = LinearReg()
self._momentum.Length = length
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, rsi, self._process_candle).Start()
if self._use_tp.Value:
self.StartProtection(Unit(Decimal(float(self._tp_percent.Value)), UnitTypes.Percent), Unit(), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, bollinger_value, rsi_value):
if candle.State != CandleStates.Finished:
return
time = candle.OpenTime
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._prev_close is not None:
pc = self._prev_close
true_range = max(high - low, abs(high - pc), abs(low - pc))
else:
true_range = high - low
self._prev_close = close
sma = float(process_float(self._close_sma, candle.ClosePrice, time, True).GetValue[Decimal](None))
range_average = float(process_float(self._range_sma, true_range, time, True).GetValue[Decimal](None))
highest = float(process_float(self._highest, candle.HighPrice, time, True).GetValue[Decimal](None))
lowest = float(process_float(self._lowest, candle.LowPrice, time, True).GetValue[Decimal](None))
if not self._close_sma.IsFormed or not self._range_sma.IsFormed or not self._highest.IsFormed or not self._lowest.IsFormed:
return
# Momentum is the linear regression of the close's distance from the mean of the Donchian midline and the SMA.
basis = ((highest + lowest) / 2.0 + sma) / 2.0
momentum_value = process_float(self._momentum, close - basis, time, True)
if not self._momentum.IsFormed:
return
momentum = float(momentum_value.GetValue[Decimal](None))
previous = self._prev_momentum
self._prev_momentum = momentum
if previous is None:
return
if not bollinger_value.IsFormed or not rsi_value.IsFormed:
return
if bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
bb_upper = float(bollinger_value.UpBand)
bb_lower = float(bollinger_value.LowBand)
kc_upper = sma + KELTNER_MULTIPLIER * range_average
kc_lower = sma - KELTNER_MULTIPLIER * range_average
squeeze_off = bb_upper > kc_upper and bb_lower < kc_lower
if not squeeze_off:
return
rsi = float(rsi_value.GetValue[Decimal](None))
if momentum < 0 and momentum > previous and rsi > 30 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif momentum > 0 and momentum < previous and rsi < 70 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return ttm_squeeze_strategy()