Стратегия Freeman
Freeman — внутридневная стратегия, комбинирующая несколько индикаторных фильтров для поэтапного входа в тенденцию. На рабочем таймфрейме используются две «RSI-школы», построенные на разных периодах скользящих средних, а тренд подтверждается фильтром с более высокого таймфрейма. Риски контролируются ATR-ориентированными уровнями стоп-лосса и тейк-профита, а также трейлинг-стопом в пунктах.
Общая информация
- Работает на свечах таймфрейма, выбранного параметром
CandleType (по умолчанию 15 минут).
- Для фильтрации тренда подключается отдельный поток свечей (
FilterCandleType), по умолчанию — часовой.
- Сигналы формируются двумя блоками RSI, которые сравнивают текущее и предыдущее значения и анализируют наклон скользящих средних.
- Допускается наращивание позиции при движении цены, причём после убыточного выхода объём следующей заявки увеличивается по коэффициенту.
Логика входов
Длинные позиции
- Трендовый фильтр опционален. При его включении часовая скользящая средняя должна расти.
- RSI Teacher #1 активен, когда:
- RSI #1 на предыдущей свече был ниже
RsiSellLevel, а на текущей начинает расти.
- Быстрая скользящая средняя увеличивается.
- Часовой RSI (период 14) остаётся ниже
RsiBuyLevel, подтверждая отсутствие перекупленности.
- RSI Teacher #2 активен, когда:
- RSI #2 был ниже
RsiSellLevel2 и поворачивает вверх.
- Медленная скользящая средняя растёт.
- Часовой RSI остаётся ниже
RsiBuyLevel2.
- Лонг открывается, если активен хотя бы один блок и трендовый фильтр (если включён) подтверждает направление.
- Дополнительные покупки допускаются, когда цена прошла от последнего входа больше, чем
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();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class freeman_strategy(Strategy):
def __init__(self):
super(freeman_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast Period", "Fast SMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow Period", "Slow SMA period", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period for filter", "Indicators")
self._rsi_buy_level = self.Param("RsiBuyLevel", 55.0) \
.SetDisplay("RSI Buy Level", "RSI below which buys are allowed", "Levels")
self._rsi_sell_level = self.Param("RsiSellLevel", 45.0) \
.SetDisplay("RSI Sell Level", "RSI above which sells are allowed", "Levels")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@property
def RsiBuyLevel(self):
return self._rsi_buy_level.Value
@property
def RsiSellLevel(self):
return self._rsi_sell_level.Value
def OnReseted(self):
super(freeman_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(freeman_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast_sma = SimpleMovingAverage()
fast_sma.Length = self.FastPeriod
slow_sma = SimpleMovingAverage()
slow_sma.Length = self.SlowPeriod
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_sma, slow_sma, rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_sma)
self.DrawIndicator(area, slow_sma)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value, rsi_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
# MA crossover
if not prev_above and curr_above:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif prev_above and not curr_above:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return freeman_strategy()