Falcon Liquidity Grab 策略
该策略在主要市场时段内交易流动性抓取,使用简单移动平均线确定趋势。当价格突破近期摆动高低点后沿趋势反转时入场。每笔交易都使用以 tick 为单位的固定止损和止盈。
细节
- 入场条件:
- 做多:
Low < 周期最低&&Close > SMA&&会话过滤 - 做空:
High > 周期最高&&Close < SMA&&会话过滤
- 做多:
- 离场条件:固定止损和止盈。
- 类型:反转
- 指标:SMA、Highest、Lowest
- 时间框架:15 分钟(默认)
- 止损:
StopLossPointstick,TakeProfitMultiplier× 止损距离
精确规则与默认值:SwingPeriod=20、TrendLength=50、StopLossPoints=50、TakeProfitMultiplier=2、UseSession=true、SessionStart=07:00、SessionEnd=21:00(UTC)、CandleType=15分钟。信号K线完成后,将其高低点与此前N根已完成K线的极值比较,不包括当前信号K线。Low严格低于此前最低价且Close高于SMA时做多;High严格高于此前最高价且Close低于SMA时做空。SMA包括当前信号K线。只有空仓时且在UTC时间窗口内开仓(包含起点、不包含终点,支持跨午夜)。基于Level1买卖报价的原生市价保护可在K线之间触发:止损距离=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()