在 GitHub 上查看

FXF Fast in Fast out 策略

概述

FXF Fast in Fast out 策略是一套基于波动率的突破系统,将原始的 MetaTrader 4 专家顾问重写为 StockSharp 高级策略。策略监控可配置的时间框架,当出现大幅波动的 K 线时测量点差,并尝试通过挂单捕捉顺势延续。信号只依赖于已经收盘的 K 线,而点差过滤、下单与移动止损依靠 Level1 行情数据完成。

当当前 K 线的振幅超过预设阈值时,策略会计算最新买卖价的平均值,并将其与 K 线开盘价比较:若均价高于开盘价,则在卖价上方放置买入止损;若均价低于开盘价,则在买价下方放置卖出止损。挂单会附带止损和止盈,且可以根据账户资金和止损距离进行动态仓位管理;一旦成交,还可以启用可选的移动止损保护持仓。

交易逻辑

  • 信号检测:只在 K 线收盘后工作,计算 (最高价-最低价) 转换成价格步进数,若不少于 VolatilitySizePoints 即认为波动足够。
  • 方向判定:使用最新买价与卖价的均值作为代表价格,若高于开盘价则产生做多突破信号,低于开盘价则产生做空突破信号;如果均价等于开盘价或波动不足则忽略。
  • 点差过滤:持续监控当前点差,只有当点差不超过 MaxSpreadPoints 时才允许下单;当点差扩大到限制之上时会取消现有挂单并暂停入场。
  • 挂单管理:每根 K 线只允许放置一次挂单,并且仅持有一个方向。挂单价格与最新报价之间的距离由 EnterOffsetPoints 定义,止损与止盈距离同样使用点数配置。
  • 资金管理:启用 UseMoneyManagement 后,策略按账户净值、RiskPercent 与止损距离计算下单手数;若缺乏必要的合约信息或未设置止损,则退回到默认的 Volume
  • 移动止损EnableTrailing 为真时,对已持仓使用内部追踪止损,将 TrailingStopPoints 与当前点差相加得到保护距离,一旦行情反向触发,就以市价平仓。

参数

参数 说明
EnterOffsetPoints 挂单相对于最新报价的偏移距离(价格步)。
MaxSpreadPoints 允许的最大点差(价格步)。超出时禁止入场并撤销挂单。
TakeProfitPoints 挂单的止盈距离(价格步),设为 0 表示不设置。
StopLossPoints 挂单的止损距离(价格步),资金管理需要该值。设为 0 表示不设置。
VolatilitySizePoints 触发信号所需的最小 K 线振幅(价格步)。
EnableTrailing 是否启用移动止损。
TrailingStopPoints 移动止损的基础距离(价格步),执行时会加上当前点差。
UseMoneyManagement 是否根据账户资金自动计算手数。
RiskPercent 当启用资金管理时,每笔交易的风险百分比。
MaxOrdersPerBar 单根 K 线允许的最大挂单次数,通常设为 1。
CandleType 策略使用的 K 线类型/周期,默认 15 分钟。

下单流程

  1. 识别:收盘后的 K 线满足波动要求时记录方向信号。
  2. 校验:需要有最新的买卖报价、策略允许交易、当前无持仓且没有其他挂单。
  3. 下单:根据方向放置买入或卖出止损单,附带止损与止盈。
  4. 退出:订单成交后,若启用移动止损则根据报价更新保护价,触发时立即市价平仓;同时保留原始止损和止盈供撮合平台执行。

注意事项

  • 策略需要同时订阅 K 线与 Level1 行情,否则无法评估波动与点差。
  • 当缺少价格步长或步价值等合约信息时,资金管理会自动降级为固定手数模式。
  • 移动止损通过内部市价出场实现,以最大限度复现原始 EA 在不同交易环境中的行为。
namespace StockSharp.Samples.Strategies;

using System;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Fast In Fast Out scalping strategy: quick entry/exit using EMA and RSI.
/// </summary>
public class FxfFastInFastOutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public FxfFastInFastOutStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");

		_emaPeriod = Param(nameof(EmaPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 7)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		decimal? prevRsi = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, rsi, (candle, emaVal, rsiVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevRsi.HasValue)
				{
					if (prevRsi.Value <= 50 && rsiVal > 50 && close > emaVal && Position <= 0)
						BuyMarket();
					else if (prevRsi.Value >= 50 && rsiVal < 50 && close < emaVal && Position >= 0)
						SellMarket();
				}

				prevRsi = rsiVal;
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawOwnTrades(area);
		}
	}
}