Fly System Scalp — это адаптация советника FlySystemEA на платформу StockSharp. Стратегия постоянно отслеживает лучшие цены Bid/Ask и выставляет симметричные стоп-ордера Buy Stop и Sell Stop на фиксированном расстоянии от рынка. Цель — быстро реагировать на импульсные движения, сохраняя строгий контроль над спредом, комиссиями и торговым расписанием.
Логика работы
Подписка на Level-1. Стратегия получает обновления лучшего предложения и спроса по инструменту.
Проверки перед действиями. На каждом тике выполняются проверки:
стратегия подключена и разрешено выставление заявок;
текущий момент входит в торговый интервал (если фильтр включён);
спред плюс значение CommissionInPips не превышает параметр MaxSpread.
Размещение стоп-ордеров. При выполнении условий и отсутствии открытых позиций выставляются два ордера:
Buy Stop на уровне Ask + PendingDistance * pip с защитным стоп-лоссом и опциональным тейк-профитом;
Sell Stop на Bid - PendingDistance * pip с зеркальными параметрами.
Если актуальная цена отличается от требуемой более чем на ModifyThreshold пунктов, ордера перерегистрируются.
Сопровождение. При исполнении одного ордера противоположный немедленно отменяется. Если спред или время выходят за допустимые границы, все отложенные заявки удаляются и цикл ожидания начинается заново.
Управление объёмом. При включённом AutoLotSize объём рассчитывается как доля (RiskFactor%) от капитала, делённая на возможный убыток по стоп-лоссу. При отсутствии данных брокера используется фиксированный ManualVolume.
Защитные механизмы. Вызов StartProtection() подключает встроенную защиту StockSharp для контроля позиции.
Параметры
Название
Описание
Значение по умолчанию
PendingDistance
Расстояние до рынка в пунктах для обоих стоп-ордеров.
4
StopLossDistance
Размер стоп-лосса в пунктах.
0.4
TakeProfitDistance
Размер тейк-профита в пунктах.
10
UseTakeProfit
Включение/отключение тейк-профита.
false
MaxSpread
Максимально допустимый спред (0 — без ограничения).
1
CommissionInPips
Комиссия в пунктах, добавляемая к спреду при проверке.
0
AutoLotSize
Автоматический расчёт объёма.
false
RiskFactor
Процент капитала для расчёта риска.
10
ManualVolume
Фиксированный объём без автолота.
0.1
UseTimeFilter
Использовать ли торговый интервал.
false
TradeStartTime
Начало торгового интервала (включительно).
00:00:00
TradeStopTime
Конец торгового интервала (исключая).
00:00:00
ModifyThreshold
Минимальное отклонение в пунктах для перерегистрации ордера.
1
Рекомендации по использованию
Для автоматического расчёта объёма требуются корректные свойства инструмента: Step, PriceStep, StepPrice, LotStep, MinVolume, MaxVolume. При отсутствии данных стратегия использует ManualVolume.
Размер пункта определяется по шагу цены и количеству знаков, что соответствует логике исходного советника (учёт трёх- и пятизнаковых котировок).
Если TradeStartTime и TradeStopTime совпадают при включённом фильтре, торговля разрешена круглосуточно. При TradeStartTime > TradeStopTime диапазон считается переходящим через полночь.
Проверка спреда суммирует фактический спред и CommissionInPips, что эквивалентно оригинальной реализации.
Визуальные объекты не создаются — отображение можно организовать отдельными инструментами.
Отличия от оригинала
Удалены таймер и графические элементы, присутствовавшие в MQL-версии; управление построено на событиях StockSharp.
Логика модификации ордеров упрощена: перерегистрация выполняется при превышении порога ModifyThreshold.
Автоопределение комиссий заменено на ручной параметр, но контроль спреда по-прежнему учитывает комиссию.
Вместо ручного контроля стоп-уровней используется стандартный метод StartProtection().
Советы по тестированию
Для корректного бэктеста необходимы исторические данные уровня 1 (bid/ask) либо реконструированный поток котировок из тиков. Только свечные данные не позволят корректно смоделировать исполнение стоп-ордеров.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fly System Scalp strategy: EMA crossover + RSI confirmation.
/// Buys when close crosses above EMA and RSI confirms.
/// Sells when close crosses below EMA and RSI confirms.
/// </summary>
public class FlySystemScalpStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _rsiPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
public FlySystemScalpStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA period", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
decimal? prevClose = null;
decimal? prevEma = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, rsi, (candle, emaVal, rsiVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (prevClose.HasValue && prevEma.HasValue)
{
var crossUp = prevClose.Value <= prevEma.Value && close > emaVal;
var crossDown = prevClose.Value >= prevEma.Value && close < emaVal;
if (crossUp && rsiVal < 55m && Position <= 0)
BuyMarket();
else if (crossDown && rsiVal > 45m && Position >= 0)
SellMarket();
}
prevClose = close;
prevEma = emaVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class fly_system_scalp_strategy(Strategy):
"""
Fly System Scalp: EMA crossover + RSI confirmation.
Buys when close crosses above EMA and RSI < 55.
Sells when close crosses below EMA and RSI > 45.
"""
def __init__(self):
super(fly_system_scalp_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA period", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI period", "Indicators")
self._prev_close = None
self._prev_ema = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fly_system_scalp_strategy, self).OnReseted()
self._prev_close = None
self._prev_ema = None
def OnStarted2(self, time):
super(fly_system_scalp_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_val, rsi_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema = float(ema_val)
rsi = float(rsi_val)
if self._prev_close is not None and self._prev_ema is not None:
cross_up = self._prev_close <= self._prev_ema and close > ema
cross_down = self._prev_close >= self._prev_ema and close < ema
if cross_up and rsi < 55 and self.Position <= 0:
self.BuyMarket()
elif cross_down and rsi > 45 and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_ema = ema
def CreateClone(self):
return fly_system_scalp_strategy()