GitHub で見る

Precipice Strategy

Overview

The Precipice strategy is a direct conversion of the MetaTrader expert advisor Precipice (barabashkakvn's edition). The system does not analyse market structure or use indicators; instead it waits for the previous position to close and then flips a coin to decide whether to enter long or short. If the trader enables both directions, every finished candle has a 50% chance of spawning a new position provided the account is currently flat. Optional protective orders mirror the MetaTrader behaviour by attaching the same stop-loss and take-profit distance in "pips" to every trade.

The StockSharp implementation keeps the random nature of the original code and mirrors its money-management settings. It automatically converts the MetaTrader pip distance into the instrument's native price step so the stop-loss and take-profit remain symmetrical regardless of the number of decimal places used by the security.

Trading logic

  1. Subscribe to the primary candle series defined by CandleType and process only completed candles so the trade timing matches MetaTrader's OnTick logic after the bar closes.
  2. Ignore all signals while a position is open. The expert places at most one trade at a time.
  3. When the strategy is flat, draw a random number for the buy branch. If UseBuy is enabled and the draw is below 0.5, submit a market buy order with TradeVolume lots.
  4. If no long position was opened, draw another random number for the sell branch. When UseSell is enabled and the result exceeds 0.5, submit a market sell order.
  5. After an entry is filled, attach optional stop-loss and take-profit orders located StopLossTakeProfitPips MetaTrader pips away from the candle close. Protective orders are cancelled automatically when the position returns to zero.

Parameters

Name Type Default Description
CandleType DataType 1-minute time frame Primary timeframe processed by the strategy.
TradeVolume decimal 1 Order size used for every market entry.
StopLossTakeProfitPips int 100 Distance (in MetaTrader pips) between the entry price and both protective orders. Set to 0 to disable stop-loss and take-profit placement.
UseBuy bool true Enable random long entries.
UseSell bool true Enable random short entries.

Differences from the original MetaTrader expert

  • MetaTrader exposes the instrument's freeze and stop levels; the StockSharp port emulates only the pip-distance conversion and relies on the broker to reject invalid stop distances if necessary.
  • The original EA uses the current Bid/Ask quotes. The StockSharp strategy bases protective orders on the candle's closing price because the high-level API receives aggregated candle data; slippage and spread effects must be handled externally.
  • MetaTrader works with individual tickets, whereas StockSharp manages net positions. The conversion keeps at most one net position and removes protective orders as soon as the exposure goes back to zero.

Usage tips

  • Choose a realistic TradeVolume that matches the security's lot step. The constructor also applies this value to Strategy.Volume so helper methods send orders with the intended quantity.
  • Adjust StopLossTakeProfitPips to match the instrument's volatility. The strategy multiplies pips by the security's price step (scaled for 3/5-digit quotes) to obtain a native price distance.
  • Enable only UseBuy or UseSell if you want the random generator to open trades in a single direction, for example to test directional risk controls.
  • Because entries are random, monitor the strategy with additional risk limits or a maximum position duration if you need deterministic exit conditions.

Indicators

  • None. The strategy relies purely on random trade generation and optional protective orders.
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;
	}
}