在 GitHub 上查看

Precipice 策略

概览

Precipice 策略直接移植自 MetaTrader 专家顾问 Precipice (barabashkakvn's edition)。系统不会分析市场结构或使用任何指标,而是等待上一笔仓位平仓后抛硬币决定做多还是做空。如果同时允许多空方向,在账户处于空仓状态时,每根已结束的蜡烛都有 50% 的概率生成一笔新的开仓。可选的保护性订单会为每笔交易附加相同的止损和止盈距离,以 "点" 为单位,完全对齐原始 EA 的资金管理逻辑。

StockSharp 版本保留了原始代码的随机特性,并复刻其资金管理设置。策略会自动把 MetaTrader 的点值转换为标的物的本地最小价格步长,因此无论证券有多少小数位,止损与止盈都保持对称。

交易逻辑

  1. 订阅由 CandleType 指定的主图蜡烛序列,仅处理已收盘的蜡烛,从而在每根柱线结束时复制 MetaTrader OnTick 逻辑。
  2. 只要仓位不为空,就忽略所有信号。该专家顾问始终只持有一笔仓位。
  3. 当策略空仓时,为买入分支生成一个随机数。如果启用 UseBuy 且结果小于 0.5,则以 TradeVolume 手数提交市价买单。
  4. 如果没有触发多头,继续为卖出分支生成新的随机数。启用 UseSell 且结果大于 0.5 时,提交市价卖单。
  5. 开仓完成后,根据 StopLossTakeProfitPips 参数,把止损和止盈分别放在距蜡烛收盘价相同点数的位置。一旦仓位重新归零,策略会自动撤销这些保护性订单。

参数

名称 类型 默认值 说明
CandleType DataType 1 分钟周期 策略处理的主时间框架。
TradeVolume decimal 1 每次开仓使用的数量。
StopLossTakeProfitPips int 100 入场价与止损/止盈之间的距离(MetaTrader 点)。设为 0 可禁用保护性订单。
UseBuy bool true 允许随机开多。
UseSell bool true 允许随机开空。

与原版 MetaTrader 专家的差异

  • MetaTrader 提供冻结距离与最小止损距离;StockSharp 版本仅实现点值换算,必要时由经纪商拒绝不合规的止损距离。
  • 原版 EA 直接使用当前买价/卖价。由于高阶 API 仅接收聚合后的蜡烛数据,移植版以蜡烛收盘价计算保护性订单,滑点与点差的处理需由外部风险控制完成。
  • MetaTrader 以单独的订单票据运作,而 StockSharp 管理净头寸。移植版同样保持最多只有一笔净头寸,并在仓位归零时撤销所有保护单。

使用建议

  • 选择符合标的最小手数的 TradeVolume。构造函数同时把该值应用到 Strategy.Volume,确保便捷下单方法使用预期数量。
  • 根据品种波动性调整 StopLossTakeProfitPips。策略会把点数乘以证券的价格步长(针对 3/5 位小数自动缩放)以得到本地价格距离。
  • 如果只想测试单方向的风险控制,可仅启用 UseBuyUseSell
  • 由于入场完全随机,建议搭配额外的风险限制或最大持仓时间,以获得可控的退出条件。

指标

  • 无。策略完全依靠随机交易与可选的保护性订单。
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>
/// Precipice strategy using EMA crossover for trend direction.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class PrecipiceStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="PrecipiceStrategy"/> class.
	/// </summary>
	public PrecipiceStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast EMA period", "Indicator");

		_slowPeriod = Param(nameof(SlowPeriod), 80)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow EMA period", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fast = null;
		_slow = null;
		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fast.IsFormed || !_slow.IsFormed)
		{
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevFast = fastValue;
			_prevSlow = slowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 80;
				_prevFast = fastValue;
				_prevSlow = slowValue;
				return;
			}
		}

		// EMA crossover
		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 80;
		}
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 80;
		}

		_prevFast = fastValue;
		_prevSlow = slowValue;
	}
}