Открыть на GitHub

Стратегия Day Trading PAMXA

Описание

Day Trading PAMXA — это порт на StockSharp популярного эксперта MetaTrader 5, который объединяет развороты осциллятора Awesome Oscillator и фильтр по стохастику. В версии для StockSharp сохранена многофреймовая структура:

  • Главный цикл принятия решений работает на таймфрейме Signal Candles (по умолчанию 1 час).
  • Awesome Oscillator рассчитывается на отдельном таймфрейме AO Candles (по умолчанию 1 день), что позволяет учитывать старший импульс.
  • Стохастик использует собственный таймфрейм Stochastic Candles (по умолчанию 1 час), чтобы %K/%D совпадали с оригинальными настройками.

Стратегия всегда держит не более одной позиции. При появлении бычьего сигнала сначала закрываются все открытые шорты, затем открывается лонг. Для медвежьего сигнала логика зеркальна.

Логика входа

  1. Получить последние завершённые значения Awesome Oscillator на таймфрейме AO.
  2. Получить последние завершённые значения %K и %D стохастика на его таймфрейме.
  3. На каждом завершённом сигналом свечи таймфрейма Signal выполняется проверка:
    • Бычий сигнал: предыдущий столбик AO был ниже нуля, а текущий закрылся выше нуля, и при этом %K или %D опустились ниже порога Stochastic Level Down. Если шортов нет — открывается лонг.
    • Медвежий сигнал: предыдущий столбик AO был выше нуля, текущий закрылся ниже нуля, и %K или %D поднялись выше порога Stochastic Level Up. Если лонгов нет — открывается шорт.

Выход и управление рисками

  • Стоп-лосс и тейк-профит задаются в пипсах. При пробое минимума свечи (для лонга) или максимума (для шорта) ниже/выше стоп-уровня позиция закрывается. Аналогично проверяется цель.
  • Трейлинг-стоп активируется после прохождения ценой расстояния Trailing Stop + Trailing Step в сторону позиции. Для лонга стоп подтягивается под максимум свечи минус дистанция, для шорта — над минимум свечи плюс дистанция. Перенос выполняется только когда прирост превышает шаг трейлинга, как в MetaTrader.
  • Money Mode позволяет выбрать фиксированный объём (FixedVolume) либо риск-процент (RiskPercent). В режиме RiskPercent объём рассчитывается так, чтобы при срабатывании стопа потерять заданный процент от стоимости портфеля (учитывается расстояние до стопа и шаг объёма инструмента).
  • Усреднения и пирамидинга нет — новый сигнал сначала закрывает противоположную позицию.

Параметры

Параметр Назначение
Stop Loss Дистанция стоп-лосса в пипсах (0 — отключить).
Take Profit Дистанция тейк-профита в пипсах (0 — отключить).
Trailing Stop Дистанция активации трейлинг-стопа в пипсах (0 — выключить трейлинг).
Trailing Step Минимальный шаг в пипсах для переноса трейлинга (должен быть >0 при включённом трейлинге).
Money Mode Режим управления объёмом: фиксированный объём или риск-процент.
Money Value Лот в режиме FixedVolume или процент риска в режиме RiskPercent.
Order Volume Базовый объём сделок при фиксированном режиме.
Stochastic %K Длина расчёта линии %K.
Stochastic %D Длина сглаживания линии %D.
Stochastic Slow Дополнительное сглаживание стохастика.
Level Up Верхний уровень стохастика для поиска шорта.
Level Down Нижний уровень стохастика для поиска лонга.
Signal Candles Таймфрейм основного цикла сигналов.
Stochastic Candles Таймфрейм для расчёта стохастика.
AO Candles Таймфрейм для расчёта Awesome Oscillator.
AO Fast / AO Slow Периоды скользящих внутри AO.

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

  • Размер пипса рассчитывается как в MetaTrader: для инструментов с 3 или 5 знаками после запятой пип равен десяти минимальным шагам цены, иначе одному шагу.
  • Стандартный индикатор StochasticOscillator в StockSharp не поддерживает выбор ценового поля Low/High, поэтому используется расчёт по закрытию, но все периоды и сглаживание остаются настраиваемыми.
  • Трейлинг реализован виртуально — сравниваются экстремумы свечей, что повторяет серверные модификации стопа в MetaTrader без фактической регистрации стоп-заявок.
  • Метод GetWorkingSecurities подписывает стратегию на три таймфрейма, чтобы движок заранее запросил все необходимые данные.
  • В коде добавлены поясняющие комментарии на английском языке, упрощающие поддержку.

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

  • Подбирайте Signal Candles под нужный вам торговый таймфрейм. Чтобы максимально повторить MQL5-версию, оставьте Stochastic Candles и AO Candles по умолчанию.
  • При выборе режима RiskPercent убедитесь, что стоп-лосс больше нуля, иначе стратегия перейдёт к фиксированному объёму.
  • Стандартные значения трейлинга (25/5 пипсов) соответствуют оригинальному эксперту. При необходимости отключите его, установив Trailing Stop в ноль.
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>
/// Day Trading PAMXA strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class DayTradingPamxaStrategy : 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 DayTradingPamxaStrategy()
	{
		_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;
	}
}