Estrategia PowerHouse SwiftEdge AI v2.10
Estrategia de ruptura basada en pivotes con filtro de momentum y gestión de riesgo simple. La entrada larga ocurre cuando el precio rompe el máximo de pivote reciente con momentum suficiente y distancia mínima desde la señal anterior. La entrada corta se activa en una ruptura del mínimo de pivote con momentum negativo. Las posiciones usan take-profit y stop-loss especificados en puntos.
Cálculo y ejecución
Es un ejemplo determinista; el título histórico no implica un modelo de IA entrenado. C# y Python comparten esta interpretación explícita de la descripción anterior.
Use velas base terminadas. Un máximo pivote supera estrictamente los máximos de PivotPeriod barras vecinas a cada lado; un mínimo es estrictamente inferior a sus mínimos. La igualdad excluye el pivote. Se confirma solo tras terminar las barras de la derecha, sin señales retroactivas. Conserve por separado el último máximo y mínimo confirmados.
Largo requiere Close anterior <= máximo pivote y Close actual > máximo pivote; corto exige el cruce inverso del mínimo pivote. Momentum es Close actual - Close de hace MomentumPeriod barras: estrictamente mayor que MomentumThreshold para largo o menor que su negativo para corto. Ambos lados requieren al menos MinSignalBars barras terminadas desde la última entrada aceptada; cero desactiva el intervalo. No se aplazan señales rechazadas. No hay filtro EMA ni espera de seis horas.
Entre con Volume desde cero o invierta completamente con Volume + abs(Position). Una orden pendiente bloquea nuevas órdenes. Las distancias son unidades absolutas del precio cotizado: un punto equivale a una diferencia de 1, no a un tick, múltiplo de PriceStep o porcentaje. Ajústelas al instrumento. La base es la ejecución real, ponderada por volumen al aumentar parcialmente el mismo lado; reducir conserva la base e invertir la reinicia.
La protección local cierra toda la posición a mercado cuando el bid de largos o ask de cortos alcanza stop o take. Los cierres de velas también verifican protección; tocar el máximo/mínimo no simula eventos intrabar ordenados. La protección ignora el intervalo de entrada y tiene prioridad sobre una nueva entrada en la misma vela. Ante saltos, la ejecución puede diferir del disparador.
Valores iniciales: PivotPeriod=2, MomentumPeriod=10, MomentumThreshold=0, MinSignalBars=5, StopLossPoints=50, TakeProfitPoints=100, CandleType=TimeSpan.FromMinutes(5).TimeFrame(). Períodos y distancias de salida positivos; umbral e intervalo no negativos.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Trades confirmed-pivot breakouts with signed close momentum and finished-bar spacing,
/// protected by fixed price-point distances from actual fill prices.
/// </summary>
public class PowerHouseSwiftEdgeAiV210Strategy : Strategy
{
private readonly StrategyParam<int> _pivotPeriod;
private readonly StrategyParam<int> _momentumPeriod;
private readonly StrategyParam<decimal> _momentumThreshold;
private readonly StrategyParam<int> _minSignalBars;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal high, decimal low, decimal close)> _bars = [];
private decimal? _pivotHigh;
private decimal? _pivotLow;
private long _barCount;
private long? _lastSignal;
private Order _pendingOrder;
private decimal _filledPosition;
private decimal _entryPrice;
/// <summary>Initializes two-bar pivots, ten-bar momentum, five-bar spacing and 50/100 price-point exits.</summary>
public PowerHouseSwiftEdgeAiV210Strategy()
{
_pivotPeriod = Param(nameof(PivotPeriod), 2).SetGreaterThanZero()
.SetDisplay("Pivot Period", "Strict neighboring bars on each side of a confirmed pivot", "Signals");
_momentumPeriod = Param(nameof(MomentumPeriod), 10).SetGreaterThanZero()
.SetDisplay("Momentum Period", "Finished-bar close lookback", "Signals");
_momentumThreshold = Param(nameof(MomentumThreshold), 0m).SetRange(0m, decimal.MaxValue)
.SetDisplay("Momentum Threshold", "Minimum signed close change in absolute price units", "Signals");
_minSignalBars = Param(nameof(MinSignalBars), 5).SetRange(0, int.MaxValue)
.SetDisplay("Minimum Signal Bars", "Finished-bar interval between accepted entries; zero disables spacing", "Signals");
_stopLossPoints = Param(nameof(StopLossPoints), 50m).SetGreaterThanZero()
.SetDisplay("Stop Loss Points", "Fixed absolute price distance from the actual fill basis", "Protection");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 100m).SetGreaterThanZero()
.SetDisplay("Take Profit Points", "Fixed absolute price distance from the actual fill basis", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Finished candles supplying pivots, momentum and spacing", "General");
OrderRegistering += order => _pendingOrder = order;
OwnTradeReceived += ObserveFill;
}
/// <summary>Strict neighboring bars on each side of a pivot.</summary>
public int PivotPeriod { get => _pivotPeriod.Value; set => _pivotPeriod.Value = value; }
/// <summary>Lookback for the signed close-price difference.</summary>
public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
/// <summary>Nonnegative absolute-price threshold, exceeded strictly in the entry direction.</summary>
public decimal MomentumThreshold { get => _momentumThreshold.Value; set => _momentumThreshold.Value = value; }
/// <summary>Minimum finished-bar interval between accepted entry orders; zero disables it.</summary>
public int MinSignalBars { get => _minSignalBars.Value; set => _minSignalBars.Value = value; }
/// <summary>Stop distance in absolute quote-price points, not ticks or percent.</summary>
public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
/// <summary>Take-profit distance in absolute quote-price points, not ticks or percent.</summary>
public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
/// <summary>Base finished-candle type; five minutes by default.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
private void ResetState()
{
_bars.Clear();
_pivotHigh = _pivotLow = null;
_barCount = 0;
_lastSignal = null;
_pendingOrder = null;
_filledPosition = _entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (PivotPeriod <= 0 || MomentumPeriod <= 0 || MomentumThreshold < 0m
|| MinSignalBars < 0 || StopLossPoints <= 0m || TakeProfitPoints <= 0m)
throw new ArgumentException("Pivot/momentum periods and point distances must be positive; thresholds and spacing cannot be negative.");
ResetState();
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveQuote).Start();
}
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private bool IsPending => _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void Submit(Sides side, decimal volume, string comment)
=> RegisterOrder(new Order { Security = Security, Portfolio = Portfolio, Type = OrderTypes.Market, Side = side, Volume = volume, Comment = comment });
private void ObserveFill(Subscription subscription, MyTrade trade)
{
var previous = _filledPosition;
var signed = trade.Order.Side == Sides.Buy ? trade.Trade.Volume : -trade.Trade.Volume;
_filledPosition += signed;
if (_filledPosition == 0m)
_entryPrice = 0m;
else if (previous == 0m || Math.Sign(_filledPosition) != Math.Sign(previous))
_entryPrice = trade.Trade.Price;
else if (Math.Sign(signed) == Math.Sign(previous))
_entryPrice = (_entryPrice * Math.Abs(previous) + trade.Trade.Price * Math.Abs(signed)) / Math.Abs(_filledPosition);
}
private bool TryRiskExit(decimal price)
{
if (Position == 0m || _entryPrice <= 0m || IsPending || !IsFormedAndOnlineAndAllowTrading())
return false;
var profit = (price - _entryPrice) * Math.Sign(Position);
var comment = profit <= -StopLossPoints ? "SwiftEdge stop loss" : profit >= TakeProfitPoints ? "SwiftEdge take profit" : null;
if (comment is null)
return false;
Submit(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), comment);
return true;
}
private void ObserveQuote(Level1ChangeMessage quote)
{
var field = Position > 0m ? Level1Fields.BestBidPrice : Level1Fields.BestAskPrice;
if (quote.TryGetDecimal(field) is decimal price && price > 0m)
TryRiskExit(price);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_barCount++;
var width = checked(2 * PivotPeriod + 1);
_bars.Add((candle.HighPrice, candle.LowPrice, candle.ClosePrice));
if (_bars.Count > Math.Max(width, checked(MomentumPeriod + 1)))
_bars.RemoveAt(0);
if (_bars.Count >= width)
{
var start = _bars.Count - width;
var center = start + PivotPeriod;
var high = true;
var low = true;
for (var i = start; i < _bars.Count; i++)
{
if (i == center)
continue;
high &= _bars[center].high > _bars[i].high;
low &= _bars[center].low < _bars[i].low;
}
if (high)
_pivotHigh = _bars[center].high;
if (low)
_pivotLow = _bars[center].low;
}
if (TryRiskExit(candle.ClosePrice) || IsPending || !IsFormedAndOnlineAndAllowTrading()
|| _bars.Count <= MomentumPeriod || _bars.Count < 2
|| _lastSignal is long last && _barCount - last < MinSignalBars)
return;
var previous = _bars[^2].close;
var momentum = candle.ClosePrice - _bars[_bars.Count - MomentumPeriod - 1].close;
Sides? side = null;
if (_pivotHigh is decimal pivotHigh && previous <= pivotHigh && candle.ClosePrice > pivotHigh
&& momentum > MomentumThreshold && Position <= 0m)
side = Sides.Buy;
else if (_pivotLow is decimal pivotLow && previous >= pivotLow && candle.ClosePrice < pivotLow
&& momentum < -MomentumThreshold && Position >= 0m)
side = Sides.Sell;
if (side is Sides direction)
{
_lastSignal = _barCount;
Submit(direction, Volume + Math.Abs(Position), "SwiftEdge pivot breakout");
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import ArgumentException, Decimal, Int32, Math, TimeSpan
from StockSharp.Messages import CandleStates, DataType, ITickTradeMessage, Level1Fields, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Strategies import Strategy
class power_house_swift_edge_ai_v2_10_strategy(Strategy):
def __init__(self):
super(power_house_swift_edge_ai_v2_10_strategy, self).__init__()
self._pivot_period = self.Param("PivotPeriod", 2).SetGreaterThanZero() \
.SetDisplay("Pivot Period", "Strict neighboring bars on each side of a confirmed pivot", "Signals")
self._momentum_period = self.Param("MomentumPeriod", 10).SetGreaterThanZero() \
.SetDisplay("Momentum Period", "Finished-bar close lookback", "Signals")
self._momentum_threshold = self.Param("MomentumThreshold", Decimal.Zero).SetRange(Decimal.Zero, Decimal.MaxValue) \
.SetDisplay("Momentum Threshold", "Minimum signed close change in absolute price units", "Signals")
self._min_signal_bars = self.Param("MinSignalBars", 5).SetRange(0, Int32.MaxValue) \
.SetDisplay("Minimum Signal Bars", "Finished-bar interval between accepted entries; zero disables spacing", "Signals")
self._stop_loss_points = self.Param("StopLossPoints", Decimal(50)).SetGreaterThanZero() \
.SetDisplay("Stop Loss Points", "Fixed absolute price distance from the actual fill basis", "Protection")
self._take_profit_points = self.Param("TakeProfitPoints", Decimal(100)).SetGreaterThanZero() \
.SetDisplay("Take Profit Points", "Fixed absolute price distance from the actual fill basis", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Finished candles supplying pivots, momentum and spacing", "General")
tick_type = clr.GetClrType(ITickTradeMessage)
self._tick_price = tick_type.GetProperty("Price")
self._tick_volume = tick_type.GetProperty("Volume")
self._reset_state()
self.OrderRegistering += self._observe_order
self.OwnTradeReceived += self._observe_fill
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def _reset_state(self):
self._bars = []
self._pivot_high = self._pivot_low = None
self._bar_count = 0
self._last_signal = None
self._pending_order = None
self._filled_position = self._entry_price = Decimal.Zero
def OnReseted(self):
super(power_house_swift_edge_ai_v2_10_strategy, self).OnReseted()
self._reset_state()
def _observe_order(self, order):
self._pending_order = order
def OnStarted2(self, time):
super(power_house_swift_edge_ai_v2_10_strategy, self).OnStarted2(time)
if self._pivot_period.Value <= 0 or self._momentum_period.Value <= 0 or self._momentum_threshold.Value < 0 \
or self._min_signal_bars.Value < 0 or self._stop_loss_points.Value <= 0 or self._take_profit_points.Value <= 0:
raise ArgumentException("Pivot/momentum periods and point distances must be positive; thresholds and spacing cannot be negative.")
self._reset_state()
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_quote).Start()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _is_pending(self):
order = self._pending_order
return order is not None and order.State != OrderStates.Done and order.State != OrderStates.Failed
def _submit(self, side, volume, comment):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = volume
order.Comment = comment
self.RegisterOrder(order)
def _observe_fill(self, subscription, trade):
previous = self._filled_position
price = self._tick_price.GetValue(trade.Trade)
volume = self._tick_volume.GetValue(trade.Trade)
signed = volume if trade.Order.Side == Sides.Buy else -volume
self._filled_position += signed
if self._filled_position == 0:
self._entry_price = Decimal.Zero
elif previous == 0 or Math.Sign(self._filled_position) != Math.Sign(previous):
self._entry_price = price
elif Math.Sign(signed) == Math.Sign(previous):
self._entry_price = (self._entry_price * abs(previous) + price * abs(signed)) / abs(self._filled_position)
def _try_risk_exit(self, price):
if self.Position == 0 or self._entry_price <= 0 or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading():
return False
profit = (price - self._entry_price) * Math.Sign(self.Position)
comment = "SwiftEdge stop loss" if profit <= -self._stop_loss_points.Value \
else "SwiftEdge take profit" if profit >= self._take_profit_points.Value else None
if comment is None:
return False
self._submit(Sides.Sell if self.Position > 0 else Sides.Buy, abs(self.Position), comment)
return True
def _observe_quote(self, quote):
field = Level1Fields.BestBidPrice if self.Position > 0 else Level1Fields.BestAskPrice
if quote.Changes.ContainsKey(field) and quote.Changes[field] > 0:
self._try_risk_exit(quote.Changes[field])
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
self._bar_count += 1
period = self._pivot_period.Value
momentum_period = self._momentum_period.Value
width = 2 * period + 1
self._bars.append((candle.HighPrice, candle.LowPrice, candle.ClosePrice))
if len(self._bars) > max(width, momentum_period + 1):
self._bars.pop(0)
if len(self._bars) >= width:
start = len(self._bars) - width
center = start + period
neighbors = [self._bars[index] for index in range(start, len(self._bars)) if index != center]
if all(self._bars[center][0] > bar[0] for bar in neighbors):
self._pivot_high = self._bars[center][0]
if all(self._bars[center][1] < bar[1] for bar in neighbors):
self._pivot_low = self._bars[center][1]
if self._try_risk_exit(candle.ClosePrice) or self._is_pending() or not self.IsFormedAndOnlineAndAllowTrading() \
or len(self._bars) <= momentum_period or len(self._bars) < 2 \
or self._last_signal is not None and self._bar_count - self._last_signal < self._min_signal_bars.Value:
return
previous = self._bars[-2][2]
momentum = candle.ClosePrice - self._bars[-momentum_period - 1][2]
side = None
if self._pivot_high is not None and previous <= self._pivot_high and candle.ClosePrice > self._pivot_high \
and momentum > self._momentum_threshold.Value and self.Position <= 0:
side = Sides.Buy
elif self._pivot_low is not None and previous >= self._pivot_low and candle.ClosePrice < self._pivot_low \
and momentum < -self._momentum_threshold.Value and self.Position >= 0:
side = Sides.Sell
if side is not None:
self._last_signal = self._bar_count
self._submit(side, self.Volume + abs(self.Position), "SwiftEdge pivot breakout")
def CreateClone(self):
return power_house_swift_edge_ai_v2_10_strategy()