Compare commits
16 Commits
bfa3ef6605
...
022cee9a19
Author | SHA1 | Date |
---|---|---|
|
022cee9a19 | |
|
c2612ba85a | |
|
f4fb12407a | |
|
167c2ba119 | |
|
e57d67d5db | |
|
06cf8ddc19 | |
|
ffc21531b5 | |
|
8aac315338 | |
|
584e378990 | |
|
41d33356dd | |
|
e2726586e5 | |
|
9554b988e3 | |
|
64c702ebf1 | |
|
00de5bee2e | |
|
50938f093e | |
|
03b0f5582e |
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
|
||||
namespace KLHZ.Trader.Core.Contracts.Common.Enums
|
||||
{
|
||||
public enum PositionType
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
|
||||
namespace KLHZ.Trader.Core.Contracts.Common.Enums
|
||||
{
|
||||
public enum TradeDirection
|
||||
{
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface ILockableObject
|
||||
{
|
||||
public Task<bool> Lock(TimeSpan duration);
|
||||
|
||||
public void Unlock();
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace KLHZ.Trader.Core.Math.Declisions.Dtos
|
||||
{
|
||||
public class HistItem
|
||||
{
|
||||
public decimal Value { get; set; }
|
||||
public decimal Count { get; set; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
@ -191,20 +191,20 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils
|
|||
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
|
||||
if (!uptrendStartingDetectionMeanfullStep.HasValue || ((d1 <= uptrendStartingDetectionMeanfullStep
|
||||
// || d2 <= uptrendStartingDetectionMeanfullStep
|
||||
) && dt > TimeSpan.FromSeconds(10)))
|
||||
{
|
||||
res |= TradingEvent.UptrendStart;
|
||||
res |= TradingEvent.OpenLong;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
public enum DeclisionTradeAction
|
||||
{
|
||||
Unknown = 0,
|
||||
StopBuy = 1,
|
||||
StopBuyShortTime = 2,
|
||||
OpenLong = 100,
|
||||
OpenLongReal = 101,
|
||||
CloseLong = 200,
|
||||
|
|
|
@ -5,10 +5,5 @@
|
|||
Unknown = 0,
|
||||
Ask = 1,
|
||||
Bid = 2,
|
||||
AsksSummary10 = 3,
|
||||
BidsSummary10 = 4,
|
||||
AsksSummary4 = 5,
|
||||
BidsSummary4 = 6,
|
||||
BidsAsksSummary4_2min = 7,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
|
||||
{
|
||||
public enum AssetType
|
||||
{
|
||||
Unknown = 0,
|
||||
Common = 1,
|
||||
Future = 2,
|
||||
Currency = 3,
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
|
||||
{
|
||||
public enum PositionType
|
||||
{
|
||||
Unknown = 0,
|
||||
Long = 1,
|
||||
Short = 2
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
namespace KLHZ.Trader.Core.Exchange.Models.AssetsAccounting
|
||||
{
|
||||
public enum DealDirection
|
||||
{
|
||||
Unknown = 0,
|
||||
Buy = 1,
|
||||
Sell = 2
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; } = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._1_Minute);
|
||||
item = new PriceHistoryCacheUnit3(figi);
|
||||
_historyCash3.TryAdd(figi, item);
|
||||
}
|
||||
|
||||
public async ValueTask AddDataTo20SecondsWindowCache(string figi, string key, CachedValue data)
|
||||
{
|
||||
if (!_historyCash.TryGetValue(figi, out var unit))
|
||||
{
|
||||
unit = new PriceHistoryCacheUnit2(figi);
|
||||
_historyCash.TryAdd(figi, unit);
|
||||
}
|
||||
await _historyCash[figi].AddDataToTimeWindowCache(key, data, TimeWindowCacheType._20_Seconds);
|
||||
await _historyCash3[figi].AddData(data, key);
|
||||
}
|
||||
|
||||
public async ValueTask AddDataTo5MinuteWindowCache(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._5_Minutes);
|
||||
return ValueTask.FromResult(Array.Empty<ITradeDataItem>());
|
||||
}
|
||||
|
||||
public async ValueTask AddDataTo15MinuteWindowCache(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._15_Minutes);
|
||||
return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(20), key);
|
||||
}
|
||||
|
||||
public ValueTask<CachedValue[]> GetDataFrom20SecondsWindowCache(string figi, string key)
|
||||
public ValueTask<ITradeDataItem[]> GetDataFrom1MinuteWindowCache2(string figi, string key)
|
||||
{
|
||||
if (_historyCash.TryGetValue(figi, out var cahcheItem))
|
||||
return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(60), key);
|
||||
}
|
||||
public ValueTask<ITradeDataItem[]> GetDataFrom5MinuteWindowCache2(string figi, string key)
|
||||
{
|
||||
return cahcheItem.GetDataFromTimeWindowCache(key, TimeWindowCacheType._20_Seconds);
|
||||
return GetDataForTimeWindow(figi, TimeSpan.FromMinutes(5), key);
|
||||
}
|
||||
return ValueTask.FromResult(Array.Empty<CachedValue>());
|
||||
}
|
||||
|
||||
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 GetDataForTimeWindow(figi, TimeSpan.FromMinutes(15), key);
|
||||
}
|
||||
return ValueTask.FromResult(Array.Empty<CachedValue>());
|
||||
}
|
||||
|
||||
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>();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
Figi = p1.Figi,
|
||||
Processor = "support_level_calc",
|
||||
Ticker = p1.Ticker,
|
||||
Count = p1.Count,
|
||||
Direction = p1.Direction,
|
||||
Time = p1.Time,
|
||||
Price = p1.Price,
|
||||
});
|
||||
}
|
||||
if (buffer.Count > 10000)
|
||||
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)
|
||||
{
|
||||
await context1.OrderbookItems.AddRangeAsync(buffer);
|
||||
await context1.SaveChangesAsync();
|
||||
buffer.Clear();
|
||||
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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
if (buffer.Count > 0)
|
||||
{
|
||||
await context1.OrderbookItems.AddRangeAsync(buffer);
|
||||
await context1.SaveChangesAsync();
|
||||
buffer.Clear();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
values.Add(left);
|
||||
values.Add(right);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task CalcTradesMeanav(string figi)
|
||||
var pairs = new List<(decimal left, decimal right)>();
|
||||
for (int i = 0; i < values.Count - 1; i += 2)
|
||||
{
|
||||
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);
|
||||
pairs.Add((values[i], values[i + 1]));
|
||||
}
|
||||
|
||||
if (sells.Count > 0 && buys.Count > 0)
|
||||
foreach (var price in pricesToSave)
|
||||
{
|
||||
var meanS = ((decimal)sells.Sum(s => s.Count)) / sells.Count;
|
||||
var meanB = ((decimal)buys.Sum(s => s.Count)) / buys.Count;
|
||||
if (meanS != 0)
|
||||
foreach (var p in pairs)
|
||||
{
|
||||
buffer.Add(new ProcessedPrice()
|
||||
if (price.Price >= p.left && price.Price <= p.right)
|
||||
{
|
||||
Figi = d.Figi,
|
||||
Processor = "tradesbalance",
|
||||
Ticker = d.Ticker,
|
||||
Count = 1,
|
||||
Direction = 0,
|
||||
Time = d.Time,
|
||||
Value = meanB / meanS
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
});
|
||||
}
|
||||
await context1.ProcessedPrices.AddRangeAsync(buffer);
|
||||
await context1.ProcessedPrices.AddRangeAsync(forSave);
|
||||
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)
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue