Compare commits

...

16 Commits

Author SHA1 Message Date
vlad zverzhkhovskiy 022cee9a19 фиксы новой стратегии
test / deploy_trader_prod (push) Successful in 5m11s Details
2025-10-14 14:05:15 +03:00
vlad zverzhkhovskiy c2612ba85a реализация новой стратегии 2025-10-13 17:20:17 +03:00
vlad zverzhkhovskiy f4fb12407a фиксация 2025-10-08 15:45:33 +03:00
vlad zverzhkhovskiy 167c2ba119 фиксация первой реализации уровней поддержки 2025-10-08 15:45:18 +03:00
vlad zverzhkhovskiy e57d67d5db доработка стратегия входов 2025-10-07 18:40:30 +03:00
vlad zverzhkhovskiy 06cf8ddc19 доработка кеша3 2025-10-07 14:40:53 +03:00
vlad zverzhkhovskiy ffc21531b5 code cleanup 2025-10-07 14:31:29 +03:00
vlad zverzhkhovskiy 8aac315338 Добавлен новый вариант кеша 2025-10-07 14:30:50 +03:00
vlad zverzhkhovskiy 584e378990 Унификация используемых моделей 2025-10-07 13:38:49 +03:00
vlad zverzhkhovskiy 41d33356dd Унификация используемых моделей 2025-10-07 13:26:28 +03:00
vlad zverzhkhovskiy e2726586e5 Чистка кода 2025-10-07 13:16:52 +03:00
vlad zverzhkhovskiy 9554b988e3 Рефакторинг 2025-10-07 13:16:10 +03:00
vlad zverzhkhovskiy 64c702ebf1 Чистка кода 2025-10-07 13:05:38 +03:00
vlad zverzhkhovskiy 00de5bee2e Чистка кода 2025-10-07 12:26:25 +03:00
vlad zverzhkhovskiy 50938f093e реализация нового критерия выставления точки на базе дивергенции 2025-10-07 11:21:34 +03:00
vlad zverzhkhovskiy 03b0f5582e фиксация 2025-10-03 12:14:56 +03:00
57 changed files with 1648 additions and 1905 deletions

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
namespace KLHZ.Trader.Core.Contracts.Common.Enums
{
public enum PositionType
{

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
namespace KLHZ.Trader.Core.Contracts.Common.Enums
{
public enum TradeDirection
{

View File

@ -1,8 +1,26 @@
namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos
{
public class CachedValue
public class CachedValue : ITradeDataItem
{
public DateTime Time { get; init; }
public long Count { get; init; }
public decimal Price { get; init; }
public decimal Value { get; init; }
public decimal Value2 { get; init; }
public CachedValue()
{
Figi = string.Empty;
Ticker = string.Empty;
Direction = 0;
IsHistoricalData = false;
}
public bool IsHistoricalData { get; init; }
public required string Figi { get; init; }
public required string Ticker { get; init; }
public int Direction { get; init; }
}
}

View File

@ -4,14 +4,9 @@
public enum TradingEvent
{
None = 0,
StopBuy = 1,
LongOpen = 2,
ShortClose = 4,
LongClose = 8,
ShortOpen = 16,
UptrendEnd = 32,
UptrendStart = 64,
DowntrendEnd = 128,
DowntrendStart = 256,
CloseLong = 1,
OpenLong = 2,
CloseShort = 4,
OpenShort = 8,
}
}

View File

@ -8,7 +8,7 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
{
public string Figi { get; }
public int Length { get; }
public ValueTask AddData(INewPrice priceChange);
public ValueTask AddData(ITradeDataItem priceChange);
public ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(int? length = null);
public ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(TimeSpan period);
public ValueTask AddOrderbook(IOrderbook orderbook);

View File

@ -1,9 +0,0 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface ILockableObject
{
public Task<bool> Lock(TimeSpan duration);
public void Unlock();
}
}

View File

@ -1,15 +0,0 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface INewCandle
{
public bool IsHistoricalData { get; set; }
public decimal Open { get; set; }
public decimal Close { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Volume { get; set; }
public string Figi { get; set; }
public string Ticker { get; set; }
public DateTime Time { get; set; }
}
}

View File

@ -1,13 +0,0 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface INewPrice
{
public bool IsHistoricalData { get; set; }
public decimal Value { get; set; }
public string Figi { get; set; }
public string Ticker { get; set; }
public DateTime Time { get; set; }
public long Count { get; set; }
public int Direction { get; set; }
}
}

View File

@ -1,6 +1,6 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface IProcessedPrice : INewPrice
public interface IProcessedPrice : ITradeDataItem
{
public string Processor { get; set; }
}

View File

@ -12,6 +12,5 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
public string AccountId { get; }
public string? OrderId { get; }
public bool EnableMargin { get; }
public ILockableObject? ExchangeObject { get; }
}
}

View File

@ -0,0 +1,15 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface ITradeDataItem
{
public bool IsHistoricalData { get; }
public decimal Price { get; }
public string Figi { get; }
public string Ticker { get; }
public DateTime Time { get; }
public long Count { get; }
public int Direction { get; }
public decimal Value { get; }
public decimal Value2 { get; }
}
}

View File

@ -12,7 +12,6 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
public long Count { get; init; }
public required string AccountId { get; init; }
public bool EnableMargin { get; init; } = true;
public ILockableObject? ExchangeObject { get; init; }
public string? OrderId { get; init; }
}
}

View File

@ -2,14 +2,16 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{
public class NewPriceMessage : INewPrice
public class TradeDataItem : ITradeDataItem
{
public decimal Value { get; set; }
public decimal Price { get; set; }
public required string Figi { get; set; }
public required string Ticker { get; set; }
public DateTime Time { get; set; }
public bool IsHistoricalData { get; set; }
public long Count { get; set; }
public int Direction { get; set; }
public decimal Value { get; init; }
public decimal Value2 { get; init; }
}
}

View File

@ -6,10 +6,10 @@ namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces
public interface IDataBus
{
public bool AddChannel(string key, Channel<IOrderbook> channel);
public bool AddChannel(string key, Channel<INewPrice> channel);
public bool AddChannel(string key, Channel<ITradeDataItem> channel);
public bool AddChannel(string key, Channel<IMessage> channel);
public bool AddChannel(string key, Channel<ITradeCommand> channel);
public Task Broadcast(INewPrice newPriceMessage);
public Task Broadcast(ITradeDataItem newPriceMessage);
public Task Broadcast(IOrderbook orderbook);
public Task Broadcast(IMessage message);
public Task Broadcast(ITradeCommand message);

View File

@ -0,0 +1,10 @@
namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{
public class ConvolutionResult
{
public decimal Sum { get; set; }
public decimal Value { get; set; }
public int Shift { get; set; }
public decimal Leverage { get; set; }
}
}

View File

@ -0,0 +1,8 @@
namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{
public class HistItem
{
public decimal Value { get; set; }
public decimal Count { get; set; }
}
}

View File

@ -22,7 +22,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Dtos
lock (_locker)
{
_cachedValues.AddLast(cachedValue);
if (_cachedValues.Last != null && _cachedValues.First != null
while (_cachedValues.Last != null && _cachedValues.First != null
&& _cachedValues.Last.Value.Time - _cachedValues.First.Value.Time > WindowSize)
{
_cachedValues.RemoveFirst();

View File

@ -105,13 +105,13 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
}
}
public ValueTask AddData(INewPrice priceChange)
public ValueTask AddData(ITradeDataItem priceChange)
{
if (priceChange.Figi != Figi) return ValueTask.CompletedTask;
lock (_locker)
{
_pointer++;
Prices[_pointer] = priceChange.Value;
Prices[_pointer] = priceChange.Price;
Timestamps[_pointer] = priceChange.Time;
if (_length < CacheMaxLength)
{
@ -200,7 +200,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
}
}
public PriceHistoryCacheUnit2(string figi, params INewPrice[] priceChanges)
public PriceHistoryCacheUnit2(string figi, params ITradeDataItem[] priceChanges)
{
Figi = figi;

View File

@ -0,0 +1,168 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
{
public class PriceHistoryCacheUnit3
{
private readonly Dictionary<string, TimeSpan> _windowSizes = new()
{
{string.Empty, TimeSpan.FromDays(3)}
};
public string Figi { get; init; }
public IOrderbook? Orderbook
{
get
{
lock (_locker)
{
return _orderbook;
}
}
}
private IOrderbook? _orderbook;
private readonly object _locker = new();
private readonly Dictionary<string, LinkedList<ITradeDataItem>> _items = new Dictionary<string, LinkedList<ITradeDataItem>>();
public ValueTask<CachedValue[]> GetDataFromTimeWindowCache(string key, TimeWindowCacheType timeWindowCacheType)
{
return ValueTask.FromResult(Array.Empty<CachedValue>());
}
public ValueTask AddData(ITradeDataItem item, string? key = null)
{
lock (_locker)
{
key = key ?? string.Empty;
if (key == string.Empty)
{
if (item.Figi != Figi)
{
throw new ArgumentException("Для дефолтного кеша Figi должен совпадать с figi элемента.");
}
}
var windowSize = _windowSizes.TryGetValue(key, out var size) ? size : TimeSpan.FromMinutes(60);
if (!_items.TryGetValue(key, out var list))
{
list = new LinkedList<ITradeDataItem>();
_items[key] = list;
}
list.AddFirst(item);
while (list.Last != null && list.First != null
&& list.First.Value.Time - list.Last.Value.Time > windowSize)
{
list.RemoveLast();
}
}
return ValueTask.CompletedTask;
}
public ValueTask AddOrderbook(IOrderbook orderbook)
{
if (orderbook.Figi != Figi) return ValueTask.CompletedTask;
lock (_locker)
{
_orderbook = orderbook;
}
return ValueTask.CompletedTask;
}
public ValueTask<ITradeDataItem[]> GetData(TimeSpan? period = null, string? key = null, Func<ITradeDataItem, bool>? selector = null)
{
key = key ?? string.Empty;
var res = new List<ITradeDataItem>();
selector = selector ?? defaultSelector;
lock (_locker)
{
if (_items.TryGetValue(key, out var list) && list.First != null && list.Last != null)
{
var startTime = list.First.Value.Time;
if (period.HasValue)
{
foreach (var item in list)
{
if (startTime - item.Time < period.Value && selector(item))
{
res.Add(item);
}
}
}
else
{
return ValueTask.FromResult(list.Where(selector).Reverse().ToArray());
}
}
}
res.Reverse();
return ValueTask.FromResult(res.ToArray());
}
public ValueTask<(DateTime time, decimal price)> GetLastValues(string? key = null)
{
key = key ?? string.Empty;
lock (_locker)
{
if (_items.TryGetValue(key, out var list) && list.First != null && list.Last != null)
{
return ValueTask.FromResult((list.Last.Value.Time, list.Last.Value.Price));
}
}
return ValueTask.FromResult((DateTime.MinValue, 0m));
}
public ValueTask<ITradeDataItem[]> GetData(TimeSpan shift, TimeSpan period, string? key = null, Func<ITradeDataItem, bool>? selector = null)
{
key = key ?? string.Empty;
var res = new List<ITradeDataItem>();
var maxPeriod = shift + period;
selector = selector ?? defaultSelector;
lock (_locker)
{
if (_items.TryGetValue(key, out var list) && list.First != null && list.Last != null)
{
var startTime = list.First.Value.Time;
foreach (var item in list)
{
var dt = startTime - item.Time;
if (dt > shift && dt < maxPeriod && selector(item))
{
res.Add(item);
}
}
}
}
res.Reverse();
return ValueTask.FromResult(res.ToArray());
}
public PriceHistoryCacheUnit3(string figi, params ITradeDataItem[] priceChanges)
{
Figi = figi;
if (priceChanges.Length == 0)
{
return;
}
var selectedPriceChanges = priceChanges
.OrderBy(pc => pc.Time)
.ToArray();
foreach (var pc in selectedPriceChanges)
{
AddData(pc).AsTask().Wait();
}
}
private static bool defaultSelector(ITradeDataItem item) => true;
}
}

View File

@ -71,19 +71,19 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
var diff2 = y2_approximated[0] - y2_approximated[y2_approximated.Count - 1];
if (diff1 <= -meanfullDiff && diff2 >= meanfullDiff)
{
res |= TradingEvent.DowntrendEnd;
res |= TradingEvent.CloseShort;
}
else if (diff1 >= meanfullDiff && diff2 <= -meanfullDiff)
{
res |= TradingEvent.UptrendEnd;
res |= TradingEvent.CloseLong;
}
else if (diff1 <= -meanfullDiff && diff2 >= meanfullDiff)
{
res |= TradingEvent.DowntrendEnd;
res |= TradingEvent.CloseShort;
}
else if (diff1 >= 0 && diff2 <= -meanfullDiff)
{
res |= TradingEvent.DowntrendStart;
res |= TradingEvent.OpenShort;
}
success = true;
}

View File

@ -97,7 +97,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
if (diffTotal >= uptrendEndingDetectionMeanfullStep
&& times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart)
{
res |= TradingEvent.UptrendEnd;
res |= TradingEvent.CloseLong;
}
break;
}
@ -115,7 +115,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] <= uptrendStartingDetectionMeanfullStep
&& times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart)
{
res |= TradingEvent.UptrendStart;
res |= TradingEvent.OpenLong;
}
break;
}
@ -188,23 +188,23 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
// если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта
if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2])
{
if (!uptrendEndingDetectionMeanfullStep.HasValue || ((d1 >= uptrendEndingDetectionMeanfullStep
if (!uptrendEndingDetectionMeanfullStep.HasValue || ((d1 >= uptrendEndingDetectionMeanfullStep
//|| d2 >= uptrendEndingDetectionMeanfullStep
)
&& dt>TimeSpan.FromSeconds(10)))
)
&& dt > TimeSpan.FromSeconds(10)))
{
res |= TradingEvent.UptrendEnd;
res |= TradingEvent.CloseLong;
}
break;
}
// если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта
if (twavss[size - 1] >= twavbs[size - 1] && twavss[size - 2] < twavbs[size - 2])
{
if (!uptrendStartingDetectionMeanfullStep.HasValue ||( (d1 <= uptrendStartingDetectionMeanfullStep
// || d2 <= uptrendStartingDetectionMeanfullStep
if (!uptrendStartingDetectionMeanfullStep.HasValue || ((d1 <= uptrendStartingDetectionMeanfullStep
// || d2 <= uptrendStartingDetectionMeanfullStep
) && dt > TimeSpan.FromSeconds(10)))
{
res |= TradingEvent.UptrendStart;
res |= TradingEvent.OpenLong;
}
break;
}

View File

@ -1,4 +1,7 @@
namespace KLHZ.Trader.Core.Math.Declisions.Utils
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
public static class SignalProcessing
{
@ -54,15 +57,112 @@
return (res2.ToArray(), res.ToArray());
}
public static ITradeDataItem[] InterpolateData(ITradeDataItem[] items, TimeSpan timeStep)
{
var result = new List<ITradeDataItem>();
var firstItem = items[0];
var startTime = new DateTime(firstItem.Time.Year, firstItem.Time.Month, firstItem.Time.Day, 0, 0, 0, DateTimeKind.Utc);
var dt = items[0].Time - startTime;
var totalSteps = System.Math.Ceiling((items[items.Length - 1].Time - firstItem.Time).TotalSeconds / timeStep.TotalSeconds);
var deltaSeconds = System.Math.Floor(dt.TotalSeconds / timeStep.TotalSeconds);
startTime = startTime.AddSeconds(deltaSeconds * timeStep.TotalSeconds);
var firstBound = startTime;
var secondBound = startTime + timeStep;
var boundD1 = 0;
var boundD2 = 0;
for (int i = 0; i < totalSteps; i++)
{
var countD1 = 0;
var sumD1 = 0m;
var cD1 = 0L;
var countD2 = 0;
var sumD2 = 0m;
var cD2 = 0L;
for (int i1 = boundD1; i1 < items.Length; i1++)
{
if (items[i1].Direction == 1)
{
if (items[i1].Time > firstBound && items[i1].Time <= secondBound)
{
countD1++;
sumD1 += items[i1].Price;
cD1 += items[i1].Count;
}
else if (countD1 != 0)
{
boundD1 = i1;
break;
}
}
}
for (int i1 = boundD2; i1 < items.Length; i1++)
{
if (items[i1].Direction == 2)
{
if (items[i1].Time > firstBound && items[i1].Time <= secondBound)
{
countD2++;
sumD2 += items[i1].Price;
cD2 += items[i1].Count;
}
else if (countD2 != 0)
{
boundD2 = i1;
break;
}
}
}
if (countD1 != 0)
{
result.Add(new TradeDataItem()
{
Figi = firstItem.Figi,
Ticker = firstItem.Ticker,
Price = sumD1 / countD1,
Time = secondBound,
IsHistoricalData = firstItem.IsHistoricalData,
Direction = 1,
Count = cD1
});
}
if (countD2 != 0)
{
result.Add(new TradeDataItem()
{
Figi = firstItem.Figi,
Ticker = firstItem.Ticker,
Price = sumD2 / countD2,
Time = secondBound,
IsHistoricalData = firstItem.IsHistoricalData,
Direction = 2,
Count = cD2
});
}
firstBound += timeStep;
secondBound += timeStep;
}
return result.OrderBy(r => r.Time).ToArray();
}
public static decimal[] CalcDiffs(decimal[] values)
{
if (values.Length < 1) throw new ArgumentException();
var resArray = new decimal[values.Length-1];
for (int i=1; i<values.Length; i++)
var resArray = new decimal[values.Length - 1];
for (int i = 1; i < values.Length; i++)
{
resArray[i - 1] = values[i] - values[i-1];
resArray[i - 1] = values[i] - values[i - 1];
}
return resArray;
}

View File

@ -1,18 +1,19 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Dtos;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
public static class Statistics
{
public static decimal Mean(this CachedValue[] values)
public static decimal MeanCount(this ITradeDataItem[] values)
{
return values.Sum(x => x.Value)/ values.Length;
return values.Sum(x => x.Count) / values.Length;
}
public static decimal MeanPrice(this ITradeDataItem[] values)
{
return values.Sum(x => x.Price) / values.Length;
}
private static (decimal mean, decimal std) CaclSigma(decimal[] values)
@ -26,12 +27,12 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
data[i] = v * v;
}
var std = System.Math.Pow((double)(data.Sum() / (data.Length - 1)), 0.5);
return (mean,(decimal)std);
return (mean, (decimal)std);
}
public static decimal[] ClearNSigmaReqursive(decimal[] values, int depth = 0, int sigmasCount = 3)
{
if (values.Length <= 1 || depth>10) return values;
if (values.Length <= 1 || depth > 10) return values;
var sigmaRes = CaclSigma(values);
var std = sigmaRes.std;
var mean = sigmaRes.mean;
@ -39,19 +40,182 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
var _3std = sigmasCount * std;
foreach (var v in values)
{
if (System.Math.Abs(mean - v)< _3std)
if (System.Math.Abs(mean - v) < _3std)
{
forRes.Add(v);
}
}
if (forRes.Count != values.Length)
{
return ClearNSigmaReqursive(forRes.ToArray(), depth+1);
return ClearNSigmaReqursive(forRes.ToArray(), depth + 1);
}
else
{
return forRes.ToArray();
}
}
public static bool TryCalcTimeWindowsDiff(this ITradeDataItem[] values, TimeSpan boundLeft, TimeSpan boundRight,
Func<ITradeDataItem, decimal> fieldSelector, bool calcMean, out decimal result, out decimal resultRelative)
{
result = default;
resultRelative = default;
if (values.Length > 1)
{
var shiftTimeR = values.Last().Time - boundRight;
var shiftTimeL = values.Last().Time - boundLeft;
var valuesOld = values.Where(b => b.Time < shiftTimeR && b.Time >= shiftTimeL).ToArray();
var valuesNew = values.Where(b => b.Time >= shiftTimeR).ToArray();
if (valuesOld.Length > 0 && valuesNew.Length > 0)
{
var valNew = valuesNew.Sum(fieldSelector);
var valOld = valuesOld.Sum(fieldSelector);
if (calcMean)
{
valNew = valNew / valuesNew.Length;
valOld = valOld / valuesOld.Length;
}
result = valNew - valOld;
resultRelative = (valNew - valOld) / valOld;
return true;
}
}
return false;
}
public static bool TryCalcTimeDiff(this ITradeDataItem[] values, TimeSpan boundLeft, TimeSpan boundRight,
Func<ITradeDataItem, decimal> fieldSelector, bool calcMean, out decimal result)
{
result = default;
if (values.Length > 1)
{
var shiftTimeR = values.Last().Time - boundRight;
var shiftTimeL = values.Last().Time - boundLeft;
var valuesOld = values.Where(b => b.Time < shiftTimeR && b.Time >= shiftTimeL).ToArray();
var valuesNew = values.Where(b => b.Time >= shiftTimeR).ToArray();
if (valuesOld.Length > 0 && valuesNew.Length > 0)
{
var valNew = fieldSelector(valuesNew.Last());
var valOld = fieldSelector(valuesOld.Last());
result = valNew - valOld;
return true;
}
}
return false;
}
public static bool TryCalcPirsonCorrelation(this ITradeDataItem[] values, TimeSpan period, out decimal result)
{
result = default;
if (values.Any())
{
var shiftTimeDiffs1 = values.Last().Time - period;
values = values.Where(b => b.Time >= shiftTimeDiffs1).ToArray();
if (values.Any())
{
var tradevolume_diffMean = values.MeanCount();
var dprice_diffMean = values.MeanPrice();
var sum1 = (double)values.Sum(d => (d.Value2 - tradevolume_diffMean) * (d.Value - dprice_diffMean));
var sum2 = values.Sum(d => (d.Value2 - tradevolume_diffMean) * (d.Value2 - tradevolume_diffMean));
var sum3 = values.Sum(d => (d.Value - dprice_diffMean) * (d.Value - dprice_diffMean));
if (sum2 != 0 && sum3 != 0)
{
result = (decimal)(sum1 / System.Math.Sqrt((double)(sum2 * sum3)));
return true;
}
}
}
return false;
}
public static HistItem[] CalcHistogram(ITradeDataItem[] values)
{
var result = new Dictionary<decimal, decimal>();
foreach (var item in values)
{
if (!result.TryGetValue(item.Price, out var val))
{
result[item.Price] = 0;
}
result[item.Price] = val + 1;
}
return result.Select(r => new HistItem() { Value = r.Key, Count = r.Value }).OrderBy(i => i.Value).ToArray();
}
public static decimal[] GetParabolaCore(int leverageSize, decimal maxValue = 1)
{
var d = new double[2 * leverageSize + 1];
for (int i = 0; i < d.Length; i++)
{
d[i] = -System.Math.Pow((i - leverageSize), 2);
}
var min = d.Min();
var amin = System.Math.Abs(min);
for (int i = 0; i < d.Length; i++)
{
d[i] = (d[i] - min) / amin * (double)maxValue;
}
return d.Select(i => (decimal)i).ToArray();
}
public static ConvolutionResult[] CalcConvolution(HistItem[] hist, int leverageSize)
{
if (hist.Length > 2 * leverageSize + 1)
{
var results = new List<ConvolutionResult>();
var coreSize = 2 * leverageSize + 1;
for (int shift = 0; shift < hist.Length - coreSize; shift++)
{
var s = 0m;
var k = 0;
for (int i = 0; i < 2 * leverageSize + 1; i++)
{
var core = GetParabolaCore(leverageSize, hist[i + shift].Count);
s += (hist[i + shift].Count - core[i]) * (hist[i + shift].Count - core[i]);
if (i == leverageSize)
{
k = i + shift;
}
}
s = s / coreSize;
s = (decimal)System.Math.Pow((double)s, 0.5d);
//s /= coreSum;
results.Add(new ConvolutionResult()
{
Leverage = leverageSize,
Sum = s,
Value = hist[k].Value,
Shift = k,
});
}
return results.OrderByDescending(r => r.Value).ToArray();
}
return Array.Empty<ConvolutionResult>();
}
public static void MergeConvolutionResults(List<ConvolutionResult> results, List<ConvolutionResult> mergedResults)
{
if (results.Count == 0)
{
return;
}
var resultsOrdered = results.OrderBy(r => r.Sum).ToList();
var res = resultsOrdered[0];
var b1 = res.Shift - res.Leverage;
var b2 = res.Shift + res.Leverage;
var forMerge = results.Where(r => r.Shift >= b1 && r.Shift <= b2).ToList();
res.Sum = forMerge.Sum(r => r.Sum);
foreach (var m in forMerge)
{
results.Remove(m);
}
mergedResults.Add(res);
MergeConvolutionResults(results, mergedResults);
}
}
}

View File

@ -20,7 +20,7 @@ namespace KLHZ.Trader.Core.Tests
Ticker = figi + "_ticker",
Id = i,
Time = startDt,
Value = (decimal)(i + 0.5)
Price = (decimal)(i + 0.5)
};
}
}
@ -53,7 +53,7 @@ namespace KLHZ.Trader.Core.Tests
Assert.That(data.timestamps.Length == count);
for (var i = 0; i < count; i++)
{
Assert.That((float)hist[i].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[i].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[i].Time, Is.EqualTo(data.timestamps[i]));
}
}
@ -72,7 +72,7 @@ namespace KLHZ.Trader.Core.Tests
for (var i = 0; i < count; i++)
{
Assert.That((float)hist[i].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[i].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[i].Time, Is.EqualTo(data.timestamps[i]));
}
}
@ -91,7 +91,7 @@ namespace KLHZ.Trader.Core.Tests
for (var i = 0; i < count; i++)
{
Assert.That((float)hist[i].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[i].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[i].Time, Is.EqualTo(data.timestamps[i]));
}
}
@ -114,7 +114,7 @@ namespace KLHZ.Trader.Core.Tests
var k = i + shift;
if (k < hist.Length)
{
Assert.That((float)hist[k].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[k].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[k].Time, Is.EqualTo(data.timestamps[i]));
}
}
@ -138,7 +138,7 @@ namespace KLHZ.Trader.Core.Tests
var k = i + shift;
if (k < hist.Length)
{
Assert.That((float)hist[k].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[k].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[k].Time, Is.EqualTo(data.timestamps[i]));
}
}
@ -162,7 +162,7 @@ namespace KLHZ.Trader.Core.Tests
var k = i + shift;
if (k < hist.Length)
{
Assert.That((float)hist[k].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[k].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[k].Time, Is.EqualTo(data.timestamps[i]));
}
}
@ -183,24 +183,24 @@ namespace KLHZ.Trader.Core.Tests
for (var i = 0; i < count; i++)
{
Assert.That((float)hist[i].Value, Is.EqualTo(data.prices[i]));
Assert.That((float)hist[i].Price, Is.EqualTo(data.prices[i]));
Assert.That(hist[i].Time, Is.EqualTo(data.timestamps[i]));
}
var newData1 = new PriceChange() { Figi = figi, Ticker = figi, Value = 100500, Time = DateTime.UtcNow };
var newData1 = new PriceChange() { Figi = figi, Ticker = figi, Price = 100500, Time = DateTime.UtcNow };
cacheUnit.AddData(newData1);
var data2 = cacheUnit.GetData().Result;
Assert.IsTrue(data2.prices[data2.prices.Length - 1] == newData1.Value);
Assert.IsTrue(data2.prices[data2.prices.Length - 1] == newData1.Price);
Assert.IsTrue(data2.timestamps[data2.timestamps.Length - 1] == newData1.Time);
var newData2 = new PriceChange() { Figi = figi, Ticker = figi, Value = 100501, Time = DateTime.UtcNow };
var newData2 = new PriceChange() { Figi = figi, Ticker = figi, Price = 100501, Time = DateTime.UtcNow };
cacheUnit.AddData(newData2);
var data3 = cacheUnit.GetData().Result;
Assert.IsTrue(data3.prices[data3.prices.Length - 1] == newData2.Value);
Assert.IsTrue(data3.prices[data3.prices.Length - 1] == newData2.Price);
Assert.IsTrue(data3.timestamps[data3.timestamps.Length - 1] == newData2.Time);
}
@ -210,7 +210,7 @@ namespace KLHZ.Trader.Core.Tests
var cacheUnit = new PriceHistoryCacheUnit2("");
for (int i = 0; i < 5 * PriceHistoryCacheUnit2.CacheMaxLength; i++)
{
cacheUnit.AddData(new PriceChange() { Figi = "", Ticker = "", Value = i, Time = DateTime.UtcNow });
cacheUnit.AddData(new PriceChange() { Figi = "", Ticker = "", Price = i, Time = DateTime.UtcNow });
if (i >= PriceHistoryCacheUnit2.CacheMaxLength)
{
var data = cacheUnit.GetData().Result;
@ -237,11 +237,11 @@ namespace KLHZ.Trader.Core.Tests
Assert.That(data.prices.Length == length);
Assert.That(data.timestamps.Length == length);
Assert.That(data.prices.Last() == hist.Last().Value);
Assert.That(data.prices.Last() == hist.Last().Price);
Assert.That(data.timestamps.Last() == hist.Last().Time);
for (var i = 1; i <= length; i++)
{
Assert.That(hist[hist.Length - i].Value, Is.EqualTo(data.prices[data.prices.Length - i]));
Assert.That(hist[hist.Length - i].Price, Is.EqualTo(data.prices[data.prices.Length - i]));
Assert.That(hist[hist.Length - i].Time, Is.EqualTo(data.timestamps[data.prices.Length - i]));
}
}
@ -267,7 +267,7 @@ namespace KLHZ.Trader.Core.Tests
Assert.That(data.prices.Length == length);
Assert.That(data.timestamps.Length == length);
Assert.That(data.prices.Last() == hist.Last().Value);
Assert.That(data.prices.Last() == hist.Last().Price);
Assert.That(data.timestamps.Last() == hist.Last().Time);
}

View File

@ -0,0 +1,191 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
namespace KLHZ.Trader.Core.Tests
{
public class HistoryCacheUnit3Tests
{
private static PriceChange[] GetHistory(int count, string figi)
{
var res = new PriceChange[count];
if (count != 0)
{
var startDt = DateTime.UtcNow.AddSeconds(-count);
for (int i = 0; i < count; i++)
{
startDt = startDt.AddSeconds(1);
res[i] = new PriceChange()
{
Figi = figi,
Ticker = figi + "_ticker",
Id = i,
Time = startDt,
Price = (decimal)(i + 0.5),
Value = i % 2 == 0 ? i : 0
};
}
}
return res;
}
[Test]
public void Test1()
{
var count = 0;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi, hist);
var data = cacheUnit.GetData().Result;
Assert.That(data.Length == count);
}
[Test]
public void Test2()
{
var count = 1111;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi, hist);
var data = cacheUnit.GetData().Result;
Assert.That(data.Length == count);
}
[Test]
public void Test3()
{
var count = 60 * 60 * 3 * 24;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi, hist);
var data = cacheUnit.GetData().Result;
Assert.That(data.Length == count);
}
[Test]
public void Test4()
{
var count = 60 * 60 * 3 * 24;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
foreach (var item in hist)
{
cacheUnit.AddData(item);
}
var data = cacheUnit.GetData().Result;
Assert.That(data.Length == count);
}
[Test]
public void Test5()
{
var count = 60 * 60 * 3 * 24 + 10;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
for (int i = 0; i < hist.Length - 1; i++)
{
cacheUnit.AddData(hist[i]);
}
cacheUnit.AddData(hist.Last());
var data = cacheUnit.GetData().Result;
var dt = data.Last().Time - data.First().Time;
Assert.That(dt <= TimeSpan.FromDays(3));
Assert.That(dt > TimeSpan.Zero);
}
[Test]
public void Test6()
{
var count = 60 * 60 * 3 * 24 + 10;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
for (int i = 0; i < hist.Length - 1; i++)
{
cacheUnit.AddData(hist[i], "kkk");
}
var data = cacheUnit.GetData(key: "kkk").Result;
var dt = data.Last().Time - data.First().Time;
Assert.That(dt <= TimeSpan.FromHours(1));
Assert.That(dt > TimeSpan.Zero);
}
[Test]
public void Test7()
{
var count = 60 * 60 * 3 * 24 + 10;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
for (int i = 0; i < hist.Length - 1; i++)
{
cacheUnit.AddData(hist[i], "kkk");
}
var data = cacheUnit.GetData(TimeSpan.FromMinutes(3), key: "kkk").Result;
var dt = data.Last().Time - data.First().Time;
Assert.That(dt <= TimeSpan.FromMinutes(3));
Assert.That(dt > TimeSpan.Zero);
}
[Test]
public void Test8()
{
var count = 60 * 60 * 3 * 24 + 10;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
for (int i = 0; i < hist.Length - 1; i++)
{
cacheUnit.AddData(hist[i], "kkk");
}
var data = cacheUnit.GetData(TimeSpan.FromMinutes(4), TimeSpan.FromMinutes(3), key: "kkk").Result;
var dt = data.Last().Time - data.First().Time;
Assert.That(dt <= TimeSpan.FromMinutes(3));
Assert.That(dt > TimeSpan.Zero);
}
[Test]
public void Test9()
{
var count = 60 * 60 * 3 * 24 + 10;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
for (int i = 0; i < hist.Length - 1; i++)
{
cacheUnit.AddData(hist[i], "kkk");
}
var data = cacheUnit.GetData(TimeSpan.FromMinutes(4), TimeSpan.FromMinutes(3), key: "kkk", (i) => i.Value != 0).Result;
Assert.That(data.All(d => d.Value != 0));
}
[Test]
public void Test10()
{
var count = 60 * 60 * 3 * 24 + 10;
var figi = "figi";
var hist = GetHistory(count, figi);
var cacheUnit = new PriceHistoryCacheUnit3(figi);
for (int i = 0; i < hist.Length - 1; i++)
{
cacheUnit.AddData(hist[i], "kkk");
}
var data = cacheUnit.GetData(selector: (i) => i.Value != 0).Result;
Assert.That(data.All(d => d.Value != 0));
}
}
}

View File

@ -33,7 +33,7 @@ namespace KLHZ.Trader.Core.Tests
if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(49), TimeSpan.FromSeconds(49), 22, out var res))
{
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.UptrendEnd);
Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.CloseLong);
}
else
{

View File

@ -1,4 +1,6 @@
using KLHZ.Trader.Core.Math.Declisions.Utils;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using System.Security.Cryptography;
namespace KLHZ.Trader.Core.Tests
@ -33,10 +35,35 @@ namespace KLHZ.Trader.Core.Tests
var res = SignalProcessing.CalcDiffs(da.ToArray());
Assert.IsTrue(res.Length - da.Count == -1);
foreach(var r in res)
foreach (var r in res)
{
Assert.IsTrue(r == 1);
}
}
[Test]
public static void Test3()
{
var results = new List<ITradeDataItem>();
var times = new List<DateTime>();
var startDt = DateTime.UtcNow;
for (int i = 0; i < 100; i++)
{
startDt = startDt.AddSeconds(((double)(RandomNumberGenerator.GetInt32(1, 100))) / 100);
var t = new TradeDataItem()
{
Figi = "",
Ticker = "",
Time = startDt,
Count = 1,
Direction = RandomNumberGenerator.GetInt32(1, 3),
IsHistoricalData = true,
Price = (decimal)System.Math.Sin(0.01 * i) + (decimal)System.Math.Cos(0.01 * i),
};
results.Add(t);
}
var res = SignalProcessing.InterpolateData(results.ToArray(), TimeSpan.FromSeconds(5));
}
}
}

View File

@ -1,20 +1,16 @@
using KLHZ.Trader.Core.Math.Declisions.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace KLHZ.Trader.Core.Tests
{
internal class StatisticTests
{
[Test]
public static void Test()
public static void Test1()
{
var data = new decimal[1000];
for(int i = 0; i < data.Length; i++)
for (int i = 0; i < data.Length; i++)
{
data[i] = RandomNumberGenerator.GetInt32(-10, 10);
}
@ -26,5 +22,27 @@ namespace KLHZ.Trader.Core.Tests
Assert.IsTrue(data.Length != res.Length);
Assert.IsTrue(res[0] != 1000);
}
[Test]
public static void Test2()
{
var data = new decimal[1000];
for (int i = 0; i < data.Length; i++)
{
data[i] = RandomNumberGenerator.GetInt32(-10, 10);
}
var res = Statistics.CalcHistogram(data.Select(d => new CachedValue()
{
Figi = "",
Ticker = "",
Direction = 1,
Price = d / 2,
Time = DateTime.UtcNow,
}).ToArray());
Statistics.CalcConvolution(res, 5);
}
}
}

View File

@ -10,7 +10,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
private readonly ConcurrentDictionary<string, Channel<IOrderbook>> _orderbooksChannels = new();
private readonly ConcurrentDictionary<string, Channel<IMessage>> _messagesChannels = new();
private readonly ConcurrentDictionary<string, Channel<ITradeCommand>> _commandsChannel = new();
private readonly ConcurrentDictionary<string, Channel<INewPrice>> _priceChannels = new();
private readonly ConcurrentDictionary<string, Channel<ITradeDataItem>> _priceChannels = new();
public bool AddChannel(string key, Channel<IMessage> channel)
{
@ -22,7 +22,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
return _commandsChannel.TryAdd(key, channel);
}
public bool AddChannel(string key, Channel<INewPrice> channel)
public bool AddChannel(string key, Channel<ITradeDataItem> channel)
{
return _priceChannels.TryAdd(key, channel);
}
@ -32,7 +32,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
return _orderbooksChannels.TryAdd(key, channel);
}
public async Task Broadcast(INewPrice newPriceMessage)
public async Task Broadcast(ITradeDataItem newPriceMessage)
{
foreach (var channel in _priceChannels.Values)
{

View File

@ -3,8 +3,6 @@
public enum DeclisionTradeAction
{
Unknown = 0,
StopBuy = 1,
StopBuyShortTime = 2,
OpenLong = 100,
OpenLongReal = 101,
CloseLong = 200,

View File

@ -5,10 +5,5 @@
Unknown = 0,
Ask = 1,
Bid = 2,
AsksSummary10 = 3,
BidsSummary10 = 4,
AsksSummary4 = 5,
BidsSummary4 = 6,
BidsAsksSummary4_2min = 7,
}
}

View File

@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
{
[Table("price_changes")]
public class PriceChange : INewPrice
public class PriceChange : ITradeDataItem
{
[Column("id")]
public long Id { get; set; }
@ -13,7 +13,7 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
public DateTime Time { get; set; }
[Column("value")]
public decimal Value { get; set; }
public decimal Price { get; set; }
[Column("figi")]
public required string Figi { get; set; }
@ -28,5 +28,11 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
[Column("direction")]
public int Direction { get; set; }
[NotMapped]
public decimal Value { get; set; }
[NotMapped]
public decimal Value2 { get; set; }
}
}

View File

@ -13,7 +13,7 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
public DateTime Time { get; set; }
[Column("value")]
public decimal Value { get; set; }
public decimal Price { get; set; }
[Column("figi")]
public required string Figi { get; set; }
@ -31,5 +31,11 @@ namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
[NotMapped]
public int Direction { get; set; }
[NotMapped]
public decimal Value { get; set; }
[NotMapped]
public decimal Value2 { get; set; }
}
}

View File

@ -1,10 +0,0 @@
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
{
public enum AssetType
{
Unknown = 0,
Common = 1,
Future = 2,
Currency = 3,
}
}

View File

@ -1,9 +0,0 @@
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
{
public enum PositionType
{
Unknown = 0,
Long = 1,
Short = 2
}
}

View File

@ -1,52 +0,0 @@
using KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
{
/// <summary>
/// Сделка, совершенная ботом.
/// </summary>
[Table("trades")]
public class Trade
{
[Column("trade_id")]
public long Id { get; set; }
[Column("bought_at")]
public DateTime BoughtAt { get; set; }
[Column("account_id")]
public required string AccountId { get; set; }
[Column("figi")]
public required string Figi { get; set; }
[Column("ticker")]
public required string Ticker { get; set; }
[Column("price")]
public decimal Price { get; set; }
[Column("count")]
public decimal Count { get; set; }
[Column("count_lots")]
public decimal CountLots { get; set; }
[Column("archive_status")]
public int ArchiveStatus { get; set; }
[Column("direction")]
public TradeDirection Direction { get; set; }
[Column("position_type")]
public PositionType Position { get; set; }
[Column("asset_type")]
public AssetType Asset { get; set; }
[Column("asset_id")]
public Guid AssetId { get; set; }
}
}

View File

@ -1,7 +1,6 @@
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.DataLayer.Entities.Orders;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.DataLayer.Entities.Trades;
using Microsoft.EntityFrameworkCore;
@ -9,7 +8,6 @@ namespace KLHZ.Trader.Core.DataLayer
{
public class TraderDbContext : DbContext
{
public DbSet<Trade> Trades { get; set; }
public DbSet<Declision> Declisions { get; set; }
public DbSet<PriceChange> PriceChanges { get; set; }
public DbSet<ProcessedPrice> ProcessedPrices { get; set; }
@ -23,15 +21,6 @@ namespace KLHZ.Trader.Core.DataLayer
{
modelBuilder.UseSerialColumns();
modelBuilder.Entity<Trade>(entity =>
{
entity.HasKey(e1 => e1.Id);
entity.Property(e => e.BoughtAt)
.HasConversion(
v => v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
});
modelBuilder.Entity<Declision>(entity =>
{
entity.HasKey(e1 => e1.Id);

View File

@ -15,10 +15,11 @@
internal const string AreasRelationProcessor = "balancescalc30min";
internal readonly static TimeSpan AreasRelationWindow = TimeSpan.FromMinutes(15);
internal const decimal ForceExecuteCoefficient = 500000m;
internal const decimal PowerUppingCoefficient = 1.69m;
internal const decimal UppingCoefficient = 1.3m;
internal const decimal LowingCoefficient = .76m;
internal const decimal PowerLowingCoefficient = .59m;
internal const decimal BlockingCoefficient = 0m;
internal const decimal BlockingCoefficient = 0.01m;
}
}

View File

@ -1,4 +1,5 @@
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Contracts.Common.Enums;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using System.Collections.Immutable;
namespace KLHZ.Trader.Core.Exchange.Interfaces

View File

@ -1,4 +1,6 @@
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
using KLHZ.Trader.Core.Contracts.Common.Enums;
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
{
public class Asset
{

View File

@ -1,9 +0,0 @@
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
{
public enum DealDirection
{
Unknown = 0,
Buy = 1,
Sell = 2
}
}

View File

@ -1,12 +0,0 @@
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
{
public class DealResult
{
public required string AccountId { get; set; }
public required string Figi { get; set; }
public decimal Price { get; set; }
public decimal Count { get; set; }
public bool Success { get; set; }
public DealDirection Direction { get; set; }
}
}

View File

@ -1,15 +0,0 @@
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
{
public class Order
{
public required string AccountId { get; init; }
public required string Figi { get; init; }
public required string Ticker { get; init; }
public required string OrderId { get; init; }
public decimal Price { get; init; }
public long Count { get; init; }
public DateTime ExpirationTime { get; init; }
public DateTime OpenDate { get; init; }
public DealDirection Direction { get; init; }
}
}

View File

@ -2,16 +2,14 @@
{
public class ExchangeConfig
{
public bool ExchangeDataRecievingEnabled { get; set; }
public decimal StopBuyLengthMinuts { get; set; }
public decimal FutureComission { get; set; }
public decimal ShareComission { get; set; }
public decimal AccountCashPart { get; set; }
public decimal AccountCashPartFutures { get; set; }
public decimal DefaultBuyPartOfAccount { get; set; }
public string[] DataRecievingInstrumentsFigis { get; set; } = [];
public string[] TradingInstrumentsFigis { get; set; } = [];
public string[] ManagingAccountNamePatterns { get; set; } = [];
public InstrumentSettings[] InstrumentsSettings { get; set; } = [];
public bool ExchangeDataRecievingEnabled { get; init; }
public decimal FutureComission { get; init; }
public decimal ShareComission { get; init; }
public decimal AccountCashPart { get; init; }
public decimal AccountCashPartFutures { get; init; }
public string[] DataRecievingInstrumentsFigis { get; init; } = [];
public string[] TradingInstrumentsFigis { get; init; } = [];
public string[] ManagingAccountNamePatterns { get; init; } = [];
public InstrumentSettings[] InstrumentsSettings { get; init; } = [];
}
}

View File

@ -0,0 +1,14 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.Collections.Immutable;
namespace KLHZ.Trader.Core.Exchange.Models.Trading
{
internal class DeferredDeclision
{
public ImmutableDictionary<TradingEvent, decimal> Events { get; init; } = ImmutableDictionary<TradingEvent, decimal>.Empty;
public Stops Stops { get; init; }
public DateTime ExpirationTime { get; init; }
public required ITradeDataItem Message { get; init; }
}
}

View File

@ -0,0 +1,11 @@
namespace KLHZ.Trader.Core.Exchange.Models.Trading
{
internal class PirsonCalculatingResult
{
public bool Success { get; init; }
public decimal Pirson { get; init; }
public decimal PriceDiff { get; init; }
public decimal TradesDiff { get; init; }
public decimal TradesDiffRelative { get; init; }
}
}

View File

@ -0,0 +1,32 @@
using KLHZ.Trader.Core.Contracts.Common.Enums;
namespace KLHZ.Trader.Core.Exchange.Models.Trading
{
public readonly struct Stops
{
public readonly decimal LongStopLossShift;
public readonly decimal LongTakeProfitShift;
public readonly decimal ShortStopLossShift;
public readonly decimal ShortTakeProfitShift;
public Stops(decimal longStopLossShift, decimal longTakeProfitShift, decimal shortStopLossShift, decimal shortTakeProfitShift)
{
LongStopLossShift = longStopLossShift;
LongTakeProfitShift = longTakeProfitShift;
ShortStopLossShift = shortStopLossShift;
ShortTakeProfitShift = shortTakeProfitShift;
}
public (decimal takeProfit, decimal stopLoss) GetStops(PositionType positionType)
{
if (positionType == PositionType.Short)
{
return (ShortTakeProfitShift, ShortStopLossShift);
}
else
{
return (LongTakeProfitShift, LongStopLossShift);
}
}
}
}

View File

@ -0,0 +1,11 @@
namespace KLHZ.Trader.Core.Exchange.Models.Trading
{
public class SupportLevel
{
public decimal Value { get; init; }
public decimal LowValue { get; init; }
public decimal HighValue { get; init; }
public DateTime? LastLevelTime { get; init; }
public DateTime CalculatedAt { get; init; }
}
}

View File

@ -6,7 +6,6 @@ using KLHZ.Trader.Core.DataLayer.Entities.Orders;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Exchange.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -150,45 +149,13 @@ namespace KLHZ.Trader.Core.Exchange.Services
Figi = response.Trade.Figi,
Ticker = _tradeDataProvider.GetTickerByFigi(response.Trade.Figi),
Time = response.Trade.Time.ToDateTime().ToUniversalTime(),
Value = response.Trade.Price,
Price = response.Trade.Price,
IsHistoricalData = false,
Direction = (int)response.Trade.Direction,
Count = response.Trade.Quantity,
};
//await _tradeDataProvider.AddData(message, TimeSpan.FromHours(7));
await _eventBus.Broadcast(message);
var exchangeState = ExchangeScheduler.GetCurrentState();
if (exchangeState == Models.Trading.ExchangeState.ClearingTime
&& lastUpdateDict.TryGetValue(message.Figi, out var pri)
&& (DateTime.UtcNow - pri.Time).Minutes > 3)
{
var assets = _portfolioWrapper.Accounts.Values.SelectMany(a => a.Assets.Values).Where(a => a.Figi == message.Figi).ToArray();
foreach (var a in assets)
{
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
await context.Trades.AddAsync(new DataLayer.Entities.Trades.Trade()
{
AssetId = a.AssetId,
AccountId = string.Empty,
Figi = message.Figi,
Ticker = string.Empty,
ArchiveStatus = 0,
Asset = (KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums.AssetType)(int)a.Type,
BoughtAt = DateTime.UtcNow,
Count = 0,
Direction = a.Count > 0 ? DataLayer.Entities.Trades.Enums.TradeDirection.Buy : DataLayer.Entities.Trades.Enums.TradeDirection.Sell,
Position = a.Count > 0 ? DataLayer.Entities.Trades.Enums.PositionType.Long : DataLayer.Entities.Trades.Enums.PositionType.Short,
Price = message.Value,
});
await context.SaveChangesAsync();
}
}
lastUpdateDict[message.Figi] = message;
pricesBuffer.Add(message);
}
if (response.Orderbook != null)

View File

@ -1,8 +1,8 @@
using KLHZ.Trader.Core.Common.Extentions;
using KLHZ.Trader.Core.Contracts.Common.Enums;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Interfaces;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -50,13 +50,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
public ImmutableDictionary<string, Asset> Assets => GetAssets();
private readonly InvestApiClient _investApiClient;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ILogger<TraderDataProvider> _logger;
private readonly IOptions<ExchangeConfig> _options;
private readonly Dictionary<string, Asset> _assets = new();
private readonly ConcurrentDictionary<string, DateTime> _usedOrderIds = new();
@ -140,26 +138,20 @@ namespace KLHZ.Trader.Core.Exchange.Services
using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var trades = await context.Trades
.Where(t => t.ArchiveStatus == 0)
.OrderByDescending(t => t.BoughtAt)
.ToListAsync();
var oldAssets = _assets.ToDictionary();
_assets.Clear();
foreach (var position in portfolio.Positions)
{
oldAssets.TryGetValue(position.Figi, out var oldAsset);
var newAssetId = oldAsset?.AssetId ?? Guid.NewGuid();
var trade = trades.FirstOrDefault(t => t.Figi == position.Figi && t.AssetId == newAssetId);
var asset = new Asset()
{
AssetId = newAssetId,
AccountId = AccountId,
Figi = position.Figi,
Ticker = position.Ticker,
BoughtAt = trade?.BoughtAt ?? DateTime.UtcNow,
BoughtPrice = trade?.Price ?? position.AveragePositionPrice,
BoughtAt = oldAsset?.BoughtAt ?? DateTime.UtcNow,
BoughtPrice = oldAsset?.BoughtPrice ?? position.AveragePositionPrice,
Type = position.InstrumentType.ParseInstrumentType(),
Position = position.Quantity > 0 ? PositionType.Long : PositionType.Short,
BlockedItems = position.BlockedLots,

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,11 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Models.Configs;
using KLHZ.Trader.Core.Math.Declisions.Dtos.FFT;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -22,15 +18,15 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
public class TraderDataProvider
{
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
private readonly ConcurrentDictionary<string, PriceHistoryCacheUnit3> _historyCash3 = new();
private readonly InvestApiClient _investApiClient;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
private readonly ILogger<TraderDataProvider> _logger;
private readonly string[] _instrumentsFigis = [];
private readonly string[] _tradingInstrumentsFigis = [];
public readonly ConcurrentDictionary<string, IOrderbook> Orderbooks = new();
private readonly ConcurrentDictionary<string, FFTAnalyzeResult> _fftResults = new();
private readonly ConcurrentDictionary<string, string> _tickersCache = new();
private readonly ConcurrentDictionary<string, AssetType> _assetTypesCache = new();
@ -45,159 +41,77 @@ namespace KLHZ.Trader.Core.Exchange.Services
_dbContextFactory = dbContextFactory;
_logger = logger;
_instrumentsFigis = options.Value.DataRecievingInstrumentsFigis.ToArray();
_tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis.ToArray();
_isDataRecievingAllowed = options.Value.ExchangeDataRecievingEnabled;
}
public ValueTask<FFTAnalyzeResult> GetFFtResult(string figi)
{
if (_fftResults.TryGetValue(figi, out var res))
{
return ValueTask.FromResult(res);
}
return ValueTask.FromResult<FFTAnalyzeResult>(FFTAnalyzeResult.Empty);
}
public ValueTask SetFFtResult(FFTAnalyzeResult result)
{
_fftResults[result.Key] = result;
return ValueTask.CompletedTask;
}
public async ValueTask<decimal> GetLastPrice(string figi)
{
var res = 0m;
if (_historyCash.TryGetValue(figi, out var unit))
if (_historyCash3.TryGetValue(figi, out var unit))
{
res = (await unit.GetLastValues()).price;
}
return res;
}
public async ValueTask<(DateTime[] timestamps, decimal[] prices, bool isFullIntervalExists)> GetData(string figi, TimeSpan timeSpan)
{
if (_historyCash.TryGetValue(figi, out var unit))
{
var res = await unit.GetData(timeSpan);
return (res.timestamps, res.prices, res.isFullIntervalExists);
}
return (Array.Empty<DateTime>(), Array.Empty<decimal>(), false);
}
public async ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(string figi, int? length = null)
{
if (_historyCash.TryGetValue(figi, out var unit))
{
var res = await unit.GetData(length);
return (res.timestamps, res.prices);
}
return (Array.Empty<DateTime>(), Array.Empty<decimal>());
}
public async ValueTask AddData(INewPrice message, TimeSpan? clearingInterval = null)
public async ValueTask AddData(ITradeDataItem message)
{
if (message.Direction != 1) return;
if (_historyCash.TryGetValue(message.Figi, out var unit))
if (_historyCash3.TryGetValue(message.Figi, out var unit))
{
if (clearingInterval.HasValue)
{
var lasts = await unit.GetLastValues();
if (message.Time - lasts.time > clearingInterval.Value)
{
unit = new PriceHistoryCacheUnit2(message.Figi);
_historyCash[message.Figi] = unit;
}
}
await unit.AddData(message);
}
else
{
unit = new PriceHistoryCacheUnit2(message.Figi, message);
_historyCash.TryAdd(message.Figi, unit);
unit = new PriceHistoryCacheUnit3(message.Figi, message);
_historyCash3.TryAdd(message.Figi, unit);
}
}
public async ValueTask AddDataTo1MinuteWindowCache(string figi, string key, CachedValue data)
public async ValueTask AddData(string figi, string key, ITradeDataItem data)
{
if (!_historyCash.TryGetValue(figi, out var unit))
if (!_historyCash3.TryGetValue(figi, out var item))
{
unit = new PriceHistoryCacheUnit2(figi);
_historyCash.TryAdd(figi, unit);
item = new PriceHistoryCacheUnit3(figi);
_historyCash3.TryAdd(figi, item);
}
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._1_Minute);
await _historyCash3[figi].AddData(data, key);
}
public async ValueTask AddDataTo20SecondsWindowCache(string figi, string key, CachedValue data)
public ValueTask<ITradeDataItem[]> GetDataForTimeWindow(string figi, TimeSpan time, string? key = null, Func<ITradeDataItem, bool>? selector = null)
{
if (!_historyCash.TryGetValue(figi, out var unit))
if (_historyCash3.TryGetValue(figi, out var cahcheItem))
{
unit = new PriceHistoryCacheUnit2(figi);
_historyCash.TryAdd(figi, unit);
return cahcheItem.GetData(time, key: key, selector);
}
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._20_Seconds);
return ValueTask.FromResult(Array.Empty<ITradeDataItem>());
}
public async ValueTask AddDataTo5MinuteWindowCache(string figi, string key, CachedValue data)
public ValueTask<ITradeDataItem[]> GetDataFrom20SecondsWindowCache2(string figi, string key)
{
if (!_historyCash.TryGetValue(figi, out var unit))
{
unit = new PriceHistoryCacheUnit2(figi);
_historyCash.TryAdd(figi, unit);
}
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._5_Minutes);
return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(20), key);
}
public async ValueTask AddDataTo15MinuteWindowCache(string figi, string key, CachedValue data)
public ValueTask<ITradeDataItem[]> GetDataFrom1MinuteWindowCache2(string figi, string key)
{
if (!_historyCash.TryGetValue(figi, out var unit))
{
unit = new PriceHistoryCacheUnit2(figi);
_historyCash.TryAdd(figi, unit);
}
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._15_Minutes);
return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(60), key);
}
public ValueTask<CachedValue[]> GetDataFrom20SecondsWindowCache(string figi, string key)
public ValueTask<ITradeDataItem[]> GetDataFrom5MinuteWindowCache2(string figi, string key)
{
if (_historyCash.TryGetValue(figi, out var cahcheItem))
{
return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._20_Seconds);
}
return ValueTask.FromResult(Array.Empty<CachedValue>());
return GetDataForTimeWindow(figi, TimeSpan.FromMinutes(5), key);
}
public ValueTask<CachedValue[]> GetDataFrom1MinuteWindowCache(string figi, string key)
public ValueTask<ITradeDataItem[]> GetDataFrom15MinuteWindowCache2(string figi, string key)
{
if (_historyCash.TryGetValue(figi, out var cahcheItem))
{
return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._1_Minute);
}
return ValueTask.FromResult(Array.Empty<CachedValue>());
return GetDataForTimeWindow(figi, TimeSpan.FromMinutes(15), key);
}
public ValueTask<CachedValue[]> GetDataFrom5MinuteWindowCache(string figi, string key)
{
if (_historyCash.TryGetValue(figi, out var cahcheItem))
{
return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._5_Minutes);
}
return ValueTask.FromResult(Array.Empty<CachedValue>());
}
public ValueTask<CachedValue[]> GetDataFrom15MinuteWindowCache(string figi, string key)
{
if (_historyCash.TryGetValue(figi, out var cahcheItem))
{
return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._15_Minutes);
}
return ValueTask.FromResult(Array.Empty<CachedValue>());
}
public async ValueTask AddOrderbook(IOrderbook orderbook)
{
if (!_historyCash.TryGetValue(orderbook.Figi, out var unit))
if (!_historyCash3.TryGetValue(orderbook.Figi, out var unit))
{
unit = new PriceHistoryCacheUnit2(orderbook.Figi);
_historyCash.TryAdd(orderbook.Figi, unit);
unit = new PriceHistoryCacheUnit3(orderbook.Figi);
_historyCash3.TryAdd(orderbook.Figi, unit);
}
Orderbooks[orderbook.Figi] = orderbook;
await unit.AddOrderbook(orderbook);
@ -230,18 +144,18 @@ namespace KLHZ.Trader.Core.Exchange.Services
if (_isDataRecievingAllowed)
{
var time = DateTime.UtcNow.AddHours(-1.5);
var time = DateTime.UtcNow.AddHours(-20);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.PriceChanges
.Where(c => _instrumentsFigis.Contains(c.Figi) && c.Time >= time)
.Where(c => _tradingInstrumentsFigis.Contains(c.Figi) && c.Time >= time)
.OrderBy(c => c.Time)
.Select(c => new NewPriceMessage()
.Select(c => new TradeDataItem()
{
Figi = c.Figi,
Ticker = c.Ticker,
Time = c.Time,
Value = c.Value,
Price = c.Price,
IsHistoricalData = true,
Direction = c.Direction,
Count = c.Count,
@ -251,15 +165,6 @@ namespace KLHZ.Trader.Core.Exchange.Services
foreach (var price in data)
{
await AddData(price);
var cachedData = await GetData(price.Figi);
if ((DateTime.UtcNow - price.Time).TotalMinutes < 5)
{
if (ShapeAreaCalculator.TryGetAreasRelation(cachedData.timestamps, cachedData.prices, price.Value, Constants.AreasRelationWindow, out var rel))
{
await AddDataTo1MinuteWindowCache(price.Figi, Constants._1minCacheKey, new CachedValue() { Time = price.Time, Value = (decimal)rel });
}
}
}
}
@ -307,6 +212,59 @@ namespace KLHZ.Trader.Core.Exchange.Services
await _forSave.Writer.WriteAsync(declision);
}
}
internal async Task LogPrice(ITradeDataItem message, string processor, decimal value)
{
await LogPrice(new ProcessedPrice()
{
Figi = message.Figi,
Ticker = message.Ticker,
Processor = processor,
Time = message.Time,
Price = value,
}, false);
}
internal async Task LogPrice(string figi, string ticker, DateTime time, decimal value, string processor)
{
await LogPrice(new ProcessedPrice()
{
Figi = figi,
Ticker = ticker,
Processor = processor,
Time = time,
Price = value,
}, false);
}
internal async Task LogDeclision(DeclisionTradeAction action, ITradeDataItem message, decimal? profit = null)
{
await LogDeclision(new Declision()
{
AccountId = string.Empty,
Figi = message.Figi,
Ticker = message.Ticker,
Value = profit,
Price = message.Price,
Time = message.IsHistoricalData ? message.Time : DateTime.UtcNow,
Action = action,
}, false);
}
internal async Task LogDeclision(DeclisionTradeAction action, decimal price, DateTime time, ITradeDataItem message)
{
await LogDeclision(new Declision()
{
AccountId = string.Empty,
Figi = message.Figi,
Ticker = message.Ticker,
Value = price,
Price = price,
Time = time,
Action = action,
}, false);
}
private async Task WritePricesTask()
{
var buffer1 = new List<ProcessedPrice>();

View File

@ -23,10 +23,7 @@ namespace KLHZ.Trader.Core.Exchange.Utils
var time = TimeOnly.FromDateTime(dt);
if (day == DayOfWeek.Sunday || day == DayOfWeek.Saturday)
{
if (time > _openTimeHoliday && time < _closeTimeHoliday)
{
return ExchangeState.Open;
}
return ExchangeState.Close;
}
else
{

View File

@ -0,0 +1,95 @@
using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Interfaces;
using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting;
namespace KLHZ.Trader.Core.Exchange.Utils
{
internal static class TraderUtils
{
internal static Dictionary<TradingEvent, decimal> MergeResultsMult(IDictionary<TradingEvent, decimal> result, IDictionary<TradingEvent, decimal> data)
{
var res = new Dictionary<TradingEvent, decimal>();
foreach (var k in result.Keys)
{
var valRes = result[k];
var valData = data[k];
res[k] = valRes * valData;
}
return res;
}
internal static Dictionary<TradingEvent, decimal> MergeResultsMax(IDictionary<TradingEvent, decimal> result, IDictionary<TradingEvent, decimal> data)
{
var res = new Dictionary<TradingEvent, decimal>();
foreach (var k in result.Keys)
{
var valRes = result[k];
var valData = result[k];
res[k] = System.Math.Max(valRes, valData);
}
return res;
}
internal static Dictionary<TradingEvent, decimal> GetInitDict(decimal initValue)
{
var values = Enum.GetValues<TradingEvent>();
return values.ToDictionary(v => v, v => initValue);
}
internal static bool IsOperationAllowed(IManagedAccount account, decimal boutPrice, decimal count,
decimal accountCashPartFutures, decimal accountCashPart)
{
if (!BotModeSwitcher.CanPurchase()) return false;
var balance = account.Balance;
var total = account.Total;
var futures = account.Assets.Values.FirstOrDefault(v => v.Type == AssetType.Futures);
if (futures != null)
{
if ((balance - boutPrice * count) / total < accountCashPartFutures) return false;
}
else
{
if ((balance - boutPrice * count) / total < accountCashPart) return false;
}
return true;
}
internal static ITradeDataItem FilterHighFreqValues(ITradeDataItem message, Dictionary<string, List<ITradeDataItem>> pricesCache1)
{
if (!pricesCache1.TryGetValue(message.Figi, out var list))
{
list = new List<ITradeDataItem>();
pricesCache1[message.Figi] = list;
}
list.Add(message);
if ((list.Last().Time - list.First().Time).TotalSeconds < 0.5)
{
list.Add(message);
return message;
}
else
{
message = new PriceChange()
{
Figi = message.Figi,
Ticker = message.Ticker,
Count = list.Sum(l => l.Count),
Direction = message.Direction,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Price = list.Sum(l => l.Price) / list.Count
};
list.Clear();
return message;
}
}
}
}

View File

@ -2,8 +2,6 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Orders;
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Services;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using KLHZ.Trader.Service.Models;
using Microsoft.AspNetCore.Mvc;
@ -15,15 +13,13 @@ namespace KLHZ.Trader.Service.Controllers
[Route("[controller]/[action]")]
public class PlayController : ControllerBase
{
private readonly TraderDataProvider _traderDataProvider;
private readonly IDataBus _dataBus;
private readonly IDbContextFactory<TraderDbContext> _dbContextFactory;
public PlayController(IDataBus dataBus, IDbContextFactory<TraderDbContext> dbContextFactory, TraderDataProvider traderDataProvider)
public PlayController(IDataBus dataBus, IDbContextFactory<TraderDbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
_dataBus = dataBus;
_traderDataProvider = traderDataProvider;
}
[HttpGet]
@ -54,12 +50,12 @@ namespace KLHZ.Trader.Service.Controllers
var prices = await context1.PriceChanges
.Where(c => (c.Figi == figi1 || c.Figi == figi2) && c.Time >= time1 && c.Time < time2)
.OrderBy(c => c.Time)
.Select(c => new NewPriceMessage()
.Select(c => new TradeDataItem()
{
Figi = c.Figi,
Ticker = c.Ticker,
Time = c.Time,
Value = c.Value,
Price = c.Price,
IsHistoricalData = true,
Direction = c.Direction,
Count = c.Count,
@ -240,333 +236,120 @@ namespace KLHZ.Trader.Service.Controllers
}
[HttpGet]
public async Task CalcOrderbookMeanav(string figi)
public async Task RunConvolution(double? shift = null)
{
try
var timeStep = 30;
var time = DateTime.UtcNow.AddDays(-shift ?? -7).Date;
while (time < DateTime.UtcNow)
{
var t = DateTime.UtcNow.AddHours(-4);
var forSave = new List<Core.DataLayer.Entities.Prices.ProcessedPrice>();
time = time.AddMinutes(timeStep);
var figi1 = "FUTIMOEXF000";
//var figi1 = "BBG004730N88";
var figi2 = "FUTIMOEXF000";
//var figi2 = "FUTIMOEXF000";
var time2 = time;
//var time1 = new DateTime(2025, 9, 24, 11, 00, 0, DateTimeKind.Utc);
//var time2 = DateTime.UtcNow.AddMinutes(18);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.OrderbookItems
.Where(i => i.Time > t && i.Figi == figi && (i.ItemType == Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.BidsSummary4 || i.ItemType == Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.AsksSummary4))
.OrderBy(i => i.Time)
var time1 = time2.AddHours(-3);
var prices = await context1.PriceChanges
.Where(c => (c.Figi == figi1 || c.Figi == figi2) && c.Time >= time1 && c.Time < time2 && c.Direction == 1)
.OrderBy(c => c.Time)
.Select(c => new TradeDataItem()
{
Figi = c.Figi,
Ticker = c.Ticker,
Time = c.Time,
Price = c.Price,
IsHistoricalData = true,
Direction = c.Direction,
Count = c.Count,
})
.ToArrayAsync();
var bids = new LinkedList<OrderbookItem>();
var asks = new LinkedList<OrderbookItem>();
var buffer = new List<OrderbookItem>();
var dt = TimeSpan.FromMinutes(1);
var q = data.ToLookup(d => d.Time);
foreach (var d in q)
var pricesToSave = prices.Where(p => p.Time < time && p.Time >= time.AddMinutes(-timeStep)).ToArray();
if (pricesToSave.Any())
{
var pair = d.DistinctBy(www => www.ItemType).OrderBy(www => www.ItemType).ToArray();
if (pair.Length == 2)
var p1 = pricesToSave.Last();
forSave.Add(new Core.DataLayer.Entities.Prices.ProcessedPrice()
{
bids.AddLast(pair[1]);
if (bids.Last().Time - bids.First().Time > dt)
{
bids.RemoveFirst();
}
if (pair[0].Count != 0)
{
pair[1].Price = ((decimal)pair[1].Count) / ((decimal)pair[0].Count);
buffer.Add(new OrderbookItem()
{
Figi = pair[1].Figi,
Ticker = pair[1].Ticker,
Count = 1,
ItemType = Core.DataLayer.Entities.Orders.Enums.OrderbookItemType.BidsAsksSummary4_2min,
Price = bids.Sum(b => b.Price) / bids.Count,
Time = pair[1].Time,
});
}
if (buffer.Count > 10000)
{
await context1.OrderbookItems.AddRangeAsync(buffer);
await context1.SaveChangesAsync();
buffer.Clear();
}
}
else
{
}
}
if (buffer.Count > 0)
{
await context1.OrderbookItems.AddRangeAsync(buffer);
await context1.SaveChangesAsync();
buffer.Clear();
}
}
catch (Exception ex)
{
}
}
[HttpGet]
public async Task CalcTradesMeanav(string figi)
{
try
{
var t = DateTime.UtcNow.AddHours(-4);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.PriceChanges
.Where(i => i.Time > t && i.Figi == figi)
.OrderBy(i => i.Time)
.ToArrayAsync();
var sells = new LinkedList<PriceChange>();
var buys = new LinkedList<PriceChange>();
var buffer = new List<ProcessedPrice>();
var dt = TimeSpan.FromMinutes(1);
foreach (var d in data)
{
if (d.Direction == 1)
{
if (buys.Last().Time - buys.First().Time > dt)
{
buys.RemoveFirst();
}
buys.AddLast(d);
}
if (d.Direction == 2)
{
sells.AddLast(d);
if (sells.Last().Time - sells.First().Time > dt)
{
sells.RemoveFirst();
}
sells.AddLast(d);
}
if (sells.Count > 0 && buys.Count > 0)
{
var meanS = ((decimal)sells.Sum(s => s.Count)) / sells.Count;
var meanB = ((decimal)buys.Sum(s => s.Count)) / buys.Count;
if (meanS != 0)
{
buffer.Add(new ProcessedPrice()
{
Figi = d.Figi,
Processor = "tradesbalance",
Ticker = d.Ticker,
Count = 1,
Direction = 0,
Time = d.Time,
Value = meanB / meanS
});
}
}
if (buffer.Count > 10000)
{
await context1.ProcessedPrices.AddRangeAsync(buffer);
await context1.SaveChangesAsync();
buffer.Clear();
}
}
if (buffer.Count > 0)
{
await context1.ProcessedPrices.AddRangeAsync(buffer);
await context1.SaveChangesAsync();
buffer.Clear();
}
}
catch (Exception ex)
{
}
}
[HttpGet]
public async Task TestInmterpolate(string figi)
{
try
{
var t1 = DateTime.UtcNow.AddHours(-4);
var t2 = DateTime.UtcNow.AddHours(1);
//t1 = new DateTime(2025, 9, 15, 10, 1, 0, DateTimeKind.Utc);
//t2 = new DateTime(2025, 9, 15, 10, 1, 50, DateTimeKind.Utc);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.PriceChanges
.Where(i => i.Time >= t1 && i.Time <= t2 && i.Figi == figi)
.OrderBy(i => i.Time)
.ToArrayAsync();
var buffer = new List<ProcessedPrice>();
var res = SignalProcessing.InterpolateData(data.Select(d => d.Time).ToArray(), data.Select(d => d.Value).ToArray(),
TimeSpan.FromSeconds(1));
for (int i = 0; i < res.Item1.Length; i++)
{
buffer.Add(new ProcessedPrice()
{
Figi = figi,
Processor = "1secinterpol",
Ticker = data[0].Ticker,
Count = 1,
Direction = 0,
Time = res.Item1[i],
Value = (decimal)res.Item2[i]
Figi = p1.Figi,
Processor = "support_level_calc",
Ticker = p1.Ticker,
Count = p1.Count,
Direction = p1.Direction,
Time = p1.Time,
Price = p1.Price,
});
var leverage = 3;
var hist = Statistics.CalcHistogram(prices);
var convs = Statistics.CalcConvolution(hist, leverage).ToList();
var orderedConvs = convs.OrderByDescending(c => c.Sum).Take(5).ToList();
orderedConvs = orderedConvs.OrderBy(c => c.Value).ToList();
var values = new List<decimal>();
foreach (var c in orderedConvs)
{
var left = c.Value - 0.5m * (leverage - 1);
var right = c.Value + 0.5m * (leverage + 1);
if (values.Count > 0)
{
var last = values.Last();
if (last < left)
{
values.Add(left);
values.Add(right);
}
else if (last >= left && last < right)
{
values.Remove(last);
values.Add(right);
}
else if (last >= right)
{
}
}
else
{
values.Add(left);
values.Add(right);
}
}
var pairs = new List<(decimal left, decimal right)>();
for (int i = 0; i < values.Count - 1; i += 2)
{
pairs.Add((values[i], values[i + 1]));
}
foreach (var price in pricesToSave)
{
foreach (var p in pairs)
{
if (price.Price >= p.left && price.Price <= p.right)
{
forSave.Add(new Core.DataLayer.Entities.Prices.ProcessedPrice()
{
Figi = price.Figi,
Processor = "support_level",
Ticker = price.Ticker,
Count = price.Count,
Direction = price.Direction,
Time = price.Time,
Price = price.Price,
});
}
}
}
await context1.ProcessedPrices.AddRangeAsync(forSave);
await context1.SaveChangesAsync();
}
await context1.ProcessedPrices.AddRangeAsync(buffer);
await context1.SaveChangesAsync();
}
catch (Exception ex)
{
}
}
[HttpGet]
public async Task TestFFT(string figi)
{
try
{
var t1 = new DateTime(2025, 9, 15, 6, 30, 0, DateTimeKind.Utc);
var t2 = new DateTime(2025, 9, 15, 7, 50, 50, DateTimeKind.Utc);
using var context1 = await _dbContextFactory.CreateDbContextAsync();
context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var data = await context1.PriceChanges
.Where(i => i.Time >= t1 && i.Time <= t2 && i.Figi == figi)
.OrderBy(i => i.Time)
.ToArrayAsync();
var buffer = new List<ProcessedPrice>();
var values = data.Select(d => d.Value).ToArray();
var times = data.Select(d => d.Time).ToArray();
var res = SignalProcessing.InterpolateData(times, values,
TimeSpan.FromSeconds(10));
//FFT.GetMainHarmonictPeriod(res.Item2, res.Item1.Last() - res.Item1.First());
}
catch (Exception ex)
{
}
}
//[HttpGet]
//public async Task GetBalance(string figi)
//{
// try
// {
// var time1 = DateTime.UtcNow.AddDays(-7);
// using var context1 = await _dbContextFactory.CreateDbContextAsync();
// context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
// var data = await context1.PriceChanges
// .Where(c => c.Figi == figi && c.Time >= time1)
// .OrderBy(c => c.Time)
// .Select(c => new NewPriceMessage()
// {
// Figi = figi,
// Ticker = c.Ticker,
// Time = c.Time,
// Value = c.Value,
// IsHistoricalData = true
// })
// .ToArrayAsync();
// var buffer = new List<ProcessedPrice>();
// var list = new LinkedList<(DateTime time, double value)>();
// var previous = -1000d;
// foreach (var mess in data)
// {
// await _traderDataProvider.AddData(mess, TimeSpan.FromHours(6));
// var dataFromCache = await _traderDataProvider.GetData(figi, TimeSpan.FromMinutes(15));
// if (dataFromCache.isFullIntervalExists)
// {
// if (ShapeAreaCalculator.TryGetAreasRelation(dataFromCache.timestamps, dataFromCache.prices,mess.Value,out var res))
// {
// if (list.Count > 0 && mess.Time - list.Last().time > TimeSpan.FromMinutes(5))
// {
// list.Clear();
// }
// list.AddLast((mess.Time, res));
// if (list.Last().time - list.First().time > TimeSpan.FromMinutes(1))
// {
// list.RemoveFirst();
// }
// var newRes = (decimal)(list.Sum(i => i.value) / list.Count);
// if (list.Count > 0)
// {
// try
// {
// buffer.Add(new ProcessedPrice()
// {
// Figi = figi,
// Processor = "balancescalc30min",
// Ticker = mess.Ticker,
// Time = mess.Time,
// Value = newRes,
// //Value = (decimal)res,
// });
// }
// catch(Exception ex)
// {
// }
// }
// }
// }
// else
// {
// previous = -1d;
// }
// if (buffer.Count > 10000)
// {
// await context1.ProcessedPrices.AddRangeAsync(buffer);
// await context1.SaveChangesAsync();
// buffer.Clear();
// }
// }
// if (buffer.Count > 0)
// {
// await context1.ProcessedPrices.AddRangeAsync(buffer);
// await context1.SaveChangesAsync();
// buffer.Clear();
// }
// }
// catch (Exception ex)
// {
// }
//}
////[HttpGet]
//public async Task LoadTradesToHistory(string figi)
//{
// try
// {
// using var context1 = await _dbContextFactory.CreateDbContextAsync();
// context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
// var data = await context1.InstrumentTrades
// .Where(c => c.Figi == figi)
// .OrderBy(c => c.BoughtAt)
// .Select(c => new PriceChange()
// {
// Figi = figi,
// Ticker = c.Ticker,
// Time = c.BoughtAt,
// Value = c.Price,
// IsHistoricalData = true
// })
// .ToArrayAsync();
// await context1.PriceChanges.Where(p => p.Figi == figi).ExecuteDeleteAsync();
// await context1.PriceChanges.AddRangeAsync(data);
// await context1.SaveChangesAsync();
// }
// catch (Exception ex)
// {
// }
//}
}
}

View File

@ -2,11 +2,11 @@
namespace KLHZ.Trader.Service.Models
{
public class TimeSeriesData
internal class TimeSeriesData
{
public required string Figi { get; set; }
public DateTime Time { get; set; }
public INewPrice? NewPrice { get; set; }
public ITradeDataItem? NewPrice { get; set; }
public IOrderbook? Orderbook { get; set; }
}
}

View File

@ -8,17 +8,15 @@
},
"LokiUrl": "",
"ExchangeConfig": {
"StopBuyLengthMinuts": 15,
"ExchangeDataRecievingEnabled": true,
"Token": "",
"ManagingAccountNamePatterns": [ "автотрейд" ],
"DataRecievingInstrumentsFigis": [ "BBG004730N88", "FUTIMOEXF000", "FUTGMKN09250", "FUTBR1025000", "FUTNG0925000", "FUTNASD09250" ],
"TradingInstrumentsFigis": [ "FUTIMOEXF000" ],
"TradingInstrumentsFigis": [ "FUTIMOEXF000"],
"FutureComission": 0.0025,
"ShareComission": 0.0004,
"AccountCashPart": 0.05,
"AccountCashPartFutures": 0.5,
"DefaultBuyPartOfAccount": 0.3333,
"InstrumentsSettings": [
{
"Figi": "FUTIMOEXF000",