using ScriptSolution; using ScriptSolution.Model; using System; using SourceEts; using ScriptSolution.Model.Interfaces; using SourceEts.Models; using System.Collections.Generic; using System.Drawing; using System.Linq; namespace ExBots { internal class GridContrTrendWithStopNotCalc : Script { public ParamOptimization TypeCalc = new ParamOptimization("%", new List() { "%", "Валюта цены" }, "Расчеты в", "Все значения сетки указываются в соответсвующем формате, кроме количества уровней"); public ParamOptimization StartPriceCalc = new ParamOptimization("Новый день", new List() { "Новый день", "01:00:00", "02:00:00","03:00:00","04:00:00","05:00:00","06:00:00", "07:00:00","08:00:00","09:00:00","10:00:00","11:00:00","12:00:00", "13:00:00","14:00:00","15:00:00","16:00:00","17:00:00","18:00:00", "19:00:00","20:00:00","21:00:00","22:00:00","23:00:00"}, "Цена расчета определяется в", "Цена берется при открытии нового часа или дня, по цене закрытия предыдущего периода. Важно чтоб таймфрейм позволял это определить"); public ParamOptimization StepGrid = new ParamOptimization(1, 50, 100, 10, "Шаг сетки", ""); public ParamOptimization CountLevelLong = new ParamOptimization(3, 2, 5, 1, "Количество уровней лонг", "Максимально отрисуется на графике 10 уровней"); public ParamOptimization OtstupLong = new ParamOptimization(0.2, 2, 5, 1, "Отступ лонг", "Отступ от центрального уровня для лонг"); public ParamOptimization ProfitLong = new ParamOptimization(1.2, 2, 5, 1, "Профит лонг", "Считается от уровня"); public ParamOptimization StopLong = new ParamOptimization(3, 2, 5, 1, "Стоп лонг", "Считается от уровня"); public ParamOptimization CountLevelShort = new ParamOptimization(4, 2, 5, 1, "Количество уровней шорт", "Максимально отрисуется на графике 10 уровней"); public ParamOptimization OtstupShort = new ParamOptimization(0.4, 2, 5, 1, "Отступ шорт", "Отступ от центрального уровня для шорт"); public ParamOptimization ProfitShort = new ParamOptimization(1.5, 2, 5, 1, "Профит шорт", "Считается от уровня"); public ParamOptimization StopShort = new ParamOptimization(4, 2, 5, 1, "Стоп шорт", "Считается от уровня"); double _startPrice = 0; int _barCalc = 0; int _startHour = -1; /// /// Если выбран конкретный час формирования /// /// /// private bool NewPeriod(int bar) { if (TimeSpan.TryParse(StartPriceCalc.ValueString, out var ts)) _startHour = ts.Hours - 1; else return false; int currentHour = Candles.DateTimeCandle[bar].Hour; int nextHour = Candles.DateTimeCandle[bar + 1].Hour; // Возвращаем true, только если текущий час равен нужному и он меньше следующего return currentHour == _startHour && currentHour < nextHour; } public override void Execute() { IsPlotProfitAndStop = false; //Создаем уровни для отрисовки if (_levelsLongLine.Count == 0) { for (int i = 0; i < 10; i++) { _levelsLongLine.Add(new List()); _levelsShortLine.Add(new List()); } } for (var bar = IndexBar; bar < CandleCount - 1; bar++) { if (_barCalc < bar + 1) { _barCalc = bar + 1; while (_levelsLongLine[0].Count - 1 < bar + 1) { int innerBar = Math.Max(_levelsLongLine[0].Count - 1, 0); if (Candles.DateTimeCandle[innerBar].Date != Candles.DateTimeCandle[innerBar + 1].Date && StartPriceCalc.ValueString == "Новый день" || NewPeriod(innerBar)) { _startPrice = Candles.CloseSeries[innerBar]; CreateLevels(); } DrawLineLevel(innerBar); } } ParamDebug("Цена для расчета уровней", _startPrice); if (_levelsLong.Count != CountLevelLong.ValueInt) { for (int i = LongPos.Count - 1; i > CountLevelLong.ValueInt - 1; i--) { KillOrderOpen(0, LongPos[0], "Уменьшение"); } } if (_levelsShort.Count != CountLevelLong.ValueInt) { for (int i = ShortPos.Count - 1; i > CountLevelShort.ValueInt - 1; i--) { KillOrderOpen(0, ShortPos[0], "Уменьшение"); } } if (Service.TypeTorg != EnumTradingType.Long) { CloseShortProfit(bar); for (int i = _levelsShort.Count - 1; i >= 0; i--) { var level = _levelsShort[i]; if (level.isExitStop) continue; if (level.Pos != null && level.Pos.EntryOrderStatus == EnumOrderStatus.Performed && level.Pos.ExitOrderStatus == EnumOrderStatus.Performed) level.Pos = null; level.Pos = ShortAtLimit(bar + 1, level.LevelPrice, 1, "Short " + (i + 1).ToString() + " цена=" + level.LevelPrice, new AddInfoToOrder { IgnoreGapForOpenLimit = true }); } CloseShortProfit(bar); //for (int i = ShortPos.Count - 1; i >= 0; i--) //{ // if (ShortPos[i].Info != "Стоп") // CoverAtProfit(bar + 1, ShortPos[i], SetLelelMinStep(Convert.ToDouble(ShortPos[i].EntryPrice) - GetValue(ShortPos[i].EntryPrice, ProfitShort.Value)), "Profit"); //} for (int i = ShortPos.Count - 1; i >= 0; i--) { var level = _levelsShort.FirstOrDefault(a => Math.Abs(a.LevelPrice - ShortPos[i].EntryPrice) < 0.0000001); if (level != null && level.Pos == null) continue; var stop = SetLelelMinStep(ShortPos[i].EntryPrice + GetValue(ShortPos[i].EntryPrice, StopShort.Value)); if (stop <= Candles.HighSeries[bar + 1] && String.IsNullOrEmpty(ShortPos[i].Info)) { ShortPos[i].Info = "Стоп"; } if (ShortPos[i].Info == "Стоп" && ShortPos[i].ExitOrderStatus == EnumOrderStatus.None || level != null && level.isExitStop) { if (level != null) level.isExitStop = true; CoverAtStop(bar + 1, ShortPos[i], stop, "Стоп уровня", new AddInfoToOrder { IsAlwaysClose = true }); } else { //Переда закрытие по стопу снимаем активный профит KillOrderClose(0, ShortPos[i], "Будет закрытие по стопу"); } } } if (Service.TypeTorg != EnumTradingType.Short) { if (bar == 9932) { } CloseLongProfit(bar); // т.к. могут во время теста на минутках закрытие на открытии свечи for (int i = 0; i < _levelsLong.Count; i++) { var level = _levelsLong[i]; if (level.isExitStop) continue; if (level.Pos != null && level.Pos.EntryOrderStatus == EnumOrderStatus.Performed && level.Pos.ExitOrderStatus == EnumOrderStatus.Performed) { level.Pos = null; } level.Pos = BuyAtLimit(bar + 1, level.LevelPrice, 1, "Long " + (i + 1).ToString() + " цена=" + level.LevelPrice, new AddInfoToOrder { IgnoreGapForOpenLimit = true }); } CloseLongProfit(bar); //// т.к. могут во время теста свеча быть растущей и нужно будет сразу закрывать for (int i = LongPos.Count - 1; i >= 0; i--) { var level = _levelsLong.FirstOrDefault(a => Math.Abs(a.LevelPrice - LongPos[i].EntryPrice) < 0.0000001); if (level != null && level.Pos == null) continue; var stop = SetLelelMinStep(LongPos[i].EntryPrice - GetValue(LongPos[i].EntryPrice, StopLong.Value)); if (stop >= Candles.LowSeries[bar + 1] && String.IsNullOrEmpty(LongPos[i].Info)) { LongPos[i].Info = "Стоп"; } if (LongPos[i].Info == "Стоп" && LongPos[i].ExitOrderStatus == EnumOrderStatus.None || level != null && level.isExitStop) { if (level != null) level.isExitStop = true; SellAtStop(bar + 1, LongPos[i], stop, "Стоп уровня", new AddInfoToOrder { IsAlwaysClose = true }); } else { //Переда закрытие по стопу снимаем активный профит KillOrderClose(0, LongPos[i], "Будет закрытие по стопу"); } } } if (LongPos.Count != 0 || ShortPos.Count != 0) { SendTimePosCloseFromForm(bar + 1, ""); SendClosePosOnRiskFromForm(bar + 1, ""); } } } private void CloseLongProfit(int bar) { for (int i = LongPos.Count - 1; i >= 0; i--) { double profit = SetLelelMinStep(LongPos[i].EntryPrice + GetValue(LongPos[i].EntryPrice, ProfitLong.Value)); if (LongPos[i].Info != "Стоп" && (Service.IsRealAction || bar + 1 > LongPos[i].EntryBar || !Service.IsRealAction && (Service.TypeTimeFrame == EnumTimeFrame.Tick || Service.TypeTimeFrame == EnumTimeFrame.Second) || !Service.IsRealAction && (Service.TypeTimeFrame != EnumTimeFrame.Tick || Service.TypeTimeFrame != EnumTimeFrame.Second) && bar + 1 == LongPos[i].EntryBar && Candles.CloseSeries[bar + 1] >= profit)) //В сулчае тестирования на минутках и выше, что не закрывал в профит на шортовой свече SellAtProfit(bar + 1, LongPos[i], profit, "Profit", new AddInfoToOrder { IgnoreGapForOpenLimit = true }); } } private void CloseShortProfit(int bar) { for (int i = ShortPos.Count - 1; i >= 0; i--) { double profit = SetLelelMinStep(ShortPos[i].EntryPrice - GetValue(ShortPos[i].EntryPrice, ProfitShort.Value)); if (ShortPos[i].Info != "Стоп" && (Service.IsRealAction || bar + 1 > ShortPos[i].EntryBar || !Service.IsRealAction && (Service.TypeTimeFrame == EnumTimeFrame.Tick || Service.TypeTimeFrame == EnumTimeFrame.Second) || !Service.IsRealAction && (Service.TypeTimeFrame != EnumTimeFrame.Tick || Service.TypeTimeFrame != EnumTimeFrame.Second) && bar + 1 == ShortPos[i].EntryBar && Candles.CloseSeries[bar + 1] <= profit)) //В сулчае тестирования на минутках и выше, что не закрывал в профит на шортовой свече CoverAtProfit(bar + 1, ShortPos[i], profit, "Profit", new AddInfoToOrder { IgnoreGapForOpenLimit = true }); } } /// /// Списки значений уровней для отрисовки их на графике /// List> _levelsLongLine = new List>(); List> _levelsShortLine = new List>(); /// /// Отрисовываем на графике. Максимум по 10 уровней будет для каждого направления /// /// private void DrawLineLevel(int bar) { if (_levelsLongLine[0].Count - 1 < bar + 1) //чтоб не бегал на одной и той свече по этим циклам { while (_levelsLongLine[0].Count <= bar + 1) { for (int i = 0; i < 10; i++) { _levelsLongLine[i].Add(Double.NaN); _levelsShortLine[i].Add(Double.NaN); } } if (Service.TypeTorg != EnumTradingType.Long) { int count = Math.Min(_levelsShortLine.Count, _levelsShort.Count); for (int i = count - 1; i >= 0; i--) { _levelsShortLine[i][^1] = _levelsShort[i].LevelPrice; Plotseries("Уровень шорт " + (i + 1), _levelsShortLine[i], new UserChartPropModel { ColorLine = Color.MediumOrchid }); ParamDebug("Уровень шорт " + (i + 1), _levelsShort[i].LevelPrice, bar + 1); } } if (Service.TypeTorg != EnumTradingType.Short) { int count = Math.Min(_levelsLongLine.Count, _levelsLong.Count); for (int i = 0; i < count; i++) { _levelsLongLine[i][^1] = _levelsLong[i].LevelPrice; Plotseries("Уровень лонг " + (i + 1), _levelsLongLine[i], new UserChartPropModel { ColorLine = Color.SkyBlue }); ParamDebug("Уровень лонг " + (i + 1), _levelsLong[i].LevelPrice, bar + 1); } } } } List _levelsLong = new List(); List _levelsShort = new List(); /// /// Создаем уровни /// /// private void CreateLevels() { if (_startPrice < 0.0000001) return; _levelsLong.Clear(); _levelsShort.Clear(); for (int i = 0; i < CountLevelLong.ValueInt; i++) { _levelsLong.Add(new Level { LevelPrice = SetLelelMinStep(Math.Round(_startPrice - GetValue(_startPrice, OtstupLong.Value) - GetValue(_startPrice, StepGrid.Value * i), FinInfo.Security.Accuracy)) }); } for (int i = 0; i < CountLevelShort.ValueInt; i++) { _levelsShort.Add(new Level { LevelPrice = SetLelelMinStep(Math.Round(_startPrice + GetValue(_startPrice, OtstupShort.Value) + GetValue(_startPrice, StepGrid.Value * i), FinInfo.Security.Accuracy)) }); } } /// /// Устанавливаем кратность шагу цены /// /// /// private double SetLelelMinStep(double level) { return Math.Round(Math.Round(level / FinInfo.Security.MinStep) * FinInfo.Security.MinStep, FinInfo.Security.Accuracy); } /// /// Получаем значения, с рассчетом процентов /// Возможно дальше будет использовать и для обычных пунктов /// /// /// /// private double GetValue(double price, double value) { return TypeCalc.ValueString == "%" ? Math.Round(price / 100 * value, FinInfo.Security.Accuracy) : Math.Round(value, FinInfo.Security.Accuracy); } /// /// Класс для хранения уровней /// private class Level { /// /// Уровень /// public double LevelPrice { get; set; } /// /// Был ли выход по стопу за текущий день /// Если был, то сегодня этот уровень не работает /// public bool isExitStop { get; set; } /// /// Позиция по уровню /// public IPosition Pos { get; set; } } public override void SetSettingDefault() { DefaultParamModel.TypeTorg = EnumTradingType.LongAndShort; DefaultParamModel.IsNotCalculationScript = true; DefaultParamModel.Timeframe = 1; DefaultParamModel.TypeTimeframe = EnumTimeFrame.Minute; DefaultParamModel.IsCollapseStops = true; DefaultParamModel.OrderOpenCloseType.OpenManagmentOrders.IsPhysicalOrder = true; DefaultParamModel.OrderOpenCloseType.OpenManagmentOrders.IsLimitOrderOrder = true; DefaultParamModel.OrderOpenCloseType.OpenManagmentOrders.OrderType = EnumTypeOrder.Limit; DefaultParamModel.OrderOpenCloseType.CloseManagmentOrders.IsPhysicalOrder = true; DefaultParamModel.OrderOpenCloseType.CloseManagmentOrders.IsLimitOrderOrder = true; DefaultParamModel.OrderOpenCloseType.CloseManagmentOrders.OrderType = EnumTypeOrder.Limit; } public override void GetAttributesStratetgy() { DesParamStratetgy.Version = "5"; DesParamStratetgy.DateRelease = new DateTime(2025, 05, 17); DesParamStratetgy.DateChange = new DateTime(2025, 06, 5); DesParamStratetgy.Author = "Дернов Юрий"; DesParamStratetgy.Description = ""; DesParamStratetgy.Change = ""; DesParamStratetgy.TypeStrategy = EnumStrategy.Grid; DesParamStratetgy.NameStrategy = "Сетка конт. со стопами без пересчета"; } } }