using System;
using System.ComponentModel;
using System.IO;
using TSLab.Script.Handlers;
using TSLab.Script.Handlers.Options;
using static GanovCubes.ExtTrade;
using static GanovCubes.FileLogger;
namespace GanovCubes
{
#region CubeDescription
[HandlerCategory("Ganov Cubes. Different")]
[HelperName("BalanceControl", Language = "ru-RU")]
[Description("Кубик работает только в режиме агента, в режиме лаборатории всегда возвращается ноль. Данный кубик получает данные со входа, сравнивает их суммой," +
" полученной на прошлом пересчете и если разница составляет более указанного процента, то возвращается ранее полученную сумму. Таким образом, предотвращается" +
" передача в систему существенно неверных данных, присутствие которых может привести к покупке активов на сумму, выходящую за имеющиеся свободные средства." +
" Кубик рекомендуется запускать в отдельном агенте с пересчетом по метроному и передавать данные в другие агенты через механизм глобального кеша, таким образом, " +
" у всех агентов будет актуальная и отфильтрованная информация о текущем балансе счета. Кубик для хранения прошлых данных использует локальный кеш и файл на диске" +
" по указанном пути. Обращение к файлу происходит только в случае если кеш пустой, в остальных случаях кубик использует кеш. В файл данные" +
" записываются 1 раз в 5 минут (настройкой \"периодичность записи в файл\" можно изменить интервал перезаписи). Данные сохраняются в файл, расположенный в папке:" +
@"C: \Users\UserName\AppData\Local\TSLab\TSLab 2.0\DataStorage\AccountData\")]
[InputsCount(1)]
[Input(0, TemplateTypes.DOUBLE, Name = "Balance")]
[OutputsCount(1)]
[OutputType(TemplateTypes.DOUBLE)]
#endregion
public class BalanceControl : IValuesHandler, IDoubleInput0, IDoubleReturns, IContextUses
{
#region Fields
double _retValue = 0;
string _filePath;
#endregion
#region Properties
public IContext Context { get; set; }
///
/// ID Контроллера
///
[HandlerParameter(true, "ID#", NotOptimized = true, Name = "ID Контроллера", IsVisibleInBlock = false)]
[Description("Произвольный текст. ID необходимо указывать в случае если в рамках одного скрипта работает два и" +
" более контроллера баланса, т.к. они оба используют локальный кеш, то при наличии одной и той же метки" +
" будут перетирать данные друг друга")]
public string ID { get; set; } = "ID#";
///
/// % несоответствия баланса
///
[HandlerParameter(true, "50", NotOptimized = true, Name = "% несоответствия баланса", IsVisibleInBlock = false)]
[Description("Процент, на который различается текущий и получаемый на вход баланс. В случае если разница больше" +
" указанного процента, то система использует ранее сохраненный баланс")]
public double BalanceDifferencePct { get; set; } = 50;
///
/// Писать в файл
///
[HandlerParameter(true, "true", NotOptimized = true, Name = "Писать в файл", IsVisibleInBlock = false)]
[Description("При установке данной опции с указанной периодичностью (по умолчанию 5 минут) данные по входной величине будут" +
" писаться в файл, откуда будут браться в случае если в локальном кеше нет данных или данные равны нулю. Данная настройка" +
" по сути является избыточной, т.к.у локального кеша есть функционал сохранения на диск даже при отключении ТСЛаб, но когда" +
" вопрос стоит о деньгах, то лишняя \"страховка\" не помешает. Если такая необходимость отсутствует, то данную настройку" +
" можно отключить. По умолчанию настройка включена")]
public bool WriteToFile { get; set; } = true;
///
/// Период перезаписи файла
///
[HandlerParameter(true, "5", NotOptimized = true, Name = "Период перезаписи файла", IsVisibleInBlock = false)]
[Description("Время в минутах между перезаписью данных в файл")]
public double FileWriteInterval { get; set; } = 5;
///
/// Писать данные в лог-файл
///
[HandlerParameter(true, "false", NotOptimized = true, Name = "Писать данные в лог-файл", IsVisibleInBlock = false)]
[Description("При установленной опции система будет писать данные по получаемому из вне, из кеша и выдаваемому на выход значению, что" +
" позволяет отследить работу кубика в динамике. По умолчанию отключено")]
public bool WriteToLog { get; set; } = false;
#endregion
public double Execute(double accBalance, int bar)
{
if (!Context.Runtime.IsAgentMode || bar != 0) return _retValue;
var directory = $@"{GetTSLabAddDataDirectory()}AccountData\";
if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
_filePath = $"{directory}{Context.Runtime.TradeName}_AccBalance_{ID}.csv";
var tradeName = Context.Runtime.TradeName;
var cacheBalanceID = $"BalanceControl{tradeName}{ID}";
var cacheWriteTimeID = $"BalanceControlWriteTime{tradeName}{ID}";
// Читаем данные из кеша/файла, если данных нет, то берем текущие данные по счету
var storedBalance = 0d;
var cacheData = Context.LoadObject(cacheBalanceID);
if (cacheData == null || !double.TryParse(cacheData.ToString(), out double cacheBalance) || cacheBalance == 0)
{
if (File.Exists(_filePath))
if (double.TryParse(File.ReadAllLines(_filePath)[0], out double fileBalance) && fileBalance != 0) storedBalance = fileBalance;
}
else storedBalance = cacheBalance;
if (storedBalance == 0 || Math.Abs(accBalance - storedBalance) < storedBalance * BalanceDifferencePct / 100)
{
_retValue = accBalance;
}
else
{
_retValue = accBalance;
Context.Log($"BalanceControl: от брокера получен неверный баланс {accBalance:F2}, в систему выдан сохраненный баланс {storedBalance:F2}");
}
if (WriteToLog) LogDebug($"ID: {ID}, retValue: {_retValue:F2}, storedBalance: {storedBalance:F2}, accBalance: {accBalance:F2}");
// Пишем данные в локальный кеш и при истечении интервала перезаписи в файл
Context.StoreObject(cacheBalanceID, _retValue, true);
var lastWriteDateTime = Context.LoadObject(cacheWriteTimeID);
if (WriteToFile && (lastWriteDateTime == null || (DateTime.TryParse(lastWriteDateTime.ToString(), out var lastWriteTime)
&& (DateTime.Now - lastWriteTime).TotalMinutes > FileWriteInterval && _retValue != 0)))
{
try
{
File.WriteAllText(_filePath, _retValue.ToString());
Context.StoreObject(cacheWriteTimeID, DateTime.Now, true);
}
catch (Exception e)
{
Context.Log($"BalanceControl: Невозможно записать данные по счету в файл {e.Message}");
}
}
return _retValue;
}
}
}