ダブルボトム・ダブルトップ・ハンター戦略
この戦略は、直近の安値と高値を比較することでダブルボトムとダブルトップのパターンを探します。ダブルボトムは最安値が広いルックバック期間内で2回到達した時に発生し、ダブルトップは最高値を使用します。ロングとショートのポジションはそれぞれ開設され、新たな極値が形成された後に価格が反対の極値を突破した時に決済されます。
詳細
- エントリー条件:
- ロング: ダブルボトム検出。
- ショート: ダブルトップ検出。
- ロング/ショート: 両方向。
- エグジット条件:
- ロング: 前回高値を超える新高値で価格が前回安値を下回る。
- ショート: 前回安値を下回る新安値で価格が前回高値を上回る。
- ストップ: なし。
- デフォルト値:
Length= 100Lookback= 100
- フィルター:
- カテゴリ: パターン
- 方向: 両方
- インジケーター: Highest, Lowest
- ストップ: いいえ
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 低
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Double Bottom and Top Hunter strategy.
/// The recent window is the previous Length candles and the wider window adds the Lookback candles before them. A double bottom is
/// a candle whose low reaches the lowest low of the older part of the wider window again while the recent candles stayed above it,
/// and that closes back above it; a double top mirrors this with highs. A double bottom goes long and a double top goes short,
/// reversing an opposite position. A long closes once price has made a new high above the recent high and then closes below the
/// recent low; a short closes once price has made a new low below the recent low and then closes above the recent high.
/// </summary>
public class DoubleBottomAndTopHunterStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = [];
private readonly List<decimal> _lows = [];
private bool _newExtremeSinceEntry;
/// <summary>
/// Candles in the recent window.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Older candles that widen the window.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DoubleBottomAndTopHunterStrategy()
{
_length = Param(nameof(Length), 100)
.SetGreaterThanZero()
.SetDisplay("Length", "Candles in the recent window", "Pattern");
_lookback = Param(nameof(Lookback), 100)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Older candles that widen the window", "Pattern");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_highs.Clear();
_lows.Clear();
_newExtremeSinceEntry = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var total = Length + Lookback;
if (_highs.Count < total)
{
AddCandle(candle, total);
return;
}
// Oldest first: the first Lookback entries are the older part, the last Length entries the recent one.
var olderHigh = _highs.Take(Lookback).Max();
var olderLow = _lows.Take(Lookback).Min();
var recentHigh = _highs.Skip(Lookback).Max();
var recentLow = _lows.Skip(Lookback).Min();
AddCandle(candle, total);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var doubleBottom = olderLow < recentLow && candle.LowPrice <= olderLow && close > olderLow;
var doubleTop = olderHigh > recentHigh && candle.HighPrice >= olderHigh && close < olderHigh;
if (doubleBottom && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_newExtremeSinceEntry = false;
return;
}
if (doubleTop && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_newExtremeSinceEntry = false;
return;
}
if (Position > 0)
{
if (candle.HighPrice > recentHigh)
_newExtremeSinceEntry = true;
else if (_newExtremeSinceEntry && close < recentLow)
{
SellMarket(Position);
_newExtremeSinceEntry = false;
}
}
else if (Position < 0)
{
if (candle.LowPrice < recentLow)
_newExtremeSinceEntry = true;
else if (_newExtremeSinceEntry && close > recentHigh)
{
BuyMarket(-Position);
_newExtremeSinceEntry = false;
}
}
}
private void AddCandle(ICandleMessage candle, int total)
{
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
while (_highs.Count > total)
{
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class double_bottom_and_top_hunter_strategy(Strategy):
"""
Double Bottom and Top Hunter strategy.
The recent window is the previous Length candles and the wider window adds the Lookback candles before them. A double bottom is
a candle whose low reaches the lowest low of the older part of the wider window again while the recent candles stayed above it,
and that closes back above it; a double top mirrors this with highs. A double bottom goes long and a double top goes short,
reversing an opposite position. A long closes once price has made a new high above the recent high and then closes below the
recent low; a short closes once price has made a new low below the recent low and then closes above the recent high.
"""
def __init__(self):
super(double_bottom_and_top_hunter_strategy, self).__init__()
self._length = self.Param("Length", 100).SetGreaterThanZero().SetDisplay("Length", "Candles in the recent window", "Pattern")
self._lookback = self.Param("Lookback", 100).SetGreaterThanZero().SetDisplay("Lookback", "Older candles that widen the window", "Pattern")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._highs = []
self._lows = []
self._new_extreme_since_entry = False
def OnReseted(self):
super(double_bottom_and_top_hunter_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(double_bottom_and_top_hunter_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _add_candle(self, candle, total):
self._highs.append(candle.HighPrice)
self._lows.append(candle.LowPrice)
while len(self._highs) > total:
self._highs.pop(0)
self._lows.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
lookback = self._lookback.Value
total = self._length.Value + lookback
if len(self._highs) < total:
self._add_candle(candle, total)
return
# Oldest first: the first Lookback entries are the older part, the last Length entries the recent one.
older_high = max(self._highs[:lookback])
older_low = min(self._lows[:lookback])
recent_high = max(self._highs[lookback:])
recent_low = min(self._lows[lookback:])
self._add_candle(candle, total)
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
double_bottom = older_low < recent_low and candle.LowPrice <= older_low and close > older_low
double_top = older_high > recent_high and candle.HighPrice >= older_high and close < older_high
if double_bottom and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._new_extreme_since_entry = False
return
if double_top and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._new_extreme_since_entry = False
return
if self.Position > 0:
if candle.HighPrice > recent_high:
self._new_extreme_since_entry = True
elif self._new_extreme_since_entry and close < recent_low:
self.SellMarket(self.Position)
self._new_extreme_since_entry = False
elif self.Position < 0:
if candle.LowPrice < recent_low:
self._new_extreme_since_entry = True
elif self._new_extreme_since_entry and close > recent_high:
self.BuyMarket(-self.Position)
self._new_extreme_since_entry = False
def CreateClone(self):
return double_bottom_and_top_hunter_strategy()