Forex Fraus Portfolio 策略
该策略基于长周期的 Williams %R 指标,只交易单一品种。当指标离开极端区间时,策略按照突破方向开仓。
工作原理
- 计算
WprPeriod根K线的 Williams %R。 - 当指标跌破
BuyThreshold时,准备做多;随后指标回到阈值之上时,以市价买入。 - 当指标升破
SellThreshold时,准备做空;随后指标跌回阈值之下时,以市价卖出。 - 只在
StartHour与StopHour之间的时间窗口内交易。 - 可通过参数启用止损、止盈和追踪止损。
参数
WprPeriod– Williams %R 周期。BuyThreshold– 做多信号阈值。SellThreshold– 做空信号阈值。StartHour/StopHour– 交易时段。SlPoints– 止损点数,0 表示禁用。TpPoints– 止盈点数,0 表示禁用。UseTrailing– 是否使用追踪止损。TrailingStop– 追踪止损距离(点)。TrailingStep– 追踪止损更新步长(点)。CandleType– 订阅的K线类型。
备注
原始的 MQL4 版本针对多个货币对进行交易,并分别管理每个订单。本 C# 版本专注于单一品种,演示如何使用 StockSharp 的高级 API 实现核心逻辑。
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>
/// Williams %R based strategy with trailing stop logic.
/// </summary>
public class ForexFrausPortfolioStrategy : Strategy
{
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<decimal> _buyThreshold;
private readonly StrategyParam<decimal> _sellThreshold;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _stopHour;
private readonly StrategyParam<DataType> _candleType;
private bool _okBuy;
private bool _okSell;
public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
public decimal BuyThreshold { get => _buyThreshold.Value; set => _buyThreshold.Value = value; }
public decimal SellThreshold { get => _sellThreshold.Value; set => _sellThreshold.Value = value; }
public int StartHour { get => _startHour.Value; set => _startHour.Value = value; }
public int StopHour { get => _stopHour.Value; set => _stopHour.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ForexFrausPortfolioStrategy()
{
_wprPeriod = Param(nameof(WprPeriod), 60)
.SetDisplay("WPR Period", "Williams %R calculation period", "Parameters")
.SetOptimize(20, 200, 20);
_buyThreshold = Param(nameof(BuyThreshold), -90m)
.SetDisplay("Buy Threshold", "Trigger level for long entry", "Parameters");
_sellThreshold = Param(nameof(SellThreshold), -10m)
.SetDisplay("Sell Threshold", "Trigger level for short entry", "Parameters");
_startHour = Param(nameof(StartHour), 0)
.SetDisplay("Start Hour", "Trading start hour", "Time");
_stopHour = Param(nameof(StopHour), 24)
.SetDisplay("Stop Hour", "Trading stop hour", "Time");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_okBuy = false;
_okSell = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_okBuy = false;
_okSell = false;
var wpr = new WilliamsR { Length = WprPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(wpr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, wpr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal wprValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var hour = candle.OpenTime.Hour;
var inTime = StartHour <= StopHour
? hour >= StartHour && hour < StopHour
: hour >= StartHour || hour < StopHour;
if (!inTime)
{
if (Position > 0)
SellMarket();
else if (Position < 0)
BuyMarket();
return;
}
// WPR dips below buy threshold => arm buy signal
if (wprValue < BuyThreshold)
_okBuy = true;
// WPR crosses back above buy threshold while armed => buy
if (wprValue > BuyThreshold && _okBuy)
{
_okBuy = false;
if (Position <= 0)
BuyMarket();
return;
}
// WPR rises above sell threshold => arm sell signal
if (wprValue > SellThreshold)
_okSell = true;
// WPR crosses back below sell threshold while armed => sell
if (wprValue < SellThreshold && _okSell)
{
_okSell = false;
if (Position >= 0)
SellMarket();
}
}
}
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
from StockSharp.Algo.Indicators import WilliamsR
from StockSharp.Algo.Strategies import Strategy
class forex_fraus_portfolio_strategy(Strategy):
def __init__(self):
super(forex_fraus_portfolio_strategy, self).__init__()
self._wpr_period = self.Param("WprPeriod", 60) \
.SetDisplay("WPR Period", "Williams %R calculation period", "Parameters")
self._buy_threshold = self.Param("BuyThreshold", -90.0) \
.SetDisplay("Buy Threshold", "Trigger level for long entry", "Parameters")
self._sell_threshold = self.Param("SellThreshold", -10.0) \
.SetDisplay("Sell Threshold", "Trigger level for short entry", "Parameters")
self._start_hour = self.Param("StartHour", 0) \
.SetDisplay("Start Hour", "Trading start hour", "Time")
self._stop_hour = self.Param("StopHour", 24) \
.SetDisplay("Stop Hour", "Trading stop hour", "Time")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._ok_buy = False
self._ok_sell = False
@property
def wpr_period(self):
return self._wpr_period.Value
@property
def buy_threshold(self):
return self._buy_threshold.Value
@property
def sell_threshold(self):
return self._sell_threshold.Value
@property
def start_hour(self):
return self._start_hour.Value
@property
def stop_hour(self):
return self._stop_hour.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(forex_fraus_portfolio_strategy, self).OnReseted()
self._ok_buy = False
self._ok_sell = False
def OnStarted2(self, time):
super(forex_fraus_portfolio_strategy, self).OnStarted2(time)
self._ok_buy = False
self._ok_sell = False
wpr = WilliamsR()
wpr.Length = self.wpr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(wpr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, wpr)
self.DrawOwnTrades(area)
def process_candle(self, candle, wpr_value):
if candle.State != CandleStates.Finished:
return
wpr_value = float(wpr_value)
hour = candle.OpenTime.Hour
start_h = int(self.start_hour)
stop_h = int(self.stop_hour)
if start_h <= stop_h:
in_time = hour >= start_h and hour < stop_h
else:
in_time = hour >= start_h or hour < stop_h
if not in_time:
if self.Position > 0:
self.SellMarket()
elif self.Position < 0:
self.BuyMarket()
return
buy_thr = float(self.buy_threshold)
sell_thr = float(self.sell_threshold)
if wpr_value < buy_thr:
self._ok_buy = True
if wpr_value > buy_thr and self._ok_buy:
self._ok_buy = False
if self.Position <= 0:
self.BuyMarket()
return
if wpr_value > sell_thr:
self._ok_sell = True
if wpr_value < sell_thr and self._ok_sell:
self._ok_sell = False
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return forex_fraus_portfolio_strategy()