在 GitHub 上查看
货币强度 v1.1 策略
概述
货币强度 v1.1 策略复现了 MetaTrader 专家顾问 Currency Strength v1.1 的核心思想。策略使用 26 个主要及交叉外汇货币对的日线百分比涨跌幅来衡量八大主要货币(USD、EUR、JPY、CAD、AUD、NZD、GBP、CHF)的相对强弱。当两种货币的强弱差值超过阈值时,策略会在对应的货币对上按强势方向开仓。
市场与数据
- 交易标的: 26 个主要外汇货币对(USDJPY、USDCAD、AUDUSD、USDCHF、GBPUSD、EURUSD、NZDUSD、EURJPY、EURCAD、EURGBP、EURCHF、EURAUD、EURNZD、AUDNZD、AUDCAD、AUDCHF、AUDJPY、CHFJPY、GBPCHF、GBPAUD、GBPCAD、GBPJPY、CADJPY、NZDJPY、GBPNZD、CADCHF)。
- 数据周期: 日线 (D1)。策略仅处理已经完成的 K 线,保证计算一致性。
- 所需字段: 每根 K 线的开盘价、最高价、最低价、收盘价。
货币强度计算
每个货币对的日内涨跌幅按以下公式计算:
(change) = (Close − Open) / Open × 100
随后根据原始 EA 的公式组合成各货币强度:
- EUR 强度 = EURJPY、EURCAD、EURGBP、EURCHF、EURAUD、EURUSD、EURNZD 的平均值
- USD 强度 = USDJPY、USDCAD、–AUDUSD、USDCHF、–GBPUSD、–EURUSD、–NZDUSD 的平均值
- JPY 强度 = USDJPY、EURJPY、AUDJPY、CHFJPY、GBPJPY、CADJPY、NZDJPY 的平均值取相反数
- CAD 强度 = CADCHF、CADJPY、–GBPCAD、–AUDCAD、–EURCAD、–USDCAD 的平均值
- AUD 强度 = AUDUSD、AUDNZD、AUDCAD、AUDCHF、AUDJPY、–EURAUD、–GBPAUD 的平均值
- NZD 强度 = NZDUSD、NZDJPY、–EURNZD、–AUDNZD、–GBPNZD 的平均值
- GBP 强度 = GBPUSD、–EURGBP、GBPCHF、GBPAUD、GBPCAD、GBPJPY、GBPNZD 的平均值
- CHF 强度 = CHFJPY、–USDCHF、–EURCHF、–AUDCHF、–GBPCHF、–CADCHF 的平均值
所有平均数均与原 EA 保持相同的权重构成。
交易规则
- 当 26 个货币对全部生成新的已完成日线后,重新计算所有货币强度。
- 对于每个货币对,比较其基准货币与计价货币的强度差。如果绝对值大于
DifferenceThreshold,则产生交易信号。
- 信号方向遵循强弱关系:
- 基准货币强于计价货币 → 做多该货币对。
- 基准货币弱于计价货币 → 做空该货币对。
- 仅当日线蜡烛与信号方向一致时才允许下单(收盘价高于开盘价才能买入,收盘价低于开盘价才能卖出),这与原 EA 的趋势过滤条件保持一致。
- 策略遵循净持仓模式。如果反向信号出现且存在相反持仓,会先平掉原仓位再以市价反向开仓。
- 若启用
TradeOncePerDay,每个货币对每天最多只允许一次多头入场和一次空头入场。
风险管理与离场
UseSlTp 选项可以启用日线级别的止损与止盈检查,距离以点(pip)为单位,通过 StopLossPips 与 TakeProfitPips 设置。
- 保护逻辑使用最新日线的最高价/最低价判断是否触达止盈或止损,触发后在下一次评估时以市价平仓。
- 若未启用止损止盈,仓位将保持不变,直到出现反向信号或手动停止策略,这与原 EA 的行为一致。
参数说明
| 参数 |
描述 |
CandleType |
使用的 K 线周期(默认:日线)。 |
DifferenceThreshold |
触发交易所需的最小强度差(百分比点)。 |
TradeOncePerDay |
若为 true,限制每个货币对每天仅进多一次、进空一次。 |
UseSlTp |
启用基于日线的止损/止盈逻辑。 |
TakeProfitPips |
止盈点数。 |
StopLossPips |
止损点数。 |
| 货币对参数 |
26 个货币对的 Security 输入,启动前必须全部指定。 |
Volume |
基类属性,定义下单手数(默认 0.01 手)。 |
实现细节
- 策略使用高级 API 的
SubscribeCandles 为每个货币对单独订阅日线数据。
- 仅处理
CandleStates.Finished 的蜡烛,符合 StockSharp 的迁移要求。
- 在所有货币对的交易日同步前不会触发信号,确保货币篮子计算的一致性。
- 内部字典记录每个方向的最近入场日期,并保存持仓的入场信息以供止损止盈使用。
使用建议
- 在启动策略前为 26 个参数全部指定对应的证券,缺失将抛出异常以避免部分计算。
- 确保数据源可以为所有配置的货币对提供日线蜡烛,以维持强度计算同步。
- 调整
DifferenceThreshold 以控制交易频率,阈值越小信号越多但可能带来更多反向交易。
- 根据经纪商报价精度调整点差止损止盈距离,默认假设支持小数点后第五位的报价。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades based on percentage change momentum of candles.
/// Simplified from the original multi-pair currency strength approach to single-security.
/// </summary>
public class CurrencyStrengthV11Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _differenceThreshold;
private decimal? _prevChange;
private decimal? _prevMomentum;
private decimal _entryPrice;
private DateTimeOffset? _lastTradeTime;
/// <summary>
/// Candle type for analysis.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Minimum percentage change to trigger trades.
/// </summary>
public decimal DifferenceThreshold
{
get => _differenceThreshold.Value;
set => _differenceThreshold.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public CurrencyStrengthV11Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for strength calculation", "General");
_differenceThreshold = Param(nameof(DifferenceThreshold), 0.2m)
.SetDisplay("Threshold", "Minimum percentage change to trigger trade", "Parameters")
.SetOptimize(0.05m, 1m, 0.05m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevChange = null;
_prevMomentum = null;
_entryPrice = 0m;
_lastTradeTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormed)
return;
var change = candle.OpenPrice != 0m
? (candle.ClosePrice - candle.OpenPrice) / candle.OpenPrice * 100m
: 0m;
if (_prevChange == null)
{
_prevChange = change;
return;
}
var momentum = change - _prevChange.Value;
var cooldownPassed = _lastTradeTime is null || candle.CloseTime - _lastTradeTime >= TimeSpan.FromHours(24);
var longSignal = _prevMomentum is decimal prevMomentum && prevMomentum <= DifferenceThreshold && momentum > DifferenceThreshold;
var shortSignal = _prevMomentum is decimal prevMomentum2 && prevMomentum2 >= -DifferenceThreshold && momentum < -DifferenceThreshold;
if (cooldownPassed && longSignal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
else if (cooldownPassed && shortSignal && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket(Volume > 0m ? Volume : 1m);
_entryPrice = candle.ClosePrice;
_lastTradeTime = candle.CloseTime;
}
_prevChange = change;
_prevMomentum = momentum;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class currency_strength_v11_strategy(Strategy):
def __init__(self):
super(currency_strength_v11_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._difference_threshold = self.Param("DifferenceThreshold", 0.2)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def DifferenceThreshold(self):
return self._difference_threshold.Value
@DifferenceThreshold.setter
def DifferenceThreshold(self, value):
self._difference_threshold.Value = value
def OnStarted2(self, time):
super(currency_strength_v11_strategy, self).OnStarted2(time)
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
change = (close - open_price) / open_price * 100.0 if open_price != 0.0 else 0.0
if self._prev_change is None:
self._prev_change = change
return
momentum = change - self._prev_change
threshold = float(self.DifferenceThreshold)
long_signal = (self._prev_momentum is not None and
self._prev_momentum <= threshold and
momentum > threshold)
short_signal = (self._prev_momentum is not None and
self._prev_momentum >= -threshold and
momentum < -threshold)
cooldown_passed = (self._last_trade_time is None or
candle.CloseTime - self._last_trade_time >= TimeSpan.FromHours(24))
if cooldown_passed and long_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.BuyMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
elif cooldown_passed and short_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
vol = float(self.Volume) if float(self.Volume) > 0 else 1.0
self.SellMarket(vol)
self._entry_price = float(candle.ClosePrice)
self._last_trade_time = candle.CloseTime
self._prev_change = change
self._prev_momentum = momentum
def OnReseted(self):
super(currency_strength_v11_strategy, self).OnReseted()
self._prev_change = None
self._prev_momentum = None
self._entry_price = 0.0
self._last_trade_time = None
def CreateClone(self):
return currency_strength_v11_strategy()