using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using TSLab.Script.Handlers; using TSLab.Script.Handlers.Options; using static GanovCubes.ExtTrade; namespace GanovCubes { public enum ClientType { КСУР, КПУР }; #region CubeDescription [HandlerCategory("Ganov Cubes. MarketData")] [HelperName("FinamRiskData ", Language = "ru-RU")] [Description("Кубик собирает данные с сайта \"Финам\" по ставке мин.риска и мин.резерва" + " Кубик НЕ требует подключение к инструменту, делает запрос один раз за пересчет. Все полученные данные автоматически записываются в глобальный" + " кеш и файл \"FinamRiskData.csv\". (aайл с данными располагается в папке:" + @"C: \Users\UserName\AppData\Local\TSLab\TSLab 2.0\DataStorage\MarketData\" + "). Кубик парсит только текущий формат страницы, при изменении формата страницы работоспособность кубика будет нарушена," + " о чем будет выведено сообщение в лог, которое при необходимости может быть настроено для отправки в телеграмм средствами платформы. В случае, если не" + " удается загрузить информацию с сайта, то будет использована ранее полученная информация из вышеуказанного файла" + "\nКубик получает и сохраняет следующие данные:" + "\n=======================" + "\n--Ставка риска начальной маржи для лонга" + "\n--Ставка риска начальной маржи для шорта" + "\n--Ставка резерва начальной маржи для лонга" + "\n--Ставка резерва начальной маржи для лонга")] [HelperLink("http://forum.tslab.ru/ubb/ubbthreads.php?ubb=showflat&Number=87071&page=1", "Страница на форуме TSLab", "ru-ru")] [InputsCount(0)] [OutputsCount(0)] #endregion public class FinamRiskData : IHandler, IZeroSourceHandler, IContextUses, INeedVariableId, INeedVariableName { #region Properties public IContext Context { get; set; } public string VariableId { get; set; } public string VariableName { get; set; } /// /// Тип клиента по риску /// [HandlerParameter(true, "КПУР", NotOptimized = false, Name = "Тип клиента по риску", IsVisibleInBlock = false)] [Description("Выбор одного из двух типов обозначается адрес страницы, с которой будут парситься данные (КСУР или КПУР). По умолчанию в качестве адреса " + " используется страница для клиентов с увеличенным уровнем риска: https://www.finam.ru/documents/commissionrates/marginal/kpur")] public ClientType ClientRiskType { get; set; } #endregion public async void Execute() { if (!Context.Runtime.IsAgentMode) return; await Task.Run(() => GetRiskData()); } /// /// Метод получает данные по риску и резерву /// private void GetRiskData() { try { var url = ClientRiskType == ClientType.КПУР ? "https://www.finam.ru/documents/commissionrates/marginal/kpur" : "https://www.finam.ru/documents/commissionrates/marginal/ksur"; Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var webClient = new WebClient { Encoding = Encoding.GetEncoding("windows-1251") }; var ans = webClient.DownloadString(url); var riskData = ParseRiskData(ans); if (riskData.Count > 0) StoreRiskData(riskData); } catch (Exception e) { Context.Log($"FinamMinRisk: Произошла ошибка при получении данных с сайта: {e.Message}", MessageType.Error, true); var riskData = LoadDataFromFile(); if (riskData.Count > 0) { StoreRiskData(riskData, false); Context.Log($"FinamMinRisk: Данные восстановлены из файла: {GetFileName()}", MessageType.Info, true); } } } /// /// Метод парсит таблицу с данными по начальной марже и резерву /// private Dictionary ParseRiskData(string ans) { var retValue = new Dictionary(); var startPos = ans.IndexOf("Ставки риска для клиентов"); if (startPos == -1) return retValue; var tableStart = ans.IndexOf("", startPos) + 8; var tableEnd = ans.IndexOf("", tableStart); var tableBody = ans.Substring(tableStart, tableEnd - tableStart).Replace("\n\t\t\t\t", "").Replace("\r\t", ""); startPos = 0; var delimeter = '^'; while (tableBody.IndexOf("", startPos) != -1) { var rowStart = tableBody.IndexOf("", startPos) + 4; var rowEnd = tableBody.IndexOf("", startPos); var rowBody = tableBody.Substring(rowStart, rowEnd - rowStart); int openCounter = 0; int closeCounter = 0; var sb = new StringBuilder(); foreach (var item in rowBody) { if (item == '<') openCounter++; if (item == '>') closeCounter++; if (openCounter == closeCounter && openCounter % 2 != 0) sb.Append(item); if (openCounter == closeCounter && openCounter % 2 == 0) sb.Append(delimeter); } var resString = sb.ToString().Replace(">", "").Replace("%", ""); var resData = resString.Remove(resString.Length - 1).Split(delimeter); var riskAndReserveData = new string[] { resData[8], resData[9], resData[10], resData[11] }; if (!riskAndReserveData.Any(el => !double.TryParse(el, out _))) retValue.Add(resData[4], riskAndReserveData); startPos = rowEnd + 5; } if (retValue.Count == 0) Context.Log($"FinamMinRisk: Не удалось распарсить данные с сайта", MessageType.Error, true); return retValue; } /// /// Метод принимает словарь данных по риску и сохраняет данные в файл и /// /// private void StoreRiskData(Dictionary riskData, bool writeToFile = true) { var sb = new StringBuilder(); sb.AppendLine("Ticket,RiskLong,RiskShort,ReserveLong,ReserveShort"); foreach (var item in riskData) sb.AppendLine($"{item.Key},{string.Join(",", item.Value)}"); Context.StoreGlobalObject("FinamMinRisk", riskData, true); if (writeToFile) try { var file = GetFileName(); File.WriteAllText(file, sb.ToString()); } catch (Exception e) { Context.Log($"FinamMinRisk: Невозможно записать данные в файл {GetFileName()}, причина: {e.Message}", MessageType.Error, true); } } /// /// Метод загружает данные из файл в случае если неудалось получить данные с сайта /// private Dictionary LoadDataFromFile() { var retvlaue = new Dictionary(); try { var file = GetFileName(); var riskData = File.ReadAllLines(file); for (int i = 1; i < riskData.Length; i++) if (!string.IsNullOrEmpty(riskData[i]) && !string.IsNullOrWhiteSpace(riskData[i])) { var row = riskData[0].Split(','); if (!retvlaue.ContainsKey(row[0])) retvlaue.Add(row[0], row.Skip(1).ToArray()); } } catch (Exception e) { Context.Log($"FinamMinRisk: Произошла ошибка при чтении файла {e.Message}", MessageType.Error, true); ; } return retvlaue; } /// /// Метод возвращает имя файла для записи/чтения включая адрес /// /// Имя файла включая адрес private string GetFileName() { var directory = $@"{GetTSLabAddDataDirectory()}MarketData\"; if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); return $"{directory}FinamRiskData.csv"; } } }