Открыть на GitHub

Стратегия Freeman

Freeman — внутридневная стратегия, комбинирующая несколько индикаторных фильтров для поэтапного входа в тенденцию. На рабочем таймфрейме используются две «RSI-школы», построенные на разных периодах скользящих средних, а тренд подтверждается фильтром с более высокого таймфрейма. Риски контролируются ATR-ориентированными уровнями стоп-лосса и тейк-профита, а также трейлинг-стопом в пунктах.

Общая информация

  • Работает на свечах таймфрейма, выбранного параметром CandleType (по умолчанию 15 минут).
  • Для фильтрации тренда подключается отдельный поток свечей (FilterCandleType), по умолчанию — часовой.
  • Сигналы формируются двумя блоками RSI, которые сравнивают текущее и предыдущее значения и анализируют наклон скользящих средних.
  • Допускается наращивание позиции при движении цены, причём после убыточного выхода объём следующей заявки увеличивается по коэффициенту.

Логика входов

Длинные позиции

  1. Трендовый фильтр опционален. При его включении часовая скользящая средняя должна расти.
  2. RSI Teacher #1 активен, когда:
    • RSI #1 на предыдущей свече был ниже RsiSellLevel, а на текущей начинает расти.
    • Быстрая скользящая средняя увеличивается.
    • Часовой RSI (период 14) остаётся ниже RsiBuyLevel, подтверждая отсутствие перекупленности.
  3. RSI Teacher #2 активен, когда:
    • RSI #2 был ниже RsiSellLevel2 и поворачивает вверх.
    • Медленная скользящая средняя растёт.
    • Часовой RSI остаётся ниже RsiBuyLevel2.
  4. Лонг открывается, если активен хотя бы один блок и трендовый фильтр (если включён) подтверждает направление.
  5. Дополнительные покупки допускаются, когда цена прошла от последнего входа больше, чем DistancePips, умноженное на шаг цены инструмента. Если предыдущий выход по лонгу был убыточным, объём умножается на LockCoefficient.

Короткие позиции

Условия зеркальны длинным позициям:

  • При включённом фильтре часовая скользящая средняя должна снижаться.
  • RSI Teacher #1 требует, чтобы RSI #1 был выше RsiBuyLevel и начал снижаться, быстрая SMA падала, а часовой RSI был выше RsiSellLevel.
  • RSI Teacher #2 требует, чтобы RSI #2 был выше RsiBuyLevel2 и развернулся вниз, медленная SMA падала, а часовой RSI был выше RsiSellLevel2.
  • Правила по дистанции и коэффициенту для докупок аналогичны.

Управление позицией

  • Стоп-лосс и тейк-профит рассчитываются при каждом входе на основе текущего ATR и коэффициентов StopLossAtrFactor и TakeProfitAtrFactor.
  • Трейлинг-стоп включается после прохождения ценой расстояния TrailingStopPips + TrailingStepPips и удерживает стоп на расстоянии TrailingStopPips от последнего закрытия.
  • Выходы осуществляются рыночными заявками, как только минимум/максимум свечи пробивает рассчитанные уровни стопа или цели.
  • Параметр PositionsMaximum ограничивает суммарное количество совершённых входов (лонг + шорт). Значение 0 снимает ограничение.

Временные фильтры

  • Торговлю по пятницам можно отключить параметром TradeOnFriday.
  • StartHour и EndHour задают опциональное торговое окно во времени биржи; нули означают торговлю в течение всего дня.

Параметры

Имя Описание
CandleType Таймфрейм рабочих свечей для расчёта сигналов.
FilterCandleType Таймфрейм фильтра тренда и часового RSI (по умолчанию 1 час).
FirstMaPeriod / SecondMaPeriod Периоды быстрых и медленных скользящих средних.
FilterMaPeriod Длина скользящей средней на фильтрующем таймфрейме.
MaType Тип скользящих (SMA, EMA, SMMA или WMA).
RsiFirstPeriod / RsiSecondPeriod Периоды RSI в блоках Teacher #1 и Teacher #2.
RsiSellLevel, RsiBuyLevel, RsiSellLevel2, RsiBuyLevel2 Граничные значения RSI для активации блоков.
UseRsiTeacher1, UseRsiTeacher2, UseTrendFilter Переключатели компонентов стратегии.
StopLossAtrFactor, TakeProfitAtrFactor Коэффициенты ATR для стоп-лосса и тейк-профита.
TrailingStopPips, TrailingStepPips Настройки трейлинг-стопа в пунктах.
PositionsMaximum Лимит на количество входов; 0 — без ограничения.
DistancePips Минимальная дистанция (в пунктах) для добавления к позиции.
TradeOnFriday Разрешить или запретить торговлю по пятницам.
StartHour, EndHour Торговый интервал по времени биржи.
LockCoefficient Коэффициент увеличения объёма после убыточного выхода.
SignalShift Смещение при чтении значений индикаторов (0 — текущая закрытая свеча).

Особенности реализации

  • Перенос на StockSharp обрабатывает только закрытые свечи, что эквивалентно включённому параметру Bars Control в MT5; торговля «на каждом тике» не поддерживается.
  • Все расстояния в пунктах пересчитываются через шаг цены инструмента (PriceStep).
  • Защитные механизмы (стопы, тейки, трейлинг) реализованы через рыночные заявки, поскольку используется высокоуровневый API StockSharp вместо прямой модификации позиций MT5.
  • Учёт позиций ведётся агрегированно: после полного закрытия направления флаг убыточности сбрасывается, что соответствует оригинальной логике «локирования».

Перед использованием протестируйте стратегию и настройте управление риском под свой рынок.

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Freeman strategy using dual MA crossover with RSI filter.
/// </summary>
public class FreemanStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiBuyLevel;
	private readonly StrategyParam<decimal> _rsiSellLevel;

	private decimal? _prevFast;
	private decimal? _prevSlow;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

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

	public decimal RsiBuyLevel
	{
		get => _rsiBuyLevel.Value;
		set => _rsiBuyLevel.Value = value;
	}

	public decimal RsiSellLevel
	{
		get => _rsiSellLevel.Value;
		set => _rsiSellLevel.Value = value;
	}

	public FreemanStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period for filter", "Indicators");

		_rsiBuyLevel = Param(nameof(RsiBuyLevel), 55m)
			.SetDisplay("RSI Buy Level", "RSI below which buys are allowed", "Levels");

		_rsiSellLevel = Param(nameof(RsiSellLevel), 45m)
			.SetDisplay("RSI Sell Level", "RSI above which sells are allowed", "Levels");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = null;
		_prevSlow = null;
	}

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

		_prevFast = null;
		_prevSlow = null;

		var fastSma = new SimpleMovingAverage { Length = FastPeriod };
		var slowSma = new SimpleMovingAverage { Length = SlowPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastSma, slowSma, rsi, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast == null || _prevSlow == null)
		{
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		var prevAbove = _prevFast.Value > _prevSlow.Value;
		var currAbove = fast > slow;

		_prevFast = fast;
		_prevSlow = slow;

		// MA crossover
		if (!prevAbove && currAbove)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (prevAbove && !currAbove)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}