モメンタム Alligator 4h Bitcoin戦略
モメンタム Alligator 4h Bitcoin戦略は、日足時間軸でAwesome OscillatorとBill WilliamsのAlligatorを組み合わせます。オシレーターが5期間SMAを上抜けし、価格が日足Alligatorの3本の線すべてを上回って取引されているときにロングポジションを建てます。動的ストップロスはエントリーからのパーセント下落とAlligatorのジョー(下顎)ラインのうち大きい方を使用します。利益確定のエグジット後、戦略は次の2つのシグナルをスキップします。
詳細
- エントリー条件: AOが5期間SMAを上抜けし、終値が日足Alligatorの各ラインを上回っているとき。
- ロング/ショート: ロングのみ。
- エグジット条件: パーセントストップとAlligatorのジョーの最大値での動的ストップロス。
- ストップ: はい。
- デフォルト値:
StopLossPercent= 0.02mCandleType= TimeSpan.FromHours(4)TradeStart= 2023-01-01TradeStop= 2025-01-01
- フィルター:
- カテゴリ: モメンタム
- 方向: ロングのみ
- インジケーター: Awesome Oscillator、Alligator
- ストップ: はい
- 複雑さ: 基本
- 時間軸: 中期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Momentum Alligator 4h Bitcoin strategy.
/// Goes long when the Awesome Oscillator crosses above its 5-period SMA and the close is above the jaw, teeth and lips
/// of the daily Alligator, only between TradeStart and TradeStop. The position is closed by a stop at the higher of the
/// percent stop below entry and the daily jaw. After a profitable exit the next two entry signals are skipped.
/// </summary>
public class MomentumAlligator4hBitcoinStrategy : Strategy
{
private const int _signalsToSkip = 2;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DateTimeOffset> _tradeStart;
private readonly StrategyParam<DateTimeOffset> _tradeStop;
private SimpleMovingAverage _aoSma;
private SmoothedMovingAverage _jaw;
private SmoothedMovingAverage _teeth;
private SmoothedMovingAverage _lips;
private decimal? _prevAo;
private decimal? _prevAoSma;
private decimal? _dailyJaw;
private decimal? _dailyTeeth;
private decimal? _dailyLips;
private decimal _entryPrice;
private int _skipCount;
/// <summary>
/// Percent stop below the entry price, as a fraction (0.02 = 2%).
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type of the trading timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Entries are allowed from this time.
/// </summary>
public DateTimeOffset TradeStart
{
get => _tradeStart.Value;
set => _tradeStart.Value = value;
}
/// <summary>
/// Entries are allowed until this time.
/// </summary>
public DateTimeOffset TradeStop
{
get => _tradeStop.Value;
set => _tradeStop.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MomentumAlligator4hBitcoinStrategy()
{
_stopLossPercent = Param(nameof(StopLossPercent), 0.02m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Percent stop below entry as a fraction", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_tradeStart = Param(nameof(TradeStart), new DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("Trade Start", "Entries are allowed from this time", "General");
_tradeStop = Param(nameof(TradeStop), new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero))
.SetDisplay("Trade Stop", "Entries are allowed until this time", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, TimeSpan.FromDays(1).TimeFrame())];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevAo = null;
_prevAoSma = null;
_dailyJaw = null;
_dailyTeeth = null;
_dailyLips = null;
_entryPrice = 0m;
_skipCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ao = new AwesomeOscillator();
_aoSma = new SimpleMovingAverage { Length = 5 };
_jaw = new SmoothedMovingAverage { Length = 13 };
_teeth = new SmoothedMovingAverage { Length = 8 };
_lips = new SmoothedMovingAverage { Length = 5 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ao, ProcessCandle)
.Start();
SubscribeCandles(TimeSpan.FromDays(1).TimeFrame())
.Bind(ProcessDailyCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, ao);
DrawIndicator(oscillators, _aoSma);
}
}
}
private void ProcessDailyCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// The Alligator lines are smoothed averages of the median price.
var median = (candle.HighPrice + candle.LowPrice) / 2m;
var jaw = _jaw.Process(new DecimalIndicatorValue(_jaw, median, candle.OpenTime) { IsFinal = true }).ToDecimal();
var teeth = _teeth.Process(new DecimalIndicatorValue(_teeth, median, candle.OpenTime) { IsFinal = true }).ToDecimal();
var lips = _lips.Process(new DecimalIndicatorValue(_lips, median, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (_jaw.IsFormed)
_dailyJaw = jaw;
if (_teeth.IsFormed)
_dailyTeeth = teeth;
if (_lips.IsFormed)
_dailyLips = lips;
}
private void ProcessCandle(ICandleMessage candle, decimal ao)
{
if (candle.State != CandleStates.Finished)
return;
var aoSma = _aoSma.Process(new DecimalIndicatorValue(_aoSma, ao, candle.OpenTime) { IsFinal = true }).ToDecimal();
if (!_aoSma.IsFormed)
{
_prevAo = ao;
return;
}
var prevAo = _prevAo;
var prevAoSma = _prevAoSma;
_prevAo = ao;
_prevAoSma = aoSma;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
var stop = _entryPrice * (1m - StopLossPercent);
if (_dailyJaw is decimal jawStop && jawStop > stop)
stop = jawStop;
if (candle.LowPrice <= stop)
{
var exitPrice = Math.Min(stop, candle.OpenPrice);
if (exitPrice > _entryPrice)
_skipCount = _signalsToSkip;
SellMarket(Position);
}
return;
}
if (prevAo is not decimal pa || prevAoSma is not decimal ps)
return;
if (_dailyJaw is not decimal jaw || _dailyTeeth is not decimal teeth || _dailyLips is not decimal lips)
return;
var crossUp = pa <= ps && ao > aoSma;
if (!crossUp)
return;
var close = candle.ClosePrice;
if (close <= jaw || close <= teeth || close <= lips)
return;
var openTime = candle.OpenTime;
if (openTime < TradeStart.UtcDateTime || openTime >= TradeStop.UtcDateTime)
return;
if (_skipCount > 0)
{
_skipCount--;
return;
}
_entryPrice = close;
BuyMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, DateTimeOffset
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AwesomeOscillator, SimpleMovingAverage, SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
SIGNALS_TO_SKIP = 2
class momentum_alligator_4h_bitcoin_strategy(Strategy):
"""
Momentum Alligator 4h Bitcoin strategy.
Goes long when the Awesome Oscillator crosses above its 5-period SMA and the close is above the jaw, teeth and lips
of the daily Alligator, only between TradeStart and TradeStop. The position is closed by a stop at the higher of the
percent stop below entry and the daily jaw. After a profitable exit the next two entry signals are skipped.
"""
def __init__(self):
super(momentum_alligator_4h_bitcoin_strategy, self).__init__()
self._stop_loss_percent = self.Param("StopLossPercent", 0.02).SetNotNegative().SetDisplay("Stop Loss %", "Percent stop below entry as a fraction", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._trade_start = self.Param("TradeStart", DateTimeOffset(2023, 1, 1, 0, 0, 0, TimeSpan.Zero)).SetDisplay("Trade Start", "Entries are allowed from this time", "General")
self._trade_stop = self.Param("TradeStop", DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero)).SetDisplay("Trade Stop", "Entries are allowed until this time", "General")
self._ao_sma = None
self._jaw = None
self._teeth = None
self._lips = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.TimeFrame(TimeSpan.FromDays(1)))]
def _reset_state(self):
self._prev_ao = None
self._prev_ao_sma = None
self._daily_jaw = None
self._daily_teeth = None
self._daily_lips = None
self._entry_price = 0.0
self._skip_count = 0
def OnReseted(self):
super(momentum_alligator_4h_bitcoin_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(momentum_alligator_4h_bitcoin_strategy, self).OnStarted2(time)
self._reset_state()
ao = AwesomeOscillator()
self._ao_sma = SimpleMovingAverage()
self._ao_sma.Length = 5
self._jaw = SmoothedMovingAverage()
self._jaw.Length = 13
self._teeth = SmoothedMovingAverage()
self._teeth.Length = 8
self._lips = SmoothedMovingAverage()
self._lips.Length = 5
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ao, self._process_candle).Start()
self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromDays(1))).Bind(self._process_daily_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, ao)
self.DrawIndicator(oscillators, self._ao_sma)
def _process_daily_candle(self, candle):
if candle.State != CandleStates.Finished:
return
# The Alligator lines are smoothed averages of the median price.
median = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
jaw = float(process_float(self._jaw, median, candle.OpenTime, True))
teeth = float(process_float(self._teeth, median, candle.OpenTime, True))
lips = float(process_float(self._lips, median, candle.OpenTime, True))
if self._jaw.IsFormed:
self._daily_jaw = jaw
if self._teeth.IsFormed:
self._daily_teeth = teeth
if self._lips.IsFormed:
self._daily_lips = lips
def _process_candle(self, candle, ao_value):
if candle.State != CandleStates.Finished:
return
ao = float(ao_value)
ao_sma = float(process_float(self._ao_sma, ao, candle.OpenTime, True))
if not self._ao_sma.IsFormed:
self._prev_ao = ao
return
prev_ao = self._prev_ao
prev_ao_sma = self._prev_ao_sma
self._prev_ao = ao
self._prev_ao_sma = ao_sma
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
stop = self._entry_price * (1.0 - float(self._stop_loss_percent.Value))
if self._daily_jaw is not None and self._daily_jaw > stop:
stop = self._daily_jaw
if float(candle.LowPrice) <= stop:
exit_price = min(stop, float(candle.OpenPrice))
if exit_price > self._entry_price:
self._skip_count = SIGNALS_TO_SKIP
self.SellMarket(self.Position)
return
if prev_ao is None or prev_ao_sma is None:
return
if self._daily_jaw is None or self._daily_teeth is None or self._daily_lips is None:
return
cross_up = prev_ao <= prev_ao_sma and ao > ao_sma
if not cross_up:
return
close = float(candle.ClosePrice)
if close <= self._daily_jaw or close <= self._daily_teeth or close <= self._daily_lips:
return
open_time = candle.OpenTime
if open_time < self._trade_start.Value.UtcDateTime or open_time >= self._trade_stop.Value.UtcDateTime:
return
if self._skip_count > 0:
self._skip_count -= 1
return
self._entry_price = close
self.BuyMarket(self.Volume)
def CreateClone(self):
return momentum_alligator_4h_bitcoin_strategy()