Precision Trading: Golden Edge
EMA 交叉策略,由 Hull 方向、RSI 动量和 ATR/高低价区间确认。历史描述面向黄金剥头皮;代码使用所配置的证券,测试不证明黄金交易表现。
信号与执行
只使用已完成的蜡烛,默认五分钟。七个指标必须全部形成;第一根完全预热的蜡烛仅记录前一 EMA/HMA,不入场。
- 多头:前一快 EMA ≤ 前一慢 EMA,当前快 EMA > 当前慢 EMA,当前 HMA > 前一 HMA,RSI >
RsiLongThreshold(55)。 - 空头:前一快 EMA ≥ 前一慢 EMA,当前快 EMA < 当前慢 EMA,当前 HMA < 前一 HMA,RSI <
RsiShortThreshold(45)。 - 两个方向都要求 close 和 ATR 为正,
100 * ATR / close >= MinAtrPercent(0.05),以及Highest(high) - Lowest(low) >= ATR * RangeAtrMultiplier(3)。极值窗口包含当前蜡烛。
满足条件时,从空仓买卖 Volume,或用 Volume + abs(Position) 完全反转相反持仓。同方向信号保持持仓。未完成订单阻止重复提交。每根已预热蜡烛都更新前一 EMA/HMA,被拒绝的交叉不会延迟入场。没有六小时冷却,也没有额外止损或止盈;相反方向的合格交叉反转持仓。
指标与默认参数
| 参数 | 默认值 | 含义 |
|---|---|---|
FastLength / SlowLength |
14 / 40 | 原生收盘价 EMA;快周期 < 慢周期 |
HmaLength |
55 | WMA(floor(sqrt(L)), 2 * WMA(floor(L/2), close) - WMA(L, close));L ≥ 2 |
RsiLength |
14 | 原生 RSI,Wilder 平滑收盘涨跌 |
RsiLongThreshold / RsiShortThreshold |
55 / 45 | 严格阈值;0 <= short < long <= 100 |
AtrLength |
14 | 原生 Wilder ATR,计入相对前一 close 的跳空 |
RangeLength |
20 | 包含当前蜡烛的最高 high 和最低 low |
MinAtrPercent |
0.05 | ATR 占当前 close 的百分比下限,包含边界,非负 |
RangeAtrMultiplier |
3 | 高低区间的 ATR 倍数下限,包含边界,正数 |
CandleType |
5 分钟 | 全部指标的已完成蜡烛 |
WMA 权重从最旧的 1 到最新的窗口长度。EMA 以原生算术均值初始化,之后使用 2 / (length + 1)。ATR 从增长窗口均值开始,随后使用 Wilder 平滑。周期为正;HMA 还需完成最后的平方根周期 WMA。
C# 与 Python 测试在现有 BTC/TON 历史上独立计算 EMA、HMA、RSI、ATR 和滚动极值,验证每个订单、完全反转、被拒绝的确认及小于六小时的信号。测试验证行为,不验证盈利或实盘适用性。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Confirms EMA crosses with Hull slope, RSI and ATR/price-range volatility,
/// then opens or fully reverses on finished candles.
/// </summary>
public class PrecisionTradingGoldenEdgeStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _hmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiLongThreshold;
private readonly StrategyParam<decimal> _rsiShortThreshold;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _rangeLength;
private readonly StrategyParam<decimal> _minAtrPercent;
private readonly StrategyParam<decimal> _rangeAtrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private bool _isInitialized;
private decimal _previousFast;
private decimal _previousSlow;
private decimal _previousHma;
private Order _pendingOrder;
/// <summary>Initializes EMA14/40, HMA55, RSI14 at 55/45, ATR14 and twenty-bar range confirmation.</summary>
public PrecisionTradingGoldenEdgeStrategy()
{
_fastLength = Param(nameof(FastLength), 14).SetGreaterThanZero()
.SetDisplay("Fast Length", "Fast close EMA period", "Trend");
_slowLength = Param(nameof(SlowLength), 40).SetGreaterThanZero()
.SetDisplay("Slow Length", "Slow close EMA period", "Trend");
_hmaLength = Param(nameof(HmaLength), 55).SetRange(2, int.MaxValue)
.SetDisplay("HMA Length", "Hull close average with integer half and square-root lengths", "Trend");
_rsiLength = Param(nameof(RsiLength), 14).SetGreaterThanZero()
.SetDisplay("RSI Length", "Close momentum period", "Momentum");
_rsiLongThreshold = Param(nameof(RsiLongThreshold), 55m).SetRange(0m, 100m)
.SetDisplay("Long RSI Threshold", "Strict lower bound for long confirmation", "Momentum");
_rsiShortThreshold = Param(nameof(RsiShortThreshold), 45m).SetRange(0m, 100m)
.SetDisplay("Short RSI Threshold", "Strict upper bound for short confirmation", "Momentum");
_atrLength = Param(nameof(AtrLength), 14).SetGreaterThanZero()
.SetDisplay("ATR Length", "Wilder true-range period", "Volatility");
_rangeLength = Param(nameof(RangeLength), 20).SetGreaterThanZero()
.SetDisplay("Range Length", "Current-inclusive high/low window", "Volatility");
_minAtrPercent = Param(nameof(MinAtrPercent), 0.05m).SetRange(0m, decimal.MaxValue)
.SetDisplay("Minimum ATR Percent", "Inclusive minimum ATR as percent of current close", "Volatility");
_rangeAtrMultiplier = Param(nameof(RangeAtrMultiplier), 3m).SetGreaterThanZero()
.SetDisplay("Range ATR Multiplier", "Inclusive minimum highest-lowest span as an ATR multiple", "Volatility");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Finished candles supplying all confirmations", "General");
OrderRegistering += order => _pendingOrder = order;
}
/// <summary>Fast close EMA length, strictly less than SlowLength.</summary>
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
/// <summary>Slow close EMA length.</summary>
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
/// <summary>Hull close average length, at least two.</summary>
public int HmaLength { get => _hmaLength.Value; set => _hmaLength.Value = value; }
/// <summary>Native close RSI length.</summary>
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
/// <summary>Strict long RSI threshold.</summary>
public decimal RsiLongThreshold { get => _rsiLongThreshold.Value; set => _rsiLongThreshold.Value = value; }
/// <summary>Strict short RSI threshold, below RsiLongThreshold.</summary>
public decimal RsiShortThreshold { get => _rsiShortThreshold.Value; set => _rsiShortThreshold.Value = value; }
/// <summary>Wilder true-range length.</summary>
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <summary>Current-inclusive highest-high/lowest-low window length.</summary>
public int RangeLength { get => _rangeLength.Value; set => _rangeLength.Value = value; }
/// <summary>Inclusive minimum ATR/close percentage.</summary>
public decimal MinAtrPercent { get => _minAtrPercent.Value; set => _minAtrPercent.Value = value; }
/// <summary>Inclusive minimum high-low window span as an ATR multiple.</summary>
public decimal RangeAtrMultiplier { get => _rangeAtrMultiplier.Value; set => _rangeAtrMultiplier.Value = value; }
/// <summary>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)];
private void ResetState()
{
_isInitialized = false;
_previousFast = _previousSlow = _previousHma = 0m;
_pendingOrder = null;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (FastLength <= 0 || SlowLength <= FastLength || HmaLength < 2 || RsiLength <= 0 || AtrLength <= 0
|| RangeLength <= 0 || MinAtrPercent < 0m || RangeAtrMultiplier <= 0m
|| RsiShortThreshold < 0m || RsiLongThreshold > 100m || RsiShortThreshold >= RsiLongThreshold)
throw new ArgumentException("EMA lengths must be ordered, HMA length at least two, other periods/multiplier positive, and RSI thresholds ordered within [0, 100].");
ResetState();
var fast = new ExponentialMovingAverage { Length = FastLength };
var slow = new ExponentialMovingAverage { Length = SlowLength };
var hma = new HullMovingAverage { Length = HmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var highest = new Highest { Length = RangeLength };
var lowest = new Lowest { Length = RangeLength };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx([fast, slow, hma, rsi, atr, highest, lowest], ProcessCandle, allowEmpty: true).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawIndicator(area, hma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished || values.Any(value => value.IsEmpty || !value.IsFormed))
return;
var fast = values[0].ToDecimal();
var slow = values[1].ToDecimal();
var hma = values[2].ToDecimal();
var rsi = values[3].ToDecimal();
var atr = values[4].ToDecimal();
var span = values[5].ToDecimal() - values[6].ToDecimal();
var volatilityPass = candle.ClosePrice > 0m && atr > 0m && 100m * atr / candle.ClosePrice >= MinAtrPercent
&& span >= atr * RangeAtrMultiplier;
if (_isInitialized && volatilityPass && IsFormedAndOnlineAndAllowTrading()
&& (_pendingOrder is null || _pendingOrder.State is OrderStates.Done or OrderStates.Failed))
{
Sides? side = null;
if (_previousFast <= _previousSlow && fast > slow && hma > _previousHma && rsi > RsiLongThreshold && Position <= 0m)
side = Sides.Buy;
else if (_previousFast >= _previousSlow && fast < slow && hma < _previousHma && rsi < RsiShortThreshold && Position >= 0m)
side = Sides.Sell;
if (side is Sides direction)
RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = direction,
Volume = Volume + Math.Abs(Position),
Comment = "Golden Edge confirmed cross",
});
}
_isInitialized = true;
_previousFast = fast;
_previousSlow = slow;
_previousHma = hma;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Array, ArgumentException, Decimal, Int32, TimeSpan
from StockSharp.Messages import CandleStates, DataType, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage, Highest, HullMovingAverage, IIndicator, Lowest, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class precision_trading_golden_edge_strategy(Strategy):
def __init__(self):
super(precision_trading_golden_edge_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 14).SetGreaterThanZero() \
.SetDisplay("Fast Length", "Fast close EMA period", "Trend")
self._slow_length = self.Param("SlowLength", 40).SetGreaterThanZero() \
.SetDisplay("Slow Length", "Slow close EMA period", "Trend")
self._hma_length = self.Param("HmaLength", 55).SetRange(2, Int32.MaxValue) \
.SetDisplay("HMA Length", "Hull close average with integer half and square-root lengths", "Trend")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero() \
.SetDisplay("RSI Length", "Close momentum period", "Momentum")
self._rsi_long_threshold = self.Param("RsiLongThreshold", Decimal(55)).SetRange(Decimal.Zero, Decimal(100)) \
.SetDisplay("Long RSI Threshold", "Strict lower bound for long confirmation", "Momentum")
self._rsi_short_threshold = self.Param("RsiShortThreshold", Decimal(45)).SetRange(Decimal.Zero, Decimal(100)) \
.SetDisplay("Short RSI Threshold", "Strict upper bound for short confirmation", "Momentum")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero() \
.SetDisplay("ATR Length", "Wilder true-range period", "Volatility")
self._range_length = self.Param("RangeLength", 20).SetGreaterThanZero() \
.SetDisplay("Range Length", "Current-inclusive high/low window", "Volatility")
self._min_atr_percent = self.Param("MinAtrPercent", Decimal.Parse("0.05")).SetRange(Decimal.Zero, Decimal.MaxValue) \
.SetDisplay("Minimum ATR Percent", "Inclusive minimum ATR as percent of current close", "Volatility")
self._range_atr_multiplier = self.Param("RangeAtrMultiplier", Decimal(3)).SetGreaterThanZero() \
.SetDisplay("Range ATR Multiplier", "Inclusive minimum highest-lowest span as an ATR multiple", "Volatility")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Finished candles supplying all confirmations", "General")
self._reset_state()
self.OrderRegistering += self._observe_order
@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._is_initialized = False
self._previous_fast = self._previous_slow = self._previous_hma = Decimal.Zero
self._pending_order = None
def OnReseted(self):
super(precision_trading_golden_edge_strategy, self).OnReseted()
self._reset_state()
def _observe_order(self, order):
self._pending_order = order
def OnStarted2(self, time):
super(precision_trading_golden_edge_strategy, self).OnStarted2(time)
if self._fast_length.Value <= 0 or self._slow_length.Value <= self._fast_length.Value \
or self._hma_length.Value < 2 or self._rsi_length.Value <= 0 or self._atr_length.Value <= 0 \
or self._range_length.Value <= 0 or self._min_atr_percent.Value < 0 or self._range_atr_multiplier.Value <= 0 \
or self._rsi_short_threshold.Value < 0 or self._rsi_long_threshold.Value > 100 \
or self._rsi_short_threshold.Value >= self._rsi_long_threshold.Value:
raise ArgumentException("EMA lengths must be ordered, HMA length at least two, other periods/multiplier positive, and RSI thresholds ordered within [0, 100].")
self._reset_state()
fast = ExponentialMovingAverage()
fast.Length = self._fast_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_length.Value
hma = HullMovingAverage()
hma.Length = self._hma_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
highest = Highest()
highest.Length = self._range_length.Value
lowest = Lowest()
lowest.Length = self._range_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(Array[IIndicator]([fast, slow, hma, rsi, atr, highest, lowest]), self.OnProcess, True).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawIndicator(area, hma)
self.DrawOwnTrades(area)
def OnProcess(self, candle, values):
if candle.State != CandleStates.Finished or any(value.IsEmpty or not value.IsFormed for value in values):
return
fast, slow, hma, rsi, atr, highest, lowest = [value.GetValue[Decimal](None) for value in values]
volatility_pass = candle.ClosePrice > 0 and atr > 0 \
and Decimal(100) * atr / candle.ClosePrice >= self._min_atr_percent.Value \
and highest - lowest >= atr * self._range_atr_multiplier.Value
pending = self._pending_order
if self._is_initialized and volatility_pass and self.IsFormedAndOnlineAndAllowTrading() \
and (pending is None or pending.State == OrderStates.Done or pending.State == OrderStates.Failed):
side = None
if self._previous_fast <= self._previous_slow and fast > slow and hma > self._previous_hma \
and rsi > self._rsi_long_threshold.Value and self.Position <= 0:
side = Sides.Buy
elif self._previous_fast >= self._previous_slow and fast < slow and hma < self._previous_hma \
and rsi < self._rsi_short_threshold.Value and self.Position >= 0:
side = Sides.Sell
if side is not None:
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = self.Volume + abs(self.Position)
order.Comment = "Golden Edge confirmed cross"
self.RegisterOrder(order)
self._is_initialized = True
self._previous_fast = fast
self._previous_slow = slow
self._previous_hma = hma
def CreateClone(self):
return precision_trading_golden_edge_strategy()