Falcon Liquidity Grab戦略
この戦略は、シンプル移動平均でトレンドを定義しながら、主要な市場セッション中の流動性グラブをトレードします。価格が直近のスイングレベルを越えてトレンドとともに反転するときにエントリーします。各トレードはティック単位の固定ストップロスとテイクプロフィットを使用します。
詳細
- エントリー条件:
- ロング:
Low < lowest(swing period)&&Close > SMA&&session filter - ショート:
High > highest(swing period)&&Close < SMA&&session filter
- ロング:
- エグジット条件: 固定ストップロスとテイクプロフィット。
- タイプ: リバーサル
- インジケーター: SMA、Highest、Lowest
- 時間軸: 15分(デフォルト)
- ストップ:
StopLossPointsティック、TakeProfitMultiplier× ストップ距離
厳密な条件と初期値: SwingPeriod=20、TrendLength=50、StopLossPoints=50、TakeProfitMultiplier=2、UseSession=true、SessionStart=07:00、SessionEnd=21:00 (UTC)、CandleType=15分。確定したシグナル足の高安を、その足を除く過去N本の高安と比較します。Lowが過去最安値より低くCloseがSMAより高ければロング、Highが過去最高値より高くCloseがSMAより低ければショート。SMAにはシグナル足を含めます。新規エントリーはノーポジションでUTC時間窓内のみ(開始を含み終了を含まない、日跨ぎ対応)。Level1のbid/askによるネイティブ成行保護は足の間でも動き、ストップ幅はStopLossPoints × Security.PriceStep、利確幅はTakeProfitMultiplier × ストップ幅。反対シグナルでの終値決済はありません。
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Looks for wick sweeps of preceding swing extremes with an SMA trend filter.
/// Protective stop and target distances are measured in the security's minimum price step.
/// </summary>
public class FalconLiquidityGrabStrategy : Strategy
{
private readonly StrategyParam<int> _swingPeriod;
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitMultiplier;
private readonly StrategyParam<bool> _useSession;
private readonly StrategyParam<TimeSpan> _sessionStart;
private readonly StrategyParam<TimeSpan> _sessionEnd;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _previousHighs = new();
private readonly Queue<decimal> _previousLows = new();
private Order _pendingOrder;
public int SwingPeriod { get => _swingPeriod.Value; set => _swingPeriod.Value = value; }
public int TrendLength { get => _trendLength.Value; set => _trendLength.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public decimal TakeProfitMultiplier { get => _takeProfitMultiplier.Value; set => _takeProfitMultiplier.Value = value; }
public bool UseSession { get => _useSession.Value; set => _useSession.Value = value; }
public TimeSpan SessionStart { get => _sessionStart.Value; set => _sessionStart.Value = value; }
public TimeSpan SessionEnd { get => _sessionEnd.Value; set => _sessionEnd.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public FalconLiquidityGrabStrategy()
{
_swingPeriod = Param(nameof(SwingPeriod), 20).SetGreaterThanZero()
.SetDisplay("Swing Period", "Lookback of previous highs and lows; excludes the signal candle.", "Signal");
_trendLength = Param(nameof(TrendLength), 50).SetGreaterThanZero()
.SetDisplay("Trend Length", "SMA trend-filter period.", "Signal");
_stopLossPoints = Param(nameof(StopLossPoints), 50).SetGreaterThanZero()
.SetDisplay("Stop Loss Points", "Number of minimum price steps from the entry.", "Protection");
_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 2m).SetGreaterThanZero()
.SetDisplay("Take Profit Multiplier", "Target distance divided by the stop distance.", "Protection");
_useSession = Param(nameof(UseSession), true)
.SetDisplay("Use Session", "Limit entries to the UTC trading window.", "Session");
_sessionStart = Param(nameof(SessionStart), TimeSpan.FromHours(7))
.SetDisplay("Session Start", "UTC inclusive session start.", "Session");
_sessionEnd = Param(nameof(SessionEnd), TimeSpan.FromHours(21))
.SetDisplay("Session End", "UTC exclusive session end.", "Session");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe.", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
_previousHighs.Clear();
_previousLows.Clear();
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
throw new InvalidOperationException("The instrument must supply a positive price step for tick-based protection.");
var stopDistance = StopLossPoints * priceStep;
StartProtection(
new Unit(stopDistance * TakeProfitMultiplier, UnitTypes.Absolute),
new Unit(stopDistance, UnitTypes.Absolute),
useMarketOrders: true,
isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
OrderRegistering += order => _pendingOrder = order;
var trend = new SimpleMovingAverage { Length = TrendLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(trend, (candle, value) => ProcessCandle(candle, value, trend)).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, trend);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection reacts to both quote sides before the next finished signal candle.
}
private void ProcessCandle(ICandleMessage candle, decimal trendValue, SimpleMovingAverage trend)
{
if (candle.State != CandleStates.Finished)
return;
var pendingActive = _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
if (trend.IsFormed && _previousHighs.Count == SwingPeriod && _previousLows.Count == SwingPeriod
&& Position == 0m && !pendingActive)
{
var utcTime = candle.OpenTime.ToUniversalTime().TimeOfDay;
var inSession = !UseSession || (SessionStart <= SessionEnd
? utcTime >= SessionStart && utcTime < SessionEnd
: utcTime >= SessionStart || utcTime < SessionEnd);
if (inSession)
{
if (candle.LowPrice < _previousLows.Min() && candle.ClosePrice > trendValue)
BuyMarket(Volume);
else if (candle.HighPrice > _previousHighs.Max() && candle.ClosePrice < trendValue)
SellMarket(Volume);
}
}
_previousHighs.Enqueue(candle.HighPrice);
_previousLows.Enqueue(candle.LowPrice);
while (_previousHighs.Count > SwingPeriod) _previousHighs.Dequeue();
while (_previousLows.Count > SwingPeriod) _previousLows.Dequeue();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Subscription
class falcon_liquidity_grab_strategy(Strategy):
"""Swing-wick liquidity grabs with SMA confirmation and real native tick protection."""
def __init__(self):
super(falcon_liquidity_grab_strategy, self).__init__()
self._swing_period = self.Param("SwingPeriod", 20).SetGreaterThanZero() \
.SetDisplay("Swing Period", "Number of preceding swing highs/lows.", "Signal")
self._trend_length = self.Param("TrendLength", 50).SetGreaterThanZero() \
.SetDisplay("Trend Length", "SMA trend-filter period.", "Signal")
self._stop_loss_points = self.Param("StopLossPoints", 50).SetGreaterThanZero() \
.SetDisplay("Stop Loss Points", "Stop distance in price steps.", "Protection")
self._take_profit_multiplier = self.Param("TakeProfitMultiplier", 2.0).SetGreaterThanZero() \
.SetDisplay("Take Profit Multiplier", "Take profit as a multiple of stop distance.", "Protection")
self._use_session = self.Param("UseSession", True).SetDisplay(
"Use Session", "Restrict entries to configured UTC hours.", "Session")
self._session_start = self.Param("SessionStart", TimeSpan.FromHours(7)).SetDisplay(
"Session Start", "UTC inclusive session start.", "Session")
self._session_end = self.Param("SessionEnd", TimeSpan.FromHours(21)).SetDisplay(
"Session End", "UTC exclusive session end.", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candle timeframe.", "General")
self._previous_highs = []
self._previous_lows = []
self._pending_order = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value), (self.Security, DataType.Level1)]
def OnReseted(self):
super(falcon_liquidity_grab_strategy, self).OnReseted()
self._previous_highs = []
self._previous_lows = []
self._pending_order = None
def OnStarted2(self, time):
super(falcon_liquidity_grab_strategy, self).OnStarted2(time)
step = Decimal(self.Security.PriceStep) if self.Security.PriceStep is not None else Decimal.Zero
if step <= Decimal.Zero:
raise ValueError("Positive Security.PriceStep is required for tick-based protection")
stop = step * Decimal(self._stop_loss_points.Value)
target = stop * Decimal(self._take_profit_multiplier.Value)
self.StartProtection(Unit(target, UnitTypes.Absolute),
Unit(stop, UnitTypes.Absolute),
useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
self.OrderRegistering += self._on_order_registering
self._trend = SimpleMovingAverage()
self._trend.Length = int(self._trend_length.Value)
subscription = self.SubscribeCandles(self._candle_type.Value)
subscription.Bind(self._trend, self.on_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._trend)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection uses both sides of Level1 between signal candles.
pass
def _on_order_registering(self, order):
self._pending_order = order
def on_candle(self, candle, trend_value):
if candle.State != CandleStates.Finished:
return
swing = int(self._swing_period.Value)
active = self._pending_order is not None and self._pending_order.State not in (
OrderStates.Done, OrderStates.Failed)
if self._trend.IsFormed and len(self._previous_highs) == swing and not active and self.Position == 0:
utc_time = candle.OpenTime.ToUniversalTime().TimeOfDay
start = self._session_start.Value
end = self._session_end.Value
in_session = (not self._use_session.Value or
(start <= utc_time < end if start <= end else
(utc_time >= start or utc_time < end)))
if in_session:
close = float(candle.ClosePrice)
low = float(candle.LowPrice)
high = float(candle.HighPrice)
trend = float(trend_value)
if low < min(self._previous_lows) and close > trend:
self.BuyMarket(self.Volume)
elif high > max(self._previous_highs) and close < trend:
self.SellMarket(self.Volume)
self._previous_highs.append(float(candle.HighPrice))
self._previous_lows.append(float(candle.LowPrice))
if len(self._previous_highs) > swing:
self._previous_highs.pop(0)
if len(self._previous_lows) > swing:
self._previous_lows.pop(0)
def CreateClone(self):
return falcon_liquidity_grab_strategy()