Сегодня будем смотреть пример, в котором трейлинг стоп по позиции подтягивается по ленте сделок, а сама позиция открывается из события завершения свечи.
На графике это выглядит так:

На гитХаб исходник примера находится здесь:
https://github.com/AlexWan/OsEngine
Внутри проекта:

2. Конструктор и сервисный код.
КОД
private BotTabSimple _tab;
// Basic settings
private StrategyParameterString _regime;
private StrategyParameterInt _slippage;
// GetVolume settings
private StrategyParameterString _volumeType;
private StrategyParameterDecimal _volume;
private StrategyParameterString _tradeAssetInPortfolio;
// Indicator setting
private StrategyParameterInt _indLength;
// Indicator
private Aindicator _pc;
// Exit setting
private StrategyParameterDecimal _trailStopPercent;
public StopByTradeFeedSample(string name, StartProgram startProgram) : base(name, startProgram)
{
TabCreate(BotTabType.Simple);
_tab = TabsSimple[0];
// Basic settings
_regime = CreateParameter("Regime", "Off", new[] { "Off", "On", "OnlyLong", "OnlyShort", "OnlyClosePosition" });
_slippage = CreateParameter("Slippage in price step", 0, 0, 20, 1);
_indLength = CreateParameter("Price channel length", 10, 10, 80, 3);
// GetVolume settings
_volumeType = CreateParameter("Volume type", "Deposit percent", new[] { "Contracts", "Contract currency", "Deposit percent" });
_volume = CreateParameter("Volume", 10, 1.0m, 50, 4);
_tradeAssetInPortfolio = CreateParameter("Asset in portfolio", "Prime");
// Exit setting
_trailStopPercent = CreateParameter("Trail stop percent", 0.2m, 0.5m, 5, 4);
// Create indicator PriceChannel
_pc = IndicatorsFactory.CreateIndicatorByName("PriceChannel", name + "PriceChannel", false);
_pc = (Aindicator)_tab.CreateCandleIndicator(_pc, "Prime");
_pc.ParametersDigit[0].Value = _indLength.ValueInt;
_pc.ParametersDigit[1].Value = _indLength.ValueInt;
_pc.Save();
// Subscribe to the candle finished event
_tab.CandleFinishedEvent += _tab_CandleFinishedEvent;
// Subscribe to the new tick event
_tab.NewTickEvent += _tab_NewTickEvent;
// Subscribe to the indicator update event
ParametrsChangeByUser += Event_ParametrsChangeByUser;
Description = OsLocalization.Description.DescriptionLabel108;
}
КОНЕЦ КОДА
В картинке:

1. Поле для хранения источника BotTabSimple. В это поле потом мы положим объект источника и сможем обращаться к нему из любой части робота. Так удобнее.
2. Поле для хранения индикатора.
3. Параметры. Чуть позже поговорим про каждый.
4. Создание источника BotTabSimple.
5. Создание параметров.
6. Создание индикатора PriceChannel. Установка его длины и отклонения из параметров.
7. Подписка на событие CandleFinishedEvent (завершение свечи). В обработчике этого события логика открытия позиции.
8. Подписка на событие NewTickEvent (новый трейд в ленте сделок). В обработчике этого события будет подтягивание стопа.
9. Подписка на событие изменение параметров. В этом обработчике мы будем устанавливать индикатору новое значение длины.
Создание параметров происходит в конструкторе робота:
КОД
// Basic settings
_regime = CreateParameter("Regime", "Off", new[] { "Off", "On", "OnlyLong", "OnlyShort", "OnlyClosePosition" });
_slippage = CreateParameter("Slippage in price step", 0, 0, 20, 1);
_indLength = CreateParameter("Price channel length", 10, 10, 80, 3);
// GetVolume settings
_volumeType = CreateParameter("Volume type", "Deposit percent", new[] { "Contracts", "Contract currency", "Deposit percent" });
_volume = CreateParameter("Volume", 10, 1.0m, 50, 4);
_tradeAssetInPortfolio = CreateParameter("Asset in portfolio", "Prime");
// Exit setting
_trailStopPercent = CreateParameter("Trail stop percent", 0.2m, 0.5m, 5, 4);
КОНЕЦ КОДА
В окне параметров это выглядит так:

За что отвечают параметры:
1. Regime – режим работы.
1. Off – Выключен.
2. On – включено без ограничений.
3. OnlyLong – открытие только длинных позиций.
4. OnlyShort – открытие только коротких позиций.
5. OnlyClosePosition – доступно только закрытие позиций.
2. Slippage in price step – проскальзывание для открытия и закрытия позиций в шагах цены.
3. Price channel length – длина индикатора PriceChannel.
4. Trail stop percent – величина трейлинг стопа для позиций в процентах.
5. Volume type – режим выбора объёма.
1. Contracts – кол-во контрактов инструмента.
2. Contract currency – валюта контракта.
3. Deposit percent – процент от депозита.
6. Volume – значение объёма. Что именно, зависит от предыдущего пункта. В случае Contracts тут указывается объём инструмента. В случае Contract currency здесь указывается кол-во рублей или долларов, которыми нужно войти. В случае с Deposit percent здесь указывается % от общего депозита, которым нужно войти в контракт.
7. Asset in portfolio – тут нужно указывать название валюты, которое будет использовано для расчёта объёма, если Вы выбрали тип объёма “Deposit percent”. В тестере оставляем «Prime». На крипте это обычно “USDT”.
4. Вход в логику в событии завершения свечи. Открытие позиции.
КОД
private void _tab_CandleFinishedEvent(List<Candle> candles)
{
if (Regime.ValueString == "Off")
{
return;
}
if (_pc.DataSeries[0].Values == null
|| _pc.DataSeries[1].Values == null)
{
return;
}
if (_pc.DataSeries[0].Values.Count < _pc.ParametersDigit[0].Value + 2
|| _pc.DataSeries[1].Values.Count < _pc.ParametersDigit[1].Value + 2)
{
return;
}
if (Regime.ValueString == "OnlyClosePosition")
{
return;
}
List<Position> openPositions = _tab.PositionsOpenAll;
if (openPositions == null
|| openPositions.Count == 0)
{// no positions
decimal lastPrice = candles[candles.Count - 1].Close;
decimal lastPcUp = _pc.DataSeries[0].Values[_pc.DataSeries[0].Values.Count - 2];
decimal lastPcDown = _pc.DataSeries[1].Values[_pc.DataSeries[1].Values.Count - 2];
// long
if (Regime.ValueString != "OnlyShort")
{
if (lastPrice > lastPcUp)
{
_tab.BuyAtLimit(GetVolume(_tab), lastPrice + Slippage.ValueInt * _tab.Security.PriceStep);
}
}
// Short
if (Regime.ValueString != "OnlyLong")
{
if (lastPrice < lastPcDown)
{
_tab.SellAtLimit(GetVolume(_tab), lastPrice - Slippage.ValueInt * _tab.Security.PriceStep);
}
}
}
}
КОНЕЦ КОДА

1. Если параметр Regime в положении Off, выходим из метода.
2. Если данные по индикатору не начали формироваться, выходим из метода.
3. Если кол-во данных в индикаторе меньше, чем длинна, указанная для его расчёта, выходим из метода.
4. Если параметр Regime в положении OnlyClosePosition, выходим из метода.
5. Запрашиваем текущие открытые позиции.
6. Заходим в логику открытия позиции, только если позиций нет.
7. Запрашиваем переменные, нужные для дальнейших расчётов.
8. Входим в позицию лонг, если цена пробила ценовой канал вверх.
9. Входим в позицию шорт, если цена пробила ценовой канал вниз.
КОД
private void _tab_NewTickEvent(Trade trade)
{
if (_regime.ValueString == "Off")
{
return;
}
List<Position> openPositions = _tab.PositionsOpenAll;
if (openPositions == null
|| openPositions.Count == 0)
{
return;
}
Position myPos = openPositions[0];
if(myPos.State != PositionStateType.Open)
{
return;
}
decimal stopPrice = 0;
decimal orderPrice = 0;
if (myPos.Direction == Side.Buy)
{
stopPrice = trade.Price - (trade.Price * (_trailStopPercent.ValueDecimal/100));
orderPrice = stopPrice - _slippage.ValueInt * _tab.Security.PriceStep;
}
else if(myPos.Direction == Side.Sell)
{
stopPrice = trade.Price + (trade.Price * (_trailStopPercent.ValueDecimal / 100));
orderPrice = stopPrice + _slippage.ValueInt * _tab.Security.PriceStep;
}
_tab.CloseAtTrailingStop(myPos,stopPrice,orderPrice);
}
КОНЕЦ КОДА

1. Если параметр Regime в положении Off, выходим из метода.
2. Запрашиваем текущие открытые позиции.
3. Если открытых позиций нет, выходим из метода.
4. Берём открытую позицию из массива всех открытых позиций.
5. Если статус позиции не равен Open, т.е. она ещё не до конца открыта или уже закрывается, ничего не делаем.
6. Создаём переменные для стоп уровня и цены выставления ордера.
7. Рассчитываем цену стопа и ордера для него для позиции типа BUY (лонг).
8. Рассчитываем цену стопа и ордера для него для позиции типа SELL (шорт).
9. Вызываем у источника метод для перевыставления трейлинг стопа.
Удачных алгоритмов!
P.S.
Os Engine поддержка терминала: https://t.me/osengine_official_support
Обсуждаем в телеграмме: https://t.me/o_s_a_chat
Канал проекта: https://t.me/bad_quant
Комментарии