PowerHouse SwiftEdge AI v2.10 策略
基于枢轴的突破策略并结合动量过滤和简单风险控制。 当价格突破最近枢轴高点且动量充足并满足最小信号间隔时开多。 当价格跌破枢轴低点且动量为负时开空。 仓位按照设定的止盈和止损点数退出。
计算与执行
这是确定性示例;历史名称不代表训练好的 AI 模型。C# 和 Python 共同使用下列对上述简介的明确解释。
使用已完成的基础K线。枢轴高点必须严格高于两侧各 PivotPeriod 根相邻K线的最高价,枢轴低点必须严格低于它们的最低价;同价不形成枢轴。右侧K线全部完成后才确认,不回填历史信号。分别保存最近确认的高点和低点。
做多要求 前一 Close <= 枢轴高点 且 当前 Close > 枢轴高点;做空要求反向穿越枢轴低点。动量为 当前 Close - MomentumPeriod 根前的 Close,做多必须严格大于 MomentumThreshold,做空必须严格小于其负值。双向信号都要求距上次接受的入场至少 MinSignalBars 根已完成K线;零禁用间隔。被拒绝的信号不延迟执行,没有 EMA 过滤或六小时冷却期。
空仓以 Volume 入场,反向仓位以 Volume + abs(Position) 完全反转。有未完成订单时禁止新订单。止损和止盈是固定报价价格单位:一点表示价格差 1,不是 tick、PriceStep 倍数或百分比,需按品种调整。基准为实际成交价;同方向部分加仓用成交量加权均价,减仓保留基准,反转重新建立基准。
本地保护在多仓当前 bid 或空仓当前 ask 达到止损或止盈时,用市价单平掉全部仓位。已完成K线收盘价也检查保护;不会把最高/最低价触及当作有顺序的盘中事件。保护不受入场间隔限制,同一K线上优先于新入场。价格跳空时成交价可能不同于触发价。
默认:PivotPeriod=2、MomentumPeriod=10、MomentumThreshold=0、MinSignalBars=5、StopLossPoints=50、TakeProfitPoints=100、CandleType=TimeSpan.FromMinutes(5).TimeFrame()。周期和两个退出距离必须为正;动量阈值和间隔不能为负。
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()