Estrategia Footprint
Esta estrategia analiza el desequilibrio entre los volúmenes de compra y venta dentro de cada barra (footprint). Se abre una posición larga cuando el volumen de compra supera al de venta en un porcentaje configurable y la vela cierra por debajo de su apertura. Hay disponible un filtro de tendencia diaria opcional. El objetivo de ganancia y el stop loss se gestionan mediante protección integrada.
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>
/// Footprint strategy based on aggressive buy/sell volume imbalance.
/// </summary>
public class FootprintStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _imbalancePercent;
private readonly StrategyParam<bool> _useDailyTrendFilter;
private readonly StrategyParam<int> _dailyTrendPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private bool _dailyTrendBullish;
private bool _dailyTrendReady;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal ImbalancePercent { get => _imbalancePercent.Value; set => _imbalancePercent.Value = value; }
public bool UseDailyTrendFilter { get => _useDailyTrendFilter.Value; set => _useDailyTrendFilter.Value = value; }
public int DailyTrendPeriod { get => _dailyTrendPeriod.Value; set => _dailyTrendPeriod.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public FootprintStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Bars carrying footprint buy/sell volume.", "General");
_imbalancePercent = Param(nameof(ImbalancePercent), 25m)
.SetNotNegative()
.SetDisplay("Imbalance %", "Minimum excess of buy volume over sell volume.", "Signal");
_useDailyTrendFilter = Param(nameof(UseDailyTrendFilter), false)
.SetDisplay("Use Daily Trend", "Require daily close to be above its SMA.", "Filter");
_dailyTrendPeriod = Param(nameof(DailyTrendPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Daily Trend Period", "Daily SMA period.", "Filter");
_stopLossPercent = Param(nameof(StopLossPercent), 1m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Built-in protective stop distance.", "Protection");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Built-in protective target distance.", "Protection");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
if (UseDailyTrendFilter)
yield return (Security, TimeSpan.FromDays(1).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_dailyTrendBullish = false;
_dailyTrendReady = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (StopLossPercent > 0m || TakeProfitPercent > 0m)
{
StartProtection(
TakeProfitPercent > 0m ? new Unit(TakeProfitPercent, UnitTypes.Percent) : null,
StopLossPercent > 0m ? new Unit(StopLossPercent, UnitTypes.Percent) : null);
}
if (UseDailyTrendFilter)
{
var dailySma = new SimpleMovingAverage { Length = DailyTrendPeriod };
SubscribeCandles(TimeSpan.FromDays(1).TimeFrame())
.Bind(dailySma, (candle, sma) =>
{
if (candle.State != CandleStates.Finished || !dailySma.IsFormed)
return;
_dailyTrendBullish = candle.ClosePrice > sma;
_dailyTrendReady = true;
})
.Start();
}
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 || Position != 0)
return;
if (UseDailyTrendFilter && (!_dailyTrendReady || !_dailyTrendBullish))
return;
var buy = candle.BuyVolume ?? 0m;
var sell = candle.SellVolume ?? 0m;
if (buy <= 0m && sell <= 0m)
return;
var requiredBuy = sell * (1m + ImbalancePercent / 100m);
// The documented footprint setup is contrarian: aggressive buyers dominate
// while the candle itself still closes red.
if (buy > requiredBuy && candle.ClosePrice < candle.OpenPrice)
BuyMarket();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class footprint_strategy(Strategy):
def __init__(self):
super(footprint_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._imbalance_percent = self.Param("ImbalancePercent", 25.0).SetNotNegative()
self._use_daily_trend_filter = self.Param("UseDailyTrendFilter", False)
self._daily_trend_period = self.Param("DailyTrendPeriod", 50).SetGreaterThanZero()
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative()
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0).SetNotNegative()
self._daily_trend_bullish = False
self._daily_trend_ready = False
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
result = [(self.Security, self.CandleType)]
if bool(self._use_daily_trend_filter.Value):
result.append((self.Security, DataType.TimeFrame(TimeSpan.FromDays(1))))
return result
def OnReseted(self):
super(footprint_strategy, self).OnReseted()
self._daily_trend_bullish = False
self._daily_trend_ready = False
def OnStarted2(self, time):
super(footprint_strategy, self).OnStarted2(time)
sl = float(self._stop_loss_percent.Value)
tp = float(self._take_profit_percent.Value)
if sl > 0 or tp > 0:
self.StartProtection(
Unit(tp, UnitTypes.Percent) if tp > 0 else None,
Unit(sl, UnitTypes.Percent) if sl > 0 else None)
if bool(self._use_daily_trend_filter.Value):
sma = SimpleMovingAverage()
sma.Length = int(self._daily_trend_period.Value)
def on_daily(candle, sma_value):
if candle.State != CandleStates.Finished or not sma.IsFormed:
return
self._daily_trend_bullish = float(candle.ClosePrice) > float(sma_value)
self._daily_trend_ready = True
self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromDays(1))).Bind(sma, on_daily).Start()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished or self.Position != 0:
return
if bool(self._use_daily_trend_filter.Value) and (not self._daily_trend_ready or not self._daily_trend_bullish):
return
buy = float(candle.BuyVolume) if candle.BuyVolume is not None else 0.0
sell = float(candle.SellVolume) if candle.SellVolume is not None else 0.0
if buy <= 0 and sell <= 0:
return
required_buy = sell * (1.0 + float(self._imbalance_percent.Value) / 100.0)
if buy > required_buy and float(candle.ClosePrice) < float(candle.OpenPrice):
self.BuyMarket()
def CreateClone(self):
return footprint_strategy()