Ver en GitHub

She Kanskigor Daily Strategy

Overview

She Kanskigor Daily Strategy is a once-per-day breakout system that mirrors the original MetaTrader expert advisor SHE_kanskigor.mq4. The strategy evaluates the direction of the previous daily candle and opens a single market position inside a narrow time window at the start of the new trading day. It automatically monitors the position to close it by a configurable take-profit or stop-loss distance, expressed in security price steps.

Trading Logic

  1. Subscribe to both intraday candles (default: 1 minute) and daily candles for the selected security.
  2. Update the stored daily open and close whenever a finished daily candle arrives.
  3. On every finished intraday candle:
    • Reset the "traded today" flag when a new calendar date starts.
    • Manage the active position by checking whether the close price hits the stop-loss or take-profit thresholds.
    • Check whether the current time is inside the configured trading window (default start: 00:05, window length: 5 minutes).
    • If no position was opened yet today and a valid previous daily candle is available:
      • Go long when the previous daily open is higher than the close (bearish candle).
      • Go short when the previous daily open is lower than the close (bullish candle).
    • Skip trading when the previous day closed unchanged.
  4. The strategy executes protective exits using market orders once the close price touches the configured thresholds.

Parameters

Name Description Default
Volume Order volume used for entries. 0.1
Take Profit Profit target expressed in price steps. A value of 0 disables the target. 35
Stop Loss Loss threshold expressed in price steps. A value of 0 disables the stop. 55
Start Time Time of day (exchange time zone) when the entry window starts. 00:05
Window (min) Duration, in minutes, of the entry window. 5
Intraday Candle Candle data type used for intraday processing (default: 1-minute candles). TimeFrameCandleMessage(1m)

Notes

  • The strategy allows only one entry per trading day.
  • Daily candle data must be available; otherwise the strategy waits until a completed candle arrives.
  • Protective exits operate on the closing price of finished intraday candles.
  • The code uses StockSharp high-level API (SubscribeCandles with Bind) and adheres to the project coding standards (tabs, English comments, and parameter metadata).
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>
/// Daily breakout strategy that opens a position during a short time window.
/// Uses the previous daily candle direction to decide whether to buy or sell.
/// Applies configurable take-profit and stop-loss levels expressed in price steps.
/// </summary>
public class SheKanskigorDailyStrategy : Strategy
{
	private readonly StrategyParam<TimeSpan> _startTime;
	private readonly StrategyParam<int> _tradeWindowMinutes;
	private readonly StrategyParam<decimal> _takeProfitSteps;
	private readonly StrategyParam<decimal> _stopLossSteps;
	private readonly StrategyParam<DataType> _intradayCandleType;

	private readonly DataType _dailyCandleType;

	private DateTime _currentDate;
	private bool _tradePlaced;
	private bool _dailyReady;
	private decimal _previousOpen;
	private decimal _previousClose;
	private decimal _entryPrice;

	/// <summary>
	/// Start time of the trading window.
	/// </summary>
	public TimeSpan StartTime
	{
		get => _startTime.Value;
		set => _startTime.Value = value;
	}

	/// <summary>
	/// Width of the trading window in minutes.
	/// </summary>
	public int TradeWindowMinutes
	{
		get => _tradeWindowMinutes.Value;
		set => _tradeWindowMinutes.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in security price steps.
	/// </summary>
	public decimal TakeProfitSteps
	{
		get => _takeProfitSteps.Value;
		set => _takeProfitSteps.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in security price steps.
	/// </summary>
	public decimal StopLossSteps
	{
		get => _stopLossSteps.Value;
		set => _stopLossSteps.Value = value;
	}

	/// <summary>
	/// Intraday candle type used to evaluate the trading window.
	/// </summary>
	public DataType IntradayCandleType
	{
		get => _intradayCandleType.Value;
		set => _intradayCandleType.Value = value;
	}

	/// <summary>
	/// Initializes <see cref="SheKanskigorDailyStrategy"/>.
	/// </summary>
	public SheKanskigorDailyStrategy()
	{
		_takeProfitSteps = Param(nameof(TakeProfitSteps), 35m)
			.SetDisplay("Take Profit", "Profit target in steps", "Risk")
			;

		_stopLossSteps = Param(nameof(StopLossSteps), 55m)
			.SetDisplay("Stop Loss", "Loss limit in steps", "Risk")
			;

		_startTime = Param(nameof(StartTime), new TimeSpan(0, 5, 0))
			.SetDisplay("Start Time", "Time of day to evaluate entries", "Schedule");

		_tradeWindowMinutes = Param(nameof(TradeWindowMinutes), 5)
			.SetDisplay("Window (min)", "Trading window duration in minutes", "Schedule")
			;

		_intradayCandleType = Param(nameof(IntradayCandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Intraday Candle", "Candle type for intraday checks", "Data");

		_dailyCandleType = TimeSpan.FromMinutes(5).TimeFrame();
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, IntradayCandleType), (Security, _dailyCandleType)];
	}

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

		_currentDate = default;
		_tradePlaced = false;
		_dailyReady = false;
		_previousOpen = 0m;
		_previousClose = 0m;
		_entryPrice = 0m;
	}

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

		var intraday = SubscribeCandles(IntradayCandleType);
		intraday.Bind(ProcessIntraday).Start();

		var daily = SubscribeCandles(_dailyCandleType);
		daily.Bind(ProcessDaily).Start();

		StartProtection(null, null);
	}

	private void ProcessDaily(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Store the direction of the last completed daily candle.
		_previousOpen = candle.OpenPrice;
		_previousClose = candle.ClosePrice;
		_dailyReady = true;
	}

	private void ProcessIntraday(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var openTime = candle.OpenTime;

		if (openTime.Date != _currentDate)
		{
			_currentDate = openTime.Date;
			_tradePlaced = false;
		}

		ManagePosition(candle.ClosePrice);

		var start = StartTime;
		var end = start.Add(TimeSpan.FromMinutes(TradeWindowMinutes));
		var currentTod = openTime.TimeOfDay;

		if (currentTod < start || currentTod > end)
			return;

		if (_tradePlaced)
			return;

		if (!_dailyReady)
			return;

		if (Position != 0)
		{
			_tradePlaced = true;
			return;
		}

		if (_previousOpen > _previousClose)
		{
			BuyMarket(Volume);
			_tradePlaced = true;
		}
		else if (_previousOpen < _previousClose)
		{
			SellMarket(Volume);
			_tradePlaced = true;
		}
		else
		{
			// Skip trading when the previous day closed unchanged.
			_tradePlaced = true;
		}
	}

	private void ManagePosition(decimal closePrice)
	{
		if (Position == 0)
			return;

		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m || _entryPrice == 0m)
			return;

		if (Position > 0)
		{
			var target = _entryPrice + TakeProfitSteps * step;
			var stop = _entryPrice - StopLossSteps * step;

			if (TakeProfitSteps > 0m && closePrice >= target)
			{
				SellMarket(Position);
				return;
			}

			if (StopLossSteps > 0m && closePrice <= stop)
			{
				SellMarket(Position);
			}
		}
		else
		{
			var target = _entryPrice - TakeProfitSteps * step;
			var stop = _entryPrice + StopLossSteps * step;

			if (TakeProfitSteps > 0m && closePrice <= target)
			{
				BuyMarket(-Position);
				return;
			}

			if (StopLossSteps > 0m && closePrice >= stop)
			{
				BuyMarket(-Position);
			}
		}
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (trade.Order.Security != Security)
			return;

		// Track the latest fill price to evaluate protective exits.
		_entryPrice = trade.Trade.Price;
	}
}