using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using TSLab.Script; using TSLab.Script.Control; using TSLab.Script.Control.Elements; using TSLab.Script.Handlers; using TSLab.Script.Optimization; using TSLab.Script.Realtime; namespace DrawdownMonitoring { public class DrawdownMonitoring : IExternalScript { public DateTime PrevTradeDate(ISecurity sec) { var currentDate = sec.Bars[sec.Bars.Count - 1].Date.Date; var i = 0; while (sec.Bars[sec.Bars.Count - 1 - i].Date.Date == currentDate && i < sec.Bars.Count - 1) i++; return sec.Bars[sec.Bars.Count - 1 - i].Date.Date; } public BoolOptimProperty DrawdownMonitoringEnable = new BoolOptimProperty(false, false); public OptimProperty StoreTimeHours = new OptimProperty(18, false, 10, 23, 1, 0); public OptimProperty StoreTimeMinutes = new OptimProperty(44, false, 0, 60, 1, 0); public OptimProperty MaxDrawdown= new OptimProperty(10, false, 0, 100, 1, 0); public void Execute(IContext ctx, ISecurity sec) { #region контрольная панель IControlPane controlPane = ctx.CreateControlPane("Control Pane", "Control Pane", "Control Pane", false); controlPane.Visible = true; IControl controlDrawdownMonitoringEnable = controlPane.AddControl("DrawdownMonitoringEnable", "Включить контроль просадки", ControlParameterType.Checkbox, true, 5D, 20D, double.NaN, double.NaN, false, false, null, DrawdownMonitoringEnable, -1, -16777216); controlDrawdownMonitoringEnable.IsVisible = true; controlDrawdownMonitoringEnable.IsNeedRecalculate = true; IElement TextElement = controlPane.AddTextElement("Время бара для записи баланса", true, 5D, 80D, double.NaN, 24D, -16777216, "Время бара на котором будут записаны данные:"); IControl controlStoreTimeHours = controlPane.AddControl("StoreTimeHours", "Часы", ControlParameterType.NumericUpDown, true, 5D, 100D, double.NaN, double.NaN, false, false, null, StoreTimeHours); controlStoreTimeHours.IsVisible = true; controlStoreTimeHours.IsNeedRecalculate = true; IControl controlStoreTimeMinutes = controlPane.AddControl("StoreTimeMinutes", "минуты", ControlParameterType.NumericUpDown, true, 50D, 100D, double.NaN, double.NaN, false, false, null, StoreTimeMinutes); controlStoreTimeMinutes.IsVisible = true; controlStoreTimeMinutes.IsNeedRecalculate = true; IControl controlMaxDrawdown = controlPane.AddControl("MaxDrawdown", "Максимальная просадка в %", ControlParameterType.NumericUpDown, true, 5D, 160D, double.NaN, double.NaN, false, false, null, MaxDrawdown); controlMaxDrawdown.IsVisible = true; controlMaxDrawdown.IsNeedRecalculate = true; #endregion #region переменные var storeTime = new TimeSpan(StoreTimeHours, StoreTimeMinutes, 00); var secRt = sec as ISecurityRt; DateTime storedDate; var storedBalance = 0.0; #endregion #region запись данных о балансе if (sec.Bars[ctx.BarsCount - 1].Date.TimeOfDay == storeTime) { ctx.StoreObject("LastBalanceQuantity" + secRt.PortfolioName, secRt.EstimatedBalance); ctx.StoreObject("LastBalanceDate" + secRt.PortfolioName, sec.Bars[ctx.BarsCount - 1].Date.Date); } #endregion #region загрузка данных и их проверка //проверяем достаточно ли истории для определения даты предыдущего торгового дня. Нужно чтобы убедиться, что сохранен правильный баланс if (sec.Bars[0].Date.Date == sec.Bars[ctx.BarsCount - 1].Date.Date) { ctx.Log("Недостаточно истории чтобы определить дату предыдущего торгового дня. Контроль просадки отключен.", MessageType.Warning, false); ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, true); return; } var load = ctx.LoadObject("LastBalanceDate" + secRt.PortfolioName); if (load == null || !DateTime.TryParse(load.ToString(), out storedDate)) { ctx.Log("Нет данных о балансе после последнего вечернего клиринга. Контроль просадки отключен.", MessageType.Warning, false); ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, true); return; } if (sec.Bars[ctx.BarsCount - 1].Date.Date != storedDate && sec.Bars[ctx.BarsCount - 1].Date.TimeOfDay > storeTime || PrevTradeDate(sec) != storedDate && sec.Bars[ctx.BarsCount - 1].Date.TimeOfDay < storeTime) { ctx.Log("Данные о балансе не актуальны. Дата записи " + storedDate + ". Контрорль просадки отключен", MessageType.Warning, false); ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, true); return; } load = ctx.LoadObject("LastBalanceQuantity" + secRt.PortfolioName); if (load == null || !double.TryParse(load.ToString(), out storedBalance)) { ctx.Log("Нет данных о балансе после последнего вечернего клиринга. Контроль просадки отключен.", MessageType.Warning, false); ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, true); return; } #endregion if (!DrawdownMonitoringEnable) { ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, true); return; } if (secRt.EstimatedBalance < storedBalance * (1 - (int) MaxDrawdown / 100)) { ctx.Log("Торговля запрещена. Текущая просадка равна " + (storedBalance - secRt.EstimatedBalance)/storedBalance, MessageType.Warning); ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, false); } else { ctx.Log("Торговля разрешена. Текущая просадка равна " + (storedBalance - secRt.EstimatedBalance) / storedBalance, MessageType.Warning); ctx.StoreGlobalObject("IsTradeEnable" + secRt.PortfolioName, true); } } } }