Estratégia Falcon Liquidity Grab
Esta estratégia opera capturas de liquidez durante as principais sessões de mercado usando uma média móvel simples para definir a tendência. Entra quando o preço ultrapassa níveis de swing recentes e reverte com a tendência. Cada operação usa stop loss e take profit fixos medidos em ticks.
Detalhes
- Condições de entrada:
- Comprado:
Low < lowest(swing period)&&Close > SMA&&session filter - Vendido:
High > highest(swing period)&&Close < SMA&&session filter
- Comprado:
- Condições de saída: stop loss e take profit fixos.
- Tipo: Reversão
- Indicadores: SMA, Highest, Lowest
- Período: 15 minutos (padrão)
- Stops:
StopLossPointsticks,TakeProfitMultiplier× distância do stop
Regra exata e padrões: SwingPeriod=20, TrendLength=50, StopLossPoints=50, TakeProfitMultiplier=2, UseSession=true, SessionStart=07:00, SessionEnd=21:00 UTC, CandleType=15 minutos. A mínima/máxima do candle concluído é comparada com os extremos dos N candles anteriores, excluindo o atual. Compra se Low for estritamente menor que a mínima anterior e Close maior que SMA; vende se High exceder a máxima anterior e Close ficar abaixo de SMA. A SMA inclui o candle de sinal. Só entra sem posição na janela UTC (início incluso, fim exclusivo, inclusive janelas noturnas). Proteção nativa com ordens a mercado acompanha bid/ask Level1 entre candles: stop StopLossPoints × Security.PriceStep, alvo TakeProfitMultiplier × distância do stop. Não existe saída no fechamento por sinal oposto.
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()