Compare commits

...

4 Commits

Author SHA1 Message Date
vlad zverzhkhovskiy 8edb4351dd добавил миграцию
test / deploy_trader_prod (push) Successful in 4m33s Details
2025-09-02 14:43:43 +03:00
vlad zverzhkhovskiy 62de4169b8 Сократил объем информации по стаканам 2025-09-02 14:43:00 +03:00
vlad zverzhkhovskiy 45b34b4509 добавил сбор данных по стаканам. 2025-09-02 14:25:46 +03:00
vlad zverzhkhovskiy d634cfac73 удалил калмана 2025-09-02 12:17:19 +03:00
56 changed files with 584 additions and 482 deletions

View File

@ -0,0 +1,13 @@
namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums
{
[Flags]
public enum TradingEvent
{
None = 0,
StopBuy = 1,
LongOpen = 2,
ShortClose = 4,
LongClose = 8,
ShortOpen = 16,
}
}

View File

@ -1,6 +1,6 @@
namespace KLHZ.Trader.Core.Declisions.Dtos namespace KLHZ.Trader.Core.Contracts.Declisions.Dtos
{ {
internal readonly struct TwoPeriodsProcessingDto public readonly struct TwoPeriodsResultDto
{ {
public readonly int Start; public readonly int Start;
public readonly int Bound; public readonly int Bound;
@ -11,7 +11,7 @@
public readonly TimeSpan PeriodStart; public readonly TimeSpan PeriodStart;
public readonly TimeSpan PeriodEnd; public readonly TimeSpan PeriodEnd;
public TwoPeriodsProcessingDto(bool success, float diffStart, float diffEnd, int start, int bound, int end, public TwoPeriodsResultDto(bool success, float diffStart, float diffEnd, int start, int bound, int end,
TimeSpan periodStart, TimeSpan periodEnd) TimeSpan periodStart, TimeSpan periodEnd)
{ {
Success = success; Success = success;

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
{ {
@ -8,5 +8,8 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
public int Length { get; } public int Length { get; }
public ValueTask AddData(INewPrice priceChange); public ValueTask AddData(INewPrice priceChange);
public ValueTask<(DateTime[] timestamps, float[] prices)> GetData(); public ValueTask<(DateTime[] timestamps, float[] prices)> GetData();
public ValueTask AddOrderbook(IOrderbook orderbook);
public long AsksCount { get; }
public long BidsCount { get; }
} }
} }

View File

@ -1,9 +1,9 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
{ {
public interface ITradingEventsDetector public interface ITradingEventsDetector
{ {
public ValueTask<TradingEventsDto> Detect(IPriceHistoryCacheUnit unit); public ValueTask<TradingEvent> Detect(IPriceHistoryCacheUnit unit);
} }
} }

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{ {
public interface IMessage public interface IMessage
{ {

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{ {
public interface INewCandle public interface INewCandle
{ {

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{ {
public interface INewPrice public interface INewPrice
{ {

View File

@ -0,0 +1,13 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface IOrderbook
{
public string Ticker { get; }
public string Figi { get; }
public long AsksCount { get; }
public long BidsCount { get; }
public DateTime Time { get; }
public IOrderbookItem[] Asks { get; }
public IOrderbookItem[] Bids { get; }
}
}

View File

@ -0,0 +1,8 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{
public interface IOrderbookItem
{
public long Count { get; }
public decimal Price { get; }
}
}

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
{ {
public interface IProcessedPrice : INewPrice public interface IProcessedPrice : INewPrice
{ {

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{ {

View File

@ -0,0 +1,15 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{
public class NewOrderbookMessage : IOrderbook
{
public required string Ticker { get; init; }
public required string Figi { get; init; }
public DateTime Time { get; init; }
public IOrderbookItem[] Asks { get; init; } = [];
public IOrderbookItem[] Bids { get; init; } = [];
public long AsksCount { get; init; }
public long BidsCount { get; init; }
}
}

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos
{ {

View File

@ -1,19 +1,21 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos; using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.Threading.Channels; using System.Threading.Channels;
namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces
{ {
public interface IDataBus public interface IDataBus
{ {
public bool AddChannel(string key, Channel<IOrderbook> channel);
public bool AddChannel(string key, Channel<IProcessedPrice> channel); public bool AddChannel(string key, Channel<IProcessedPrice> channel);
public bool AddChannel(string key, Channel<INewPrice> channel); public bool AddChannel(string key, Channel<INewPrice> channel);
public bool AddChannel(string key, Channel<TradeCommand> channel); public bool AddChannel(string key, Channel<TradeCommand> channel);
public bool AddChannel(string key, Channel<IMessage> channel); public bool AddChannel(string key, Channel<IMessage> channel);
public bool AddChannel(string key, Channel<INewCandle> channel); public bool AddChannel(string key, Channel<INewCandle> channel);
public Task BroadcastNewPrice(INewPrice newPriceMessage); public Task Broadcast(INewPrice newPriceMessage);
public Task BroadcastCommand(TradeCommand command); public Task Broadcast(TradeCommand command);
public Task BroadcastNewCandle(INewCandle command); public Task Broadcast(INewCandle command);
public Task BroadcastProcessedPrice(IProcessedPrice command); public Task Broadcast(IProcessedPrice command);
public Task Broadcast(IOrderbook orderbook);
} }
} }

View File

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
//Тесты
[assembly: InternalsVisibleTo("KLHZ.Trader.Core.Tests")]

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Math.Utils namespace KLHZ.Trader.Core.Math.Common
{ {
public static class Lines public static class Lines
{ {
@ -14,7 +14,7 @@
{ {
var dtime = (float)(time2 - time1).TotalSeconds; var dtime = (float)(time2 - time1).TotalSeconds;
var dval1 = (val1_2 - val1_1); var dval1 = val1_2 - val1_1;
var k1 = dval1 / dtime; var k1 = dval1 / dtime;
var b1 = val1_1; var b1 = val1_1;

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.Declisions.Dtos namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{ {
internal readonly struct PeriodPricesInfoDto internal readonly struct PeriodPricesInfoDto
{ {

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Math.Declisions.Dtos namespace KLHZ.Trader.Core.Math.Declisions.Dtos
{ {

View File

@ -1,7 +1,7 @@
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
namespace KLHZ.Trader.Core.Math.Declisions.Services namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
{ {
public class PriceHistoryCacheUnit : IPriceHistoryCacheUnit public class PriceHistoryCacheUnit : IPriceHistoryCacheUnit
{ {
@ -20,6 +20,9 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
} }
} }
public long AsksCount => 1;
public long BidsCount => 1;
private readonly object _locker = new(); private readonly object _locker = new();
private readonly float[] Prices = new float[CacheMaxLength]; private readonly float[] Prices = new float[CacheMaxLength];
private readonly DateTime[] Timestamps = new DateTime[CacheMaxLength]; private readonly DateTime[] Timestamps = new DateTime[CacheMaxLength];
@ -56,6 +59,11 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
} }
} }
public ValueTask AddOrderbook(IOrderbook orderbook)
{
return ValueTask.CompletedTask;
}
public PriceHistoryCacheUnit(string figi, params INewPrice[] priceChanges) public PriceHistoryCacheUnit(string figi, params INewPrice[] priceChanges)
{ {
Figi = figi; Figi = figi;

View File

@ -1,8 +1,7 @@
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.Reflection;
namespace KLHZ.Trader.Core.Math.Declisions.Services namespace KLHZ.Trader.Core.Math.Declisions.Services.Cache
{ {
public class PriceHistoryCacheUnit2 : IPriceHistoryCacheUnit public class PriceHistoryCacheUnit2 : IPriceHistoryCacheUnit
{ {
@ -22,6 +21,28 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
} }
} }
public long AsksCount
{
get
{
lock (_locker)
{
return _asksCount;
}
}
}
public long BidsCount
{
get
{
lock (_locker)
{
return _bidsCount;
}
}
}
private readonly object _locker = new(); private readonly object _locker = new();
private readonly float[] Prices = new float[_arrayMaxLength]; private readonly float[] Prices = new float[_arrayMaxLength];
private readonly DateTime[] Timestamps = new DateTime[_arrayMaxLength]; private readonly DateTime[] Timestamps = new DateTime[_arrayMaxLength];
@ -29,8 +50,12 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
private int _length = 0; private int _length = 0;
private int _pointer = -1; private int _pointer = -1;
private long _asksCount = 1;
private long _bidsCount = 1;
public ValueTask AddData(INewPrice priceChange) public ValueTask AddData(INewPrice priceChange)
{ {
if (priceChange.Figi != Figi) return ValueTask.CompletedTask;
lock (_locker) lock (_locker)
{ {
_pointer++; _pointer++;
@ -57,19 +82,30 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
{ {
if (_pointer < 0) if (_pointer < 0)
{ {
return ValueTask.FromResult((Array.Empty<DateTime>(), Array.Empty <float>())); return ValueTask.FromResult((Array.Empty<DateTime>(), Array.Empty<float>()));
} }
else else
{ {
var prices = new float[_length]; var prices = new float[_length];
var timestamps = new DateTime[_length]; var timestamps = new DateTime[_length];
Array.Copy(Prices,1+ _pointer - _length, prices, 0, prices.Length); Array.Copy(Prices, 1 + _pointer - _length, prices, 0, prices.Length);
Array.Copy(Timestamps,1+ _pointer - _length, timestamps, 0, timestamps.Length); Array.Copy(Timestamps, 1 + _pointer - _length, timestamps, 0, timestamps.Length);
return ValueTask.FromResult((timestamps, prices)); return ValueTask.FromResult((timestamps, prices));
} }
} }
} }
public ValueTask AddOrderbook(IOrderbook orderbook)
{
if (orderbook.Figi != Figi) return ValueTask.CompletedTask;
lock (_locker)
{
_asksCount = orderbook.AsksCount;
_bidsCount = orderbook.BidsCount;
}
return ValueTask.CompletedTask;
}
public PriceHistoryCacheUnit2(string figi, params INewPrice[] priceChanges) public PriceHistoryCacheUnit2(string figi, params INewPrice[] priceChanges)
{ {
Figi = figi; Figi = figi;
@ -85,7 +121,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
.Skip(priceChanges.Length - CacheMaxLength) .Skip(priceChanges.Length - CacheMaxLength)
.ToArray(); .ToArray();
foreach ( var pc in selectedPriceChanges) foreach (var pc in selectedPriceChanges)
{ {
AddData(pc); AddData(pc);
} }

View File

@ -0,0 +1,14 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Utils;
namespace KLHZ.Trader.Core.Math.Declisions.Services.EventsDetection
{
public class IntervalsTradingEventsDetector : ITradingEventsDetector
{
public ValueTask<TradingEvent> Detect(IPriceHistoryCacheUnit unit)
{
return ValueTask.FromResult(TwoPeriods.Detect(unit));
}
}
}

View File

@ -1,319 +0,0 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Dtos;
using MathNet.Filtering.Kalman;
using MathNet.Numerics.LinearAlgebra;
using Microsoft.Extensions.Hosting;
using System.Threading.Channels;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace KLHZ.Trader.Core.Math.Declisions.Services
{
public class KalmanPredictor : IHostedService
{
private readonly double r = 1000.0; // Measurement covariance
private readonly PriceHistoryCacheUnit _cache = new PriceHistoryCacheUnit("");
private readonly Channel<INewPrice> _messages = Channel.CreateUnbounded<INewPrice>();
private readonly IDataBus _dataBus;
private DiscreteKalmanFilter? _dkf;
public KalmanPredictor(IDataBus bus)
{
_dataBus = bus;
bus.AddChannel(nameof(KalmanPredictor), _messages);
_ = ProcessMessages();
}
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private async Task Init()
{
}
private async Task ProcessCalman(INewPrice message)
{
var H = Matrix<double>.Build.Dense(2, 2, new[] { 1d, 0d, 0d, 1d }); // Measurement matrix
var length = _cache.Length;
if (length > 2 && _dkf != null)
{
var data = await _cache.GetData();
var dt = (data.timestamps[data.prices.Length - 1] - data.timestamps[data.prices.Length - 2]).TotalSeconds;
if (dt < 0.0000001 || dt > 1800) return;
var F = Matrix<double>.Build.Dense(2, 2, new[] { 1d, 0d, dt, 1 }); // State transition matrix
var R = Matrix<double>.Build.Dense(2, 2, new[] { r, r / dt, r / dt, 2 * r / (dt * dt) });
_dkf.Predict(F);
var state = _dkf.State;
var value = state[0, 0] + dt * state[1, 0];
if (!double.IsNaN(value))
{
await _dataBus.BroadcastProcessedPrice(new ProcessedPrice()
{
Figi = message.Figi,
Processor = nameof(KalmanPredictor) + "_predict",
Ticker = message.Ticker,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = (decimal)value,
});
}
var dprice = data.prices[data.prices.Length - 1] - data.prices[data.prices.Length - 2];
var k = dprice / dt;
var b = data.prices[data.prices.Length - 2];
var z = Matrix<double>.Build.Dense(2, 1, new[] { b, k });
_dkf.Update(z, H, R);
var state2 = _dkf.State;
var value2 = state2[0, 0] + dt * state2[1, 0];
if (!double.IsNaN(value))
{
await _dataBus.BroadcastProcessedPrice(new ProcessedPrice()
{
Figi = message.Figi,
Processor = nameof(KalmanPredictor) + "_state",
Ticker = message.Ticker,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = (decimal)value,
});
}
}
else if (length >= 2)
{
var data = await _cache.GetData();
var dprice = data.prices[data.prices.Length - 1] - data.prices[data.prices.Length - 2];
var dt = (data.timestamps[data.prices.Length - 1] - data.timestamps[data.prices.Length - 2]).TotalSeconds;
if (dt < 0.0000001) return;
var k = dprice / dt;
var b = data.prices[data.prices.Length - 2];
Matrix<double> x0 = Matrix<double>.Build.Dense(2, 1, new[] { b, k });
Matrix<double> P0 = Matrix<double>.Build.Dense(2, 2, new[] { r, r / dt, r / dt, 2 * r / (dt * dt) });
_dkf = new DiscreteKalmanFilter(x0, P0);
}
}
private async Task ProcessMovAv(INewPrice message, int window)
{
var data = await _cache.GetData();
if (data.prices.Length < window) return;
var sum = 0f;
var count = 0;
for (int i = 1; i <= window; i++)
{
sum += data.prices[data.prices.Length - i];
count++;
}
await _dataBus.BroadcastProcessedPrice(new ProcessedPrice()
{
Figi = message.Figi,
Processor = nameof(KalmanPredictor) + "_mov_av_window" + window.ToString(),
Ticker = message.Ticker,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = (decimal)(sum / count),
});
}
private static (DateTime time, float value) CalcTimeWindowAverageValue(DateTime[] timestamps, float[] values, int window, int shift = 0)
{
var sum = values[values.Length - 1 - shift];
var count = 1;
var startTime = timestamps[timestamps.Length - 1 - shift];
for (int i = 2; i < values.Length && startTime - timestamps[values.Length - i - shift] < TimeSpan.FromSeconds(window); i++)
{
sum += values[values.Length - i - shift];
count++;
}
return (startTime, sum / count);
}
private static TradingEventsDto CheckByWindowAverageMean(DateTime[] timestamps, float[] prices, int size, float meanfullStep = 3f)
{
var twav15s = new float[size];
var twav120s = new float[size];
var times = new DateTime[size];
for (int shift = 0; shift < size; shift++)
{
var twav15 = CalcTimeWindowAverageValue(timestamps, prices, 15, shift);
var twav120 = CalcTimeWindowAverageValue(timestamps, prices, 120, shift);
twav15s[size - 1 - shift] = twav15.value;
twav120s[size - 1 - shift] = twav120.value;
times[size - 1 - shift] = twav120.time;
if (shift > 0)
{
var isCrossing = Utils.Lines.IsLinesCrossing(
times[size - 1 - shift],
times[size - 2 - shift],
twav15s[size - 1 - shift],
twav15s[size - 2 - shift],
twav120s[size - 1 - shift],
twav120s[size - 2 - shift]);
if (shift == 1 && !isCrossing) //если нет пересечения скользящих средний с окном 120 и 15 секунд между
//текущей и предыдущей точкой - можно не продолжать выполнение.
{
break;
}
if (shift > 1 && isCrossing)
{
// если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта
if (twav120s[size - 1] <= twav15s[size - 1] && twav120s[size - 2] > twav15s[size - 2] )
{
if (twav15s[size - 1 - shift] - twav15s[size - 1] >= meanfullStep)
{
return new TradingEventsDto(false, true);
}
}
// если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта
if (twav15s[size - 1] <= twav120s[size - 1] && twav15s[size - 2] > twav120s[size - 2])
{
if (twav15s[size - 1 - shift] - twav15s[size - 1] <= - meanfullStep)
{
return new TradingEventsDto(true, false);
}
}
}
}
}
return new TradingEventsDto(false, false);
}
private async Task ProcessTimeWindow(INewPrice message, int window)
{
var data = await _cache.GetData();
var sum = data.prices[data.prices.Length - 1];
var count = 1;
var startTime = data.timestamps[data.prices.Length - 1];
for (int i = 2; i < data.prices.Length && startTime - data.timestamps[data.prices.Length - i] < TimeSpan.FromSeconds(window); i++)
{
sum += data.prices[data.prices.Length - i];
count++;
}
await _dataBus.BroadcastProcessedPrice(new ProcessedPrice()
{
Figi = message.Figi,
Processor = nameof(KalmanPredictor) + "_timeWindow" + window.ToString(),
Ticker = message.Ticker,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = (decimal)(sum / count),
});
var diffValue = System.Math.Abs((decimal)(sum / count) - message.Value);
await _dataBus.BroadcastProcessedPrice(new ProcessedPrice()
{
Figi = message.Figi,
Processor = nameof(KalmanPredictor) + "_diff" + window.ToString(),
Ticker = message.Ticker,
IsHistoricalData = message.IsHistoricalData,
Time = message.Time,
Value = diffValue > 3 ? diffValue : 0,
});
}
private async Task ProcessMessages()
{
while (await _messages.Reader.WaitToReadAsync())
{
var message = await _messages.Reader.ReadAsync();
await _cache.AddData(message);
try
{
var data = await _cache.GetData();
var size = 50;
var twav15s = new float[size];
var twav120s = new float[size];
var times = new DateTime[size];
for (int shift = 0; shift < size; shift++)
{
var twav15 = CalcTimeWindowAverageValue(data.timestamps, data.prices, 15, shift);
var twav120 = CalcTimeWindowAverageValue(data.timestamps, data.prices, 120, shift);
twav15s[size - 1-shift] = twav15.value;
twav120s[size - 1 - shift] = twav120.value;
times[size - 1 - shift] = twav120.time;
if (shift>0)
{
var isCrossing = Utils.Lines.IsLinesCrossing(
times[size - 1 - shift],
times[size - 2 - shift],
twav15s[size - 1 - shift],
twav15s[size - 2 - shift],
twav120s[size - 1 - shift],
twav120s[size - 2 - shift]);
if (shift == 1 && !isCrossing) //если нет пересечения скользящих средний с окном 120 и 15 секунд между
//текущей и предыдущей точкой - можно не продолжать выполнение.
{
break;
}
if (shift>1 && isCrossing)
{
// если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга
if (twav120s[size - 1] <= twav15s[size - 1] && twav120s[size - 2] > twav15s[size - 2])
{
}
// если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и открытия шорта
if (twav15s[size - 1] <= twav120s[size - 1] && twav15s[size - 2] > twav120s[size - 2])
{
}
}
}
}
//if (isCrossing && twav120_2 <= twav15_2)// если филтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга
//{
// for (int i=shift;i<111; i++)
// {
// }
//}
//if (isCrossing && twav15_2 <= twav120_2)// если филтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга
//{
//}
//await ProcessCalman(message);
await ProcessMovAv(message, 3);
await ProcessTimeWindow(message, 5);
await ProcessTimeWindow(message, 15);
await ProcessTimeWindow(message, 120);
}
catch (Exception ex)
{
}
}
}
}
}

View File

@ -0,0 +1,76 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Math.Common;
namespace KLHZ.Trader.Core.Math.Declisions.Utils
{
public static class MovingAverage
{
private static (DateTime time, float value) CalcTimeWindowAverageValue(DateTime[] timestamps, float[] values, int window, int shift = 0)
{
var sum = values[values.Length - 1 - shift];
var count = 1;
var startTime = timestamps[timestamps.Length - 1 - shift];
for (int i = 2; i < values.Length && startTime - timestamps[values.Length - i - shift] < TimeSpan.FromSeconds(window); i++)
{
sum += values[values.Length - i - shift];
count++;
}
return (startTime, sum / count);
}
public static TradingEventsDto CheckByWindowAverageMean(DateTime[] timestamps, float[] prices, int size, float meanfullStep = 3f)
{
var twav15s = new float[size];
var twav120s = new float[size];
var times = new DateTime[size];
for (int shift = 0; shift < size; shift++)
{
var twav15 = CalcTimeWindowAverageValue(timestamps, prices, 15, shift);
var twav120 = CalcTimeWindowAverageValue(timestamps, prices, 120, shift);
twav15s[size - 1 - shift] = twav15.value;
twav120s[size - 1 - shift] = twav120.value;
times[size - 1 - shift] = twav120.time;
if (shift > 0)
{
var isCrossing = Lines.IsLinesCrossing(
times[size - 1 - shift],
times[size - 2 - shift],
twav15s[size - 1 - shift],
twav15s[size - 2 - shift],
twav120s[size - 1 - shift],
twav120s[size - 2 - shift]);
if (shift == 1 && !isCrossing) //если нет пересечения скользящих средний с окном 120 и 15 секунд между
//текущей и предыдущей точкой - можно не продолжать выполнение.
{
break;
}
if (shift > 1 && isCrossing)
{
// если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта
if (twav120s[size - 1] <= twav15s[size - 1] && twav120s[size - 2] > twav15s[size - 2])
{
if (twav15s[size - 1 - shift] - twav15s[size - 1] >= meanfullStep)
{
return new TradingEventsDto(false, true);
}
}
// если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта
if (twav15s[size - 1] <= twav120s[size - 1] && twav15s[size - 2] > twav120s[size - 2])
{
if (twav15s[size - 1 - shift] - twav15s[size - 1] <= -meanfullStep)
{
return new TradingEventsDto(true, false);
}
}
}
}
}
return new TradingEventsDto(false, false);
}
}
}

View File

@ -1,10 +1,15 @@
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Declisions.Dtos; using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Math.Declisions.Services; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Math.Declisions.Dtos;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
namespace KLHZ.Trader.Core.Declisions.Utils namespace KLHZ.Trader.Core.Math.Declisions.Utils
{ {
internal static class HistoryProcessingInstruments /// <summary>
/// Обработка последних интервалов истории изменения цен.
/// </summary>
public static class TwoPeriods
{ {
internal static PeriodPricesInfoDto GetPriceDiffForTimeSpan(this IPriceHistoryCacheUnit unit, TimeSpan timeShift, TimeSpan timeSpan, int? pointsShift = null) internal static PeriodPricesInfoDto GetPriceDiffForTimeSpan(this IPriceHistoryCacheUnit unit, TimeSpan timeShift, TimeSpan timeSpan, int? pointsShift = null)
{ {
@ -25,7 +30,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
int count = 0; int count = 0;
for (int i = times.Length - 1; i > -1; i--) for (int i = times.Length - 1; i > -1; i--)
{ {
if ((times[i] <= intervalEnd || (pointsShift.HasValue && count < pointsShift.Value)) && intervaEndIndex < 0) if ((times[i] <= intervalEnd || pointsShift.HasValue && count < pointsShift.Value) && intervaEndIndex < 0)
{ {
intervaEndIndex = i; intervaEndIndex = i;
} }
@ -88,7 +93,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
if (k2 == 0 && k1 != 0) return 1000; if (k2 == 0 && k1 != 0) return 1000;
return (float)(k1 / k2); return (float)(k1 / k2);
} }
internal static float CalcTrendRelationAbs(TwoPeriodsProcessingDto data) internal static float CalcTrendRelationAbs(TwoPeriodsResultDto data)
{ {
var k1 = System.Math.Abs(data.DiffStart) / System.Math.Abs(data.PeriodStart.TotalSeconds); var k1 = System.Math.Abs(data.DiffStart) / System.Math.Abs(data.PeriodStart.TotalSeconds);
var k2 = System.Math.Abs(data.DiffEnd) / System.Math.Abs(data.PeriodEnd.TotalSeconds); var k2 = System.Math.Abs(data.DiffEnd) / System.Math.Abs(data.PeriodEnd.TotalSeconds);
@ -127,7 +132,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
var trendRelation = CalcTrendRelationAbs(startDiff, endDiff); var trendRelation = CalcTrendRelationAbs(startDiff, endDiff);
var isEndLocal = endDiff.PeriodDiff == 0; var isEndLocal = endDiff.PeriodDiff == 0;
var res = totalDiff.Success && isStartGrows && (isEndStable || isEndFalls) && (trendRelation >= 2 && !isEndLocal); var res = totalDiff.Success && isStartGrows && (isEndStable || isEndFalls) && trendRelation >= 2 && !isEndLocal;
if (res) if (res)
{ {
@ -180,9 +185,9 @@ namespace KLHZ.Trader.Core.Declisions.Utils
return res; return res;
} }
internal static TwoPeriodsProcessingDto GetTwoPeriodsProcessingData(this (DateTime[] timestamps, float[] prices) data, TimeSpan shift, int shiftPointsStart, int shiftPointsEnd, TimeSpan firstPeriod, float meanfullDiff) internal static TwoPeriodsResultDto GetTwoPeriodsProcessingData(this (DateTime[] timestamps, float[] prices) data, TimeSpan shift, int shiftPointsStart, int shiftPointsEnd, TimeSpan firstPeriod, float meanfullDiff)
{ {
var res = new TwoPeriodsProcessingDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero); var res = new TwoPeriodsResultDto(success: false, 0, 0, 0, 0, 0, TimeSpan.Zero, TimeSpan.Zero);
var time = data.timestamps; var time = data.timestamps;
var prices = data.prices; var prices = data.prices;
int count = -1; int count = -1;
@ -198,7 +203,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
bound = i; bound = i;
shift = lastTime - time[i]; shift = lastTime - time[i];
} }
if (((lastTime - time[i]) >= shift + firstPeriod)) if (lastTime - time[i] >= shift + firstPeriod)
{ {
start = i; start = i;
@ -212,12 +217,12 @@ namespace KLHZ.Trader.Core.Declisions.Utils
{ {
var diff1 = prices[bound] - prices[start]; var diff1 = prices[bound] - prices[start];
var diff2 = prices[end] - prices[bound]; var diff2 = prices[end] - prices[bound];
res = new TwoPeriodsProcessingDto(true, diff1, diff2, start, bound, end, time[bound] - time[start], time[end] - time[bound]); res = new TwoPeriodsResultDto(true, diff1, diff2, start, bound, end, time[bound] - time[start], time[end] - time[bound]);
} }
return res; return res;
} }
internal static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd) public static bool CheckLongClose(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd)
{ {
var data = unit.GetData().Result; var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff); var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff);
@ -239,7 +244,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
{ {
} }
return isStartOk && isEndOk && (data.prices[periodStat.End] - data.prices[periodStat.Start] >= meanfullDiff); return isStartOk && isEndOk && data.prices[periodStat.End] - data.prices[periodStat.Start] >= meanfullDiff;
} }
internal static bool CheckUptrendStarting2(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff) internal static bool CheckUptrendStarting2(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff)
@ -293,7 +298,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
} }
internal static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd) public static bool CheckLongOpen(this IPriceHistoryCacheUnit unit, TimeSpan firstPeriod, TimeSpan secondPeriod, float meanfullDiff, int pointsStart, int pointsEnd)
{ {
var data = unit.GetData().Result; var data = unit.GetData().Result;
var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff); var periodStat = data.GetTwoPeriodsProcessingData(secondPeriod, pointsStart, pointsEnd, firstPeriod, meanfullDiff);
@ -315,7 +320,46 @@ namespace KLHZ.Trader.Core.Declisions.Utils
{ {
} }
return isStartOk && isEndOk && (data.prices[periodStat.Start] - data.prices[periodStat.End] >= meanfullDiff); return isStartOk && isEndOk && data.prices[periodStat.Start] - data.prices[periodStat.End] >= meanfullDiff;
}
public static TradingEvent Detect(IPriceHistoryCacheUnit data)
{
float meanfullDiff;
if (data.Figi == "BBG004730N88")
{
meanfullDiff = 0.05f;
}
else if (data.Figi == "FUTIMOEXF000")
{
meanfullDiff = 1f;
}
else
{
return TradingEvent.None;
}
var res = TradingEvent.None;
//var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 8, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 15, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 15, 5);
uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2;
if (uptrendStarts)
{
res |= TradingEvent.LongClose;
}
//var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff);
var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5f, 8, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8);
uptrendEnds |= uptrendEnds2;
if (uptrendEnds)
{
res |= TradingEvent.LongOpen;
}
return res;
} }
} }
} }

View File

@ -7,8 +7,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MathNet.Filtering.Kalman" Version="0.7.0" />
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.8" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.8" />
</ItemGroup> </ItemGroup>

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Math.Declisions.Services; using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
namespace KLHZ.Trader.Core.Tests namespace KLHZ.Trader.Core.Tests
{ {
@ -208,7 +208,7 @@ namespace KLHZ.Trader.Core.Tests
public void Test9() public void Test9()
{ {
var cacheUnit = new PriceHistoryCacheUnit2(""); var cacheUnit = new PriceHistoryCacheUnit2("");
for(int i= 0; i < 5*PriceHistoryCacheUnit2.CacheMaxLength; i++) 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 = "", Value = i, Time = DateTime.UtcNow });
if (i >= 500) if (i >= 500)

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Math.Declisions.Services; using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
namespace KLHZ.Trader.Core.Tests namespace KLHZ.Trader.Core.Tests
{ {

View File

@ -1,6 +1,6 @@
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Declisions.Utils; using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Services; using KLHZ.Trader.Core.Math.Declisions.Utils;
namespace KLHZ.Trader.Core.Tests namespace KLHZ.Trader.Core.Tests
{ {
@ -45,7 +45,7 @@ namespace KLHZ.Trader.Core.Tests
var periodLength = 4; var periodLength = 4;
var shift = 0; var shift = 0;
var result = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result = TwoPeriods.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength))); var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift); var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
@ -79,7 +79,7 @@ namespace KLHZ.Trader.Core.Tests
var periodLength = 4; var periodLength = 4;
var shift = 0; var shift = 0;
var result = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result = TwoPeriods.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength))); var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift); var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
@ -114,7 +114,7 @@ namespace KLHZ.Trader.Core.Tests
var periodLength = 4; var periodLength = 4;
var shift = 1; var shift = 1;
var result = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result = TwoPeriods.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength))); var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift); var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
@ -149,7 +149,7 @@ namespace KLHZ.Trader.Core.Tests
var periodLength = 4; var periodLength = 4;
var shift = 0; var shift = 0;
var result = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result = TwoPeriods.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength))); var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift); var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
@ -184,7 +184,7 @@ namespace KLHZ.Trader.Core.Tests
var periodLength = 4; var periodLength = 4;
var shift = 3; var shift = 3;
var result = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result = TwoPeriods.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength))); var maxValue = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift); var minValue = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
@ -219,7 +219,7 @@ namespace KLHZ.Trader.Core.Tests
var periodLength = 4; var periodLength = 4;
var shift = 3; var shift = 3;
var result = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result = TwoPeriods.GetPriceDiffForTimeSpan(unit, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue1 = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength))); var maxValue1 = startValue + (step > 0 ? (step * count) - step * shift : (step * count) - (step * (shift + periodLength)));
var minValue1 = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift); var minValue1 = startValue + (step > 0 ? (step * count) - (step * (shift + periodLength)) : (step * count) - step * shift);
@ -260,7 +260,7 @@ namespace KLHZ.Trader.Core.Tests
} }
var result2 = HistoryProcessingInstruments.GetPriceDiffForTimeSpan(unit2, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength)); var result2 = TwoPeriods.GetPriceDiffForTimeSpan(unit2, TimeSpan.FromSeconds(shift), TimeSpan.FromSeconds(periodLength));
var maxValue2 = 100; var maxValue2 = 100;
var minValue2 = -100; var minValue2 = -100;

View File

@ -1,3 +1,5 @@
using KLHZ.Trader.Core.Math.Common;
namespace KLHZ.Trader.Core.Tests namespace KLHZ.Trader.Core.Tests
{ {
public class LinesProcessingTest public class LinesProcessingTest
@ -14,7 +16,7 @@ namespace KLHZ.Trader.Core.Tests
var val2_1 = -0.5f; var val2_1 = -0.5f;
var val2_2 = 0.5f; var val2_2 = 0.5f;
Assert.IsTrue(KLHZ.Trader.Core.Math.Utils.Lines.IsLinesCrossing(time1, time2, val1_1, val1_2, val2_1, val2_2)); Assert.IsTrue(Lines.IsLinesCrossing(time1, time2, val1_1, val1_2, val2_1, val2_2));
} }
[Test] [Test]
@ -29,7 +31,7 @@ namespace KLHZ.Trader.Core.Tests
var val2_1 = 0.5f; var val2_1 = 0.5f;
var val2_2 = -0.5f; var val2_2 = -0.5f;
Assert.IsFalse(KLHZ.Trader.Core.Math.Utils.Lines.IsLinesCrossing(time1, time2, val1_1, val1_2, val2_1, val2_2)); Assert.IsFalse(Lines.IsLinesCrossing(time1, time2, val1_1, val1_2, val2_1, val2_2));
} }
} }
} }

View File

@ -1,5 +1,5 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos; using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Channels; using System.Threading.Channels;
@ -8,6 +8,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
{ {
public class DataBus : IDataBus public class DataBus : IDataBus
{ {
private readonly ConcurrentDictionary<string, Channel<IOrderbook>> _orderbooksChannels = new();
private readonly ConcurrentDictionary<string, Channel<IMessage>> _messagesChannels = new(); private readonly ConcurrentDictionary<string, Channel<IMessage>> _messagesChannels = new();
private readonly ConcurrentDictionary<string, Channel<INewCandle>> _candlesChannels = new(); private readonly ConcurrentDictionary<string, Channel<INewCandle>> _candlesChannels = new();
private readonly ConcurrentDictionary<string, Channel<INewPrice>> _priceChannels = new(); private readonly ConcurrentDictionary<string, Channel<INewPrice>> _priceChannels = new();
@ -39,7 +40,12 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
return _commandChannels.TryAdd(key, channel); return _commandChannels.TryAdd(key, channel);
} }
public async Task BroadcastNewPrice(INewPrice newPriceMessage) public bool AddChannel(string key, Channel<IOrderbook> channel)
{
return _orderbooksChannels.TryAdd(key, channel);
}
public async Task Broadcast(INewPrice newPriceMessage)
{ {
foreach (var channel in _priceChannels.Values) foreach (var channel in _priceChannels.Values)
{ {
@ -47,7 +53,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
} }
} }
public async Task BroadcastProcessedPrice(IProcessedPrice mess) public async Task Broadcast(IProcessedPrice mess)
{ {
foreach (var channel in _processedPricesChannels.Values) foreach (var channel in _processedPricesChannels.Values)
{ {
@ -55,7 +61,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
} }
} }
public async Task BroadcastNewCandle(INewCandle newPriceMessage) public async Task Broadcast(INewCandle newPriceMessage)
{ {
foreach (var channel in _candlesChannels.Values) foreach (var channel in _candlesChannels.Values)
{ {
@ -63,12 +69,20 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
} }
} }
public async Task BroadcastCommand(TradeCommand command) public async Task Broadcast(TradeCommand command)
{ {
foreach (var channel in _commandChannels.Values) foreach (var channel in _commandChannels.Values)
{ {
await channel.Writer.WriteAsync(command); await channel.Writer.WriteAsync(command);
} }
} }
public async Task Broadcast(IOrderbook orderbook)
{
foreach (var channel in _orderbooksChannels.Values)
{
await channel.Writer.WriteAsync(orderbook);
}
}
} }
} }

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -6,7 +6,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Threading.Channels; using System.Threading.Channels;
namespace KLHZ.Trader.Core.Declisions.Services namespace KLHZ.Trader.Core.Common.Messaging.Services
{ {
public class ProcessedPricesLogger : IHostedService public class ProcessedPricesLogger : IHostedService
{ {
@ -44,7 +44,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
Value = message.Value, Value = message.Value,
}); });
if (buffer.Count > 10000 || (DateTime.UtcNow - lastWrite) > TimeSpan.FromSeconds(5) || _channel.Reader.Count == 0) if (buffer.Count > 10000 || DateTime.UtcNow - lastWrite > TimeSpan.FromSeconds(5) || _channel.Reader.Count == 0)
{ {
lastWrite = DateTime.UtcNow; lastWrite = DateTime.UtcNow;
using var context = await _dbContextFactory.CreateDbContextAsync(); using var context = await _dbContextFactory.CreateDbContextAsync();

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema; using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Declisions namespace KLHZ.Trader.Core.DataLayer.Entities.Declisions
{ {

View File

@ -1,4 +1,4 @@
namespace KLHZ.Trader.Core.DataLayer.Entities.Declisions namespace KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums
{ {
public enum DeclisionTradeAction public enum DeclisionTradeAction
{ {

View File

@ -0,0 +1,11 @@
namespace KLHZ.Trader.Core.DataLayer.Entities.Orders.Enums
{
public enum OrderbookItemType
{
Unknown = 0,
Ask = 1,
Bid = 2,
AsksSummary = 3,
BidsSummary = 4
}
}

View File

@ -0,0 +1,32 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.DataLayer.Entities.Orders.Enums;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Orders
{
[Table("orderbook_items")]
public class OrderbookItem : IOrderbookItem
{
[Column("id")]
public long Id { get; set; }
[Column("time")]
public DateTime Time { get; set; }
[Column("price")]
public decimal Price { get; set; }
[Column("count")]
public long Count { get; set; }
[Column("figi")]
public required string Figi { get; set; }
[Column("ticker")]
public required string Ticker { get; set; }
[Column("item_type")]
public OrderbookItemType ItemType { get; set; }
}
}

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices namespace KLHZ.Trader.Core.DataLayer.Entities.Prices

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices namespace KLHZ.Trader.Core.DataLayer.Entities.Prices

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices namespace KLHZ.Trader.Core.DataLayer.Entities.Prices

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema; using KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
{ {

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations.Schema; using KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums;
using System.ComponentModel.DataAnnotations.Schema;
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
{ {

View File

@ -1,4 +1,5 @@
using KLHZ.Trader.Core.DataLayer.Entities.Declisions; 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.Prices;
using KLHZ.Trader.Core.DataLayer.Entities.Trades; using KLHZ.Trader.Core.DataLayer.Entities.Trades;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -14,6 +15,7 @@ namespace KLHZ.Trader.Core.DataLayer
public DbSet<PriceChange> PriceChanges { get; set; } public DbSet<PriceChange> PriceChanges { get; set; }
public DbSet<ProcessedPrice> ProcessedPrices { get; set; } public DbSet<ProcessedPrice> ProcessedPrices { get; set; }
public DbSet<Candle> Candles { get; set; } public DbSet<Candle> Candles { get; set; }
public DbSet<OrderbookItem> OrderbookItems { get; set; }
public TraderDbContext(DbContextOptions<TraderDbContext> options) public TraderDbContext(DbContextOptions<TraderDbContext> options)
: base(options) : base(options)
{ {
@ -60,6 +62,15 @@ namespace KLHZ.Trader.Core.DataLayer
v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
}); });
modelBuilder.Entity<OrderbookItem>(entity =>
{
entity.HasKey(e1 => e1.Id);
entity.Property(e => e.Time)
.HasConversion(
v => v.ToUniversalTime(),
v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
});
modelBuilder.Entity<ProcessedPrice>(entity => modelBuilder.Entity<ProcessedPrice>(entity =>
{ {
entity.HasKey(e1 => e1.Id); entity.HasKey(e1 => e1.Id);

View File

@ -1,41 +0,0 @@
using KLHZ.Trader.Core.Contracts.Declisions.Dtos;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Declisions.Utils;
namespace KLHZ.Trader.Core.Declisions.Services
{
public class TradingEventsDetector : ITradingEventsDetector
{
public async ValueTask<TradingEventsDto> Detect(IPriceHistoryCacheUnit data)
{
await Task.Delay(0);
float meanfullDiff;
if (data.Figi == "BBG004730N88")
{
meanfullDiff = 0.05f;
}
else if (data.Figi == "FUTIMOEXF000")
{
meanfullDiff = 1f;
}
else
{
return TradingEventsDto.Empty;
}
//var downtrendStarts = data.CheckDowntrendStarting(TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(7), meanfullDiff);
var uptrendStarts = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(7), meanfullDiff, 8, 3);
var uptrendStarts2 = data.CheckLongOpen(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), meanfullDiff, 15, 2);
var downtrendEnds = data.CheckLongOpen(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(10), meanfullDiff, 15, 5);
uptrendStarts |= downtrendEnds;
uptrendStarts |= uptrendStarts2;
//var downtrendEnds = data.CheckDowntrendEnding(TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(15), meanfullDiff);
var uptrendEnds = data.CheckLongClose(TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(20), meanfullDiff * 1.5f, 8, 8);
var uptrendEnds2 = data.CheckLongClose(TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(30), meanfullDiff, 15, 8);
uptrendEnds |= uptrendEnds2;
return new TradingEventsDto(uptrendEnds, uptrendStarts);
}
}
}

View File

@ -8,7 +8,8 @@
public decimal AccountCashPart { get; set; } public decimal AccountCashPart { get; set; }
public decimal AccountCashPartFutures { get; set; } public decimal AccountCashPartFutures { get; set; }
public decimal DefaultBuyPartOfAccount { get; set; } public decimal DefaultBuyPartOfAccount { get; set; }
public string[] AllowedInstrumentsFigis { get; set; } = []; public string[] DataRecievingInstrumentsFigis { get; set; } = [];
public string[] TradingInstrumentsFigis { get; set; } = [];
public string[] ManagingAccountNamePatterns { get; set; } = []; public string[] ManagingAccountNamePatterns { get; set; } = [];
} }
} }

View File

@ -1,6 +1,8 @@
using Grpc.Core; using Grpc.Core;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Orders;
using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.DataLayer.Entities.Trades; using KLHZ.Trader.Core.DataLayer.Entities.Trades;
using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Extentions;
@ -33,7 +35,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
_eventBus = eventBus; _eventBus = eventBus;
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
_investApiClient = investApiClient; _investApiClient = investApiClient;
_instrumentsFigis = options.Value.AllowedInstrumentsFigis.ToArray(); _instrumentsFigis = options.Value.DataRecievingInstrumentsFigis.ToArray();
_logger = logger; _logger = logger;
_managedAccountNamePatterns = options.Value.ManagingAccountNamePatterns.ToArray(); _managedAccountNamePatterns = options.Value.ManagingAccountNamePatterns.ToArray();
} }
@ -51,7 +53,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var shares = await _investApiClient.Instruments.SharesAsync(); var shares = await _investApiClient.Instruments.SharesAsync();
foreach (var share in shares.Instruments) foreach (var share in shares.Instruments)
{ {
//if (_instrumentsFigis.Contains(share.Figi)) if (_instrumentsFigis.Contains(share.Figi))
{ {
_tickersCache.TryAdd(share.Figi, share.Ticker); _tickersCache.TryAdd(share.Figi, share.Ticker);
} }
@ -59,7 +61,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
var futures = await _investApiClient.Instruments.FuturesAsync(); var futures = await _investApiClient.Instruments.FuturesAsync();
foreach (var future in futures.Instruments) foreach (var future in futures.Instruments)
{ {
//if (_instrumentsFigis.Contains(future.Figi)) if (_instrumentsFigis.Contains(future.Figi))
{ {
_tickersCache.TryAdd(future.Figi, future.Ticker); _tickersCache.TryAdd(future.Figi, future.Ticker);
} }
@ -100,6 +102,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
SubscriptionAction = SubscriptionAction.Subscribe SubscriptionAction = SubscriptionAction.Subscribe
}; };
var bookRequest = new SubscribeOrderBookRequest
{
SubscriptionAction = SubscriptionAction.Subscribe
};
foreach (var f in _instrumentsFigis) foreach (var f in _instrumentsFigis)
{ {
request.Instruments.Add( request.Instruments.Add(
@ -113,24 +120,31 @@ namespace KLHZ.Trader.Core.Exchange.Services
{ {
InstrumentId = f InstrumentId = f
}); });
bookRequest.Instruments.Add(
new OrderBookInstrument()
{
InstrumentId = f,
Depth = 10
});
} }
await stream.RequestStream.WriteAsync(new MarketDataRequest await stream.RequestStream.WriteAsync(new MarketDataRequest
{ {
SubscribeLastPriceRequest = request, SubscribeLastPriceRequest = request,
});
await stream.RequestStream.WriteAsync(new MarketDataRequest
{
SubscribeTradesRequest = tradesRequest, SubscribeTradesRequest = tradesRequest,
SubscribeOrderBookRequest = bookRequest
}); });
using var context = await _dbContextFactory.CreateDbContextAsync(); using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var pricesBuffer = new List<PriceChange>(); var pricesBuffer = new List<PriceChange>();
var orderbookItemsBuffer = new List<OrderbookItem>();
var tradesBuffer = new List<InstrumentTrade>(); var tradesBuffer = new List<InstrumentTrade>();
var lastWritePrices = DateTime.UtcNow; var lastWriteOrderbooks = DateTime.UtcNow;
var lastWriteTrades = DateTime.UtcNow; var lastWriteTrades = DateTime.UtcNow;
var lastWritePrices = DateTime.UtcNow;
var lastWrite = DateTime.UtcNow;
await foreach (var response in stream.ResponseStream.ReadAllAsync()) await foreach (var response in stream.ResponseStream.ReadAllAsync())
{ {
if (response.LastPrice != null) if (response.LastPrice != null)
@ -143,7 +157,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
Value = response.LastPrice.Price, Value = response.LastPrice.Price,
IsHistoricalData = false, IsHistoricalData = false,
}; };
await _eventBus.BroadcastNewPrice(message); await _eventBus.Broadcast(message);
pricesBuffer.Add(message); pricesBuffer.Add(message);
} }
@ -156,24 +170,62 @@ namespace KLHZ.Trader.Core.Exchange.Services
Ticker = GetTickerByFigi(response.Trade.Figi), Ticker = GetTickerByFigi(response.Trade.Figi),
Price = response.Trade.Price, Price = response.Trade.Price,
Count = response.Trade.Quantity, Count = response.Trade.Quantity,
Direction = response.Trade.Direction == Tinkoff.InvestApi.V1.TradeDirection.Sell ? KLHZ.Trader.Core.DataLayer.Entities.Trades.TradeDirection.Sell : KLHZ.Trader.Core.DataLayer.Entities.Trades.TradeDirection.Buy, Direction = response.Trade.Direction == Tinkoff.InvestApi.V1.TradeDirection.Sell ? DataLayer.Entities.Trades.Enums.TradeDirection.Sell : DataLayer.Entities.Trades.Enums.TradeDirection.Buy,
}; };
tradesBuffer.Add(trade); tradesBuffer.Add(trade);
} }
if (response.Orderbook != null)
//if (pricesBuffer.Count > 200 || (DateTime.UtcNow - lastWritePrices).TotalSeconds > 10)
{ {
lastWritePrices = DateTime.UtcNow; var asksSummary = new OrderbookItem()
await context.PriceChanges.AddRangeAsync(pricesBuffer); {
pricesBuffer.Clear(); Figi = response.Orderbook.Figi,
await context.SaveChangesAsync(); Ticker = GetTickerByFigi(response.Orderbook.Figi),
Count = response.Orderbook.Asks.Sum(a => (int)a.Quantity),
ItemType = DataLayer.Entities.Orders.Enums.OrderbookItemType.AsksSummary,
Time = response.Orderbook.Time.ToDateTime().ToUniversalTime(),
};
var bidsSummary = new OrderbookItem()
{
Figi = response.Orderbook.Figi,
Ticker = GetTickerByFigi(response.Orderbook.Figi),
Count = response.Orderbook.Bids.Sum(a => (int)a.Quantity),
ItemType = DataLayer.Entities.Orders.Enums.OrderbookItemType.BidsSummary,
Time = response.Orderbook.Time.ToDateTime().ToUniversalTime(),
};
orderbookItemsBuffer.Add(asksSummary);
orderbookItemsBuffer.Add(bidsSummary);
var message = new NewOrderbookMessage()
{
Ticker = GetTickerByFigi(response.Orderbook.Figi),
Figi = response.Orderbook.Figi,
Time = response.Orderbook.Time.ToDateTime().ToUniversalTime(),
AsksCount = asksSummary.Count,
BidsCount = bidsSummary.Count,
};
await _eventBus.Broadcast(message);
} }
//if (tradesBuffer.Count > 200 || (DateTime.UtcNow - lastWriteTrades).TotalSeconds > 10)
if (orderbookItemsBuffer.Count + pricesBuffer.Count + tradesBuffer.Count > 1000 || (DateTime.UtcNow - lastWrite).TotalSeconds > 10)
{ {
lastWriteTrades = DateTime.UtcNow; lastWrite = DateTime.UtcNow;
await context.InstrumentTrades.AddRangeAsync(tradesBuffer); if (orderbookItemsBuffer.Count > 0)
tradesBuffer.Clear(); {
await context.OrderbookItems.AddRangeAsync(orderbookItemsBuffer);
orderbookItemsBuffer.Clear();
}
if (pricesBuffer.Count > 0)
{
await context.PriceChanges.AddRangeAsync(pricesBuffer);
pricesBuffer.Clear();
}
if (tradesBuffer.Count > 0)
{
await context.InstrumentTrades.AddRangeAsync(tradesBuffer);
tradesBuffer.Clear();
}
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
} }

View File

@ -236,9 +236,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
BoughtAt = DateTime.UtcNow, BoughtAt = DateTime.UtcNow,
Count = res.LotsExecuted, Count = res.LotsExecuted,
Price = res.ExecutedOrderPrice, Price = res.ExecutedOrderPrice,
Position = DataLayer.Entities.Trades.PositionType.Long, Position = DataLayer.Entities.Trades.Enums.PositionType.Long,
Direction = DataLayer.Entities.Trades.TradeDirection.Buy, Direction = DataLayer.Entities.Trades.Enums.TradeDirection.Buy,
Asset = DataLayer.Entities.Trades.AssetType.Common, Asset = DataLayer.Entities.Trades.Enums.AssetType.Common,
}; };
await context.Trades.AddAsync(newTrade); await context.Trades.AddAsync(newTrade);

View File

@ -1,25 +1,26 @@
using KLHZ.Trader.Core.Common; using KLHZ.Trader.Core.Common;
using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Enums;
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.DataLayer.Entities.Declisions; using KLHZ.Trader.Core.DataLayer.Entities.Declisions;
using KLHZ.Trader.Core.Exchange; using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums;
using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models; using KLHZ.Trader.Core.Exchange.Models;
using KLHZ.Trader.Core.Exchange.Services; using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Services;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Channels; using System.Threading.Channels;
using Tinkoff.InvestApi; using Tinkoff.InvestApi;
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetType; using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetType;
namespace KLHZ.Trader.Core.Declisions.Services namespace KLHZ.Trader.Core.Exchange.Services
{ {
public class Trader : IHostedService public class Trader : IHostedService
{ {
@ -31,6 +32,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new(); private readonly ConcurrentDictionary<string, ManagedAccount> Accounts = new();
private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new(); private readonly ConcurrentDictionary<string, IPriceHistoryCacheUnit> _historyCash = new();
private readonly ITradingEventsDetector _tradingEventsDetector; private readonly ITradingEventsDetector _tradingEventsDetector;
private readonly ILogger<Trader> _logger;
private readonly decimal _futureComission; private readonly decimal _futureComission;
@ -41,8 +43,10 @@ namespace KLHZ.Trader.Core.Declisions.Services
private readonly string[] _managedAccountsNamePatterns = []; private readonly string[] _managedAccountsNamePatterns = [];
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>(); private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>();
public Trader( public Trader(
ILogger<Trader> logger,
ITradingEventsDetector tradingEventsDetector, ITradingEventsDetector tradingEventsDetector,
BotModeSwitcher botModeSwitcher, BotModeSwitcher botModeSwitcher,
IServiceProvider provider, IServiceProvider provider,
@ -51,6 +55,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
IDbContextFactory<TraderDbContext> dbContextFactory, IDbContextFactory<TraderDbContext> dbContextFactory,
InvestApiClient investApiClient) InvestApiClient investApiClient)
{ {
_logger = logger;
_tradingEventsDetector = tradingEventsDetector; _tradingEventsDetector = tradingEventsDetector;
_botModeSwitcher = botModeSwitcher; _botModeSwitcher = botModeSwitcher;
_dataBus = dataBus; _dataBus = dataBus;
@ -86,10 +91,12 @@ namespace KLHZ.Trader.Core.Declisions.Services
} }
_dataBus.AddChannel(nameof(Trader), _pricesChannel); _dataBus.AddChannel(nameof(Trader), _pricesChannel);
_ = ProcessMessages(); _dataBus.AddChannel(nameof(Trader), _ordersbookChannel);
_ = ProcessPrices();
_ = ProcessOrdersbooks();
} }
private async Task ProcessMessages() private async Task ProcessPrices()
{ {
while (await _pricesChannel.Reader.WaitToReadAsync()) while (await _pricesChannel.Reader.WaitToReadAsync())
{ {
@ -107,7 +114,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
try try
{ {
if (result.LongOpen) if ((result & TradingEvent.LongOpen) == TradingEvent.LongOpen)
{ {
using var context = await _dbContextFactory.CreateDbContextAsync(); using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
@ -122,7 +129,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
}); });
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
if (result.LongClose) if ((result & TradingEvent.LongClose) == TradingEvent.LongClose)
{ {
using var context = await _dbContextFactory.CreateDbContextAsync(); using var context = await _dbContextFactory.CreateDbContextAsync();
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
@ -145,6 +152,20 @@ namespace KLHZ.Trader.Core.Declisions.Services
} }
} }
private async Task ProcessOrdersbooks()
{
while (await _ordersbookChannel.Reader.WaitToReadAsync())
{
var message = await _ordersbookChannel.Reader.ReadAsync();
if (!_historyCash.TryGetValue(message.Figi, out var data))
{
data = new PriceHistoryCacheUnit2(message.Figi);
_historyCash.TryAdd(message.Figi, data);
}
await data.AddOrderbook(message);
}
}
public Task StopAsync(CancellationToken cancellationToken) public Task StopAsync(CancellationToken cancellationToken)
{ {
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -82,7 +82,7 @@ namespace KLHZ.Trader.Core.TG.Services
RecomendPrice = null, RecomendPrice = null,
Figi = "BBG004730N88", Figi = "BBG004730N88",
}; };
await _eventBus.BroadcastCommand(command); await _eventBus.Broadcast(command);
break; break;
} }
case "продать сбер": case "продать сбер":
@ -95,7 +95,7 @@ namespace KLHZ.Trader.Core.TG.Services
Count = 1, Count = 1,
LotsCount = 1, LotsCount = 1,
}; };
await _eventBus.BroadcastCommand(command); await _eventBus.Broadcast(command);
break; break;
} }
case "купить сбер": case "купить сбер":
@ -107,7 +107,7 @@ namespace KLHZ.Trader.Core.TG.Services
Figi = "BBG004730N88", Figi = "BBG004730N88",
Count = 1 Count = 1
}; };
await _eventBus.BroadcastCommand(command); await _eventBus.Broadcast(command);
break; break;
} }
} }

View File

@ -1,4 +1,4 @@
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces; using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;

View File

@ -0,0 +1,14 @@
drop table if exists orderbook_items;
create table orderbook_items
(
id bigserial,
time timestamp default current_timestamp,
figi text not null,
ticker text not null,
price decimal not null,
count bigint not null,
item_type int not null default 1,
primary key (id)
);
CREATE INDEX orderbook_items_index ON orderbook_items USING btree(figi, time, item_type);

View File

@ -0,0 +1,66 @@
using Grpc.Core;
using Microsoft.AspNetCore.Mvc;
using Tinkoff.InvestApi;
using Tinkoff.InvestApi.V1;
namespace KLHZ.Trader.Service.Controllers
{
[ApiController]
[Route("[controller]/[action]")]
public class ExchangeDataController : ControllerBase
{
private readonly InvestApiClient _investApiClient;
public ExchangeDataController(InvestApiClient investApiClient)
{
_investApiClient = investApiClient;
}
[HttpGet]
public async Task<string?> GetFigi([FromQuery] string ticker, [FromQuery] string? classCode = "TQBR")
{
var req = new InstrumentRequest()
{
ClassCode = classCode,
IdType = InstrumentIdType.Ticker,
Id = ticker,
};
try
{
var res = await _investApiClient.Instruments.GetInstrumentByAsync(req);
return res.Instrument.Figi;
}
catch (RpcException ex)
{
if (ex.StatusCode == Grpc.Core.StatusCode.NotFound && classCode == "TQBR")
{
return await GetFigi(ticker, "SPBFUT");
}
}
return null;
}
[HttpGet]
public async Task<string?> GetTicker([FromQuery] string figi)
{
var req = new InstrumentRequest()
{
ClassCode = "figi",
IdType = InstrumentIdType.Figi,
Id = figi,
};
try
{
var res = await _investApiClient.Instruments.GetInstrumentByAsync(req);
return res.Instrument.Ticker;
}
catch (Exception ex)
{
}
return null;
}
}
}

View File

@ -41,7 +41,7 @@ namespace KLHZ.Trader.Service.Controllers
foreach (var mess in data) foreach (var mess in data)
{ {
await _dataBus.BroadcastNewPrice(mess); await _dataBus.Broadcast(mess);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -3,9 +3,9 @@ using KLHZ.Trader.Core.Common.Messaging.Services;
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces; using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer;
using KLHZ.Trader.Core.Declisions.Services;
using KLHZ.Trader.Core.Exchange; using KLHZ.Trader.Core.Exchange;
using KLHZ.Trader.Core.Exchange.Services; using KLHZ.Trader.Core.Exchange.Services;
using KLHZ.Trader.Core.Math.Declisions.Services.EventsDetection;
using KLHZ.Trader.Core.TG; using KLHZ.Trader.Core.TG;
using KLHZ.Trader.Core.TG.Services; using KLHZ.Trader.Core.TG.Services;
using KLHZ.Trader.Service.Infrastructure; using KLHZ.Trader.Service.Infrastructure;
@ -53,7 +53,7 @@ builder.Services.AddHostedService<Trader>();
builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>(); builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>();
builder.Services.AddSingleton<BotModeSwitcher>(); builder.Services.AddSingleton<BotModeSwitcher>();
builder.Services.AddSingleton<IDataBus, DataBus>(); builder.Services.AddSingleton<IDataBus, DataBus>();
builder.Services.AddSingleton<ITradingEventsDetector, TradingEventsDetector>(); builder.Services.AddSingleton<ITradingEventsDetector, IntervalsTradingEventsDetector>();
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {

View File

@ -11,7 +11,8 @@
"ExchangeDataRecievingEnabled": true, "ExchangeDataRecievingEnabled": true,
"Token": "", "Token": "",
"ManagingAccountNamePatterns": [ "автотрейд 1" ], "ManagingAccountNamePatterns": [ "автотрейд 1" ],
"AllowedInstrumentsFigis": [ "BBG004730N88", "FUTIMOEXF000" ], "DataRecievingInstrumentsFigis": [ "BBG004730N88", "FUTIMOEXF000", "FUTGMKN09250", "FUTBR1025000" ],
"TradingInstrumentsFigis": [ "FUTIMOEXF000" ],
"FutureComission": 0.0025, "FutureComission": 0.0025,
"ShareComission": 0.0004, "ShareComission": 0.0004,
"AccountCashPart": 0.05, "AccountCashPart": 0.05,