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";
}
}
}