Открыть на GitHub

Killer Sell 2.0 (C#)

Обзор

Killer Sell 2.0 — это эксперт MetaTrader 4, который работает исключительно в короткую сторону. Он ищет точки входа после затяжного перекупленного движения и закрывает позиции, когда импульс переходит в область перепроданности. Перенос выполнен на высокоуровневом API StockSharp: индикаторы обновляются через SubscribeCandles().BindEx(...), а правила управления капиталом инкапсулированы внутри класса стратегии.

Логика торговли

На закрытии каждой свечи выбранного таймфрейма стратегия выполняет цепочку действий:

  1. Подготовка данных. Рассчитываются MACD (12/120/9), два индикатора Williams %R (по 350 периодов) и две версии стохастика (10/1/3 для входа и 90/7/1 для выхода). Значения используются только для завершённых свечей.
  2. Фильтр входа. Сделка открывается при выполнении всех условий:
    • Williams %R поднимается выше отметки −10.
    • Основная линия MACD превышает 0.0014.
    • %K входного стохастика пересекает сверху вниз заданный уровень (по умолчанию 90).
  3. Размещение ордера. При выполнении фильтров отправляется рыночный ордер Sell с текущим объёмом по мартингейлу. Через StartProtection к сделке прикрепляется защитный тейк-профит (100 пунктов по умолчанию).
  4. Управление выходом. Пока открыта короткая позиция, стратегия вычисляет среднюю прибыль открытых сделок в пунктах:
    • Если среднее значение ниже 10 пунктов и Williams %R опускается ниже −80, все продажи закрываются немедленно.
    • Если средняя прибыль превышает 15 пунктов и выходной стохастик %K падает ниже 12, позиция закрывается для фиксации результата.

Управление капиталом

Перенос сохраняет мартингейл из оригинального советника. Для имитации тикетов MT4 стратегия ведёт список открытых коротких сделок (цена + объём) и использует его для перерасчёта средней прибыли и выбора следующего шага лестницы:

  • Первая сделка открывается объёмом InitialVolume (по умолчанию 0.05 лота).
  • Положительный или нулевой результат сбрасывает объём к исходному значению.
  • Убыточная серия увеличивает следующий объём в MartingaleMultiplier раза (по умолчанию ×1.2), при этом MaxVolume ограничивает максимальную величину.

Реализованная прибыль фиксируется при обработке сделок, чтобы определить исход предыдущего цикла.

Параметры

Параметр Описание
CandleType Таймфрейм, по которому строятся свечи и индикаторы.
EntryWprPeriod / ExitWprPeriod Периоды Williams %R для входа и выхода.
MacdFastPeriod / MacdSlowPeriod / MacdSignalPeriod Настройки MACD.
MacdThreshold Минимальное значение MACD, разрешающее продажу.
StochasticEntryKPeriod, StochasticEntryDPeriod, StochasticEntrySlow Параметры стохастика для входа.
EntryStochasticLevel Уровень, который %K должен пересечь сверху вниз.
StochasticExitKPeriod, StochasticExitDPeriod, StochasticExitSlow Стохастик для выхода.
ExitStochasticLevel Верхняя граница перепроданности перед фиксацией прибыли.
EntryWprThreshold / ExitWprThreshold Пороговые значения Williams %R.
LossExitPips / ProfitExitPips Средняя прибыль (в пунктах), инициирующая защитный выход или фиксацию.
TakeProfitPips Расстояние защитного тейк-профита.
InitialVolume Начальный объём мартингейла.
MartingaleMultiplier Множитель после убыточной серии.
MaxVolume Предельный объём одной сделки.

Особенности переноса

  • MetaTrader работает с отдельными тикетами, тогда как StockSharp использует совокупную позицию. Чтобы воспроизвести логику усреднения, стратегия хранит каждую короткую сделку во внутреннем списке.
  • В блоке «Sell Now» исходного проекта были доступны десятки режимов управления капиталом, но выбран был именно мартингейл. В портированной версии реализован только он.
  • Жёсткий стоп-лосс в исходнике отключён. В C#-версии сделки получают только тейк-профит, а закрытие по убытку контролируется индикаторами.

Рекомендации по использованию

  1. Подключите стратегию к нужному инструменту и портфелю и задайте таймфрейм, на котором тестировался оригинальный советник (по умолчанию предполагается H1).
  2. Убедитесь, что поставщик данных передаёт завершённые свечи — индикаторы работают только по состоянию CandleStates.Finished.
  3. Настройте параметры объёма (InitialVolume, MaxVolume) с учётом плеча и требований брокера.
  4. Мартингейл значительно увеличивает риски при сильных трендах против позиций, поэтому рекомендуется предварительное тестирование и строгий контроль капитала.
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;

public class KillerSell20Strategy : 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;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public KillerSell20Strategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).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");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	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;

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

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

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}