Compare commits
4 Commits
8d329dc069
...
8edb4351dd
Author | SHA1 | Date |
---|---|---|
|
8edb4351dd | |
|
62de4169b8 | |
|
45b34b4509 | |
|
d634cfac73 |
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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 Bound;
|
||||
|
@ -11,7 +11,7 @@
|
|||
public readonly TimeSpan PeriodStart;
|
||||
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)
|
||||
{
|
||||
Success = success;
|
|
@ -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
|
||||
{
|
||||
|
@ -8,5 +8,8 @@ namespace KLHZ.Trader.Core.Contracts.Declisions.Interfaces
|
|||
public int Length { get; }
|
||||
public ValueTask AddData(INewPrice priceChange);
|
||||
public ValueTask<(DateTime[] timestamps, float[] prices)> GetData();
|
||||
public ValueTask AddOrderbook(IOrderbook orderbook);
|
||||
public long AsksCount { get; }
|
||||
public long BidsCount { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
public interface ITradingEventsDetector
|
||||
{
|
||||
public ValueTask<TradingEventsDto> Detect(IPriceHistoryCacheUnit unit);
|
||||
public ValueTask<TradingEvent> Detect(IPriceHistoryCacheUnit unit);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface IMessage
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface INewCandle
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface INewPrice
|
||||
{
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface IOrderbookItem
|
||||
{
|
||||
public long Count { get; }
|
||||
public decimal Price { get; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces
|
||||
{
|
||||
public interface IProcessedPrice : INewPrice
|
||||
{
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
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;
|
||||
|
||||
namespace KLHZ.Trader.Core.Contracts.Messaging.Interfaces
|
||||
{
|
||||
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<INewPrice> channel);
|
||||
public bool AddChannel(string key, Channel<TradeCommand> channel);
|
||||
public bool AddChannel(string key, Channel<IMessage> channel);
|
||||
public bool AddChannel(string key, Channel<INewCandle> channel);
|
||||
public Task BroadcastNewPrice(INewPrice newPriceMessage);
|
||||
public Task BroadcastCommand(TradeCommand command);
|
||||
public Task BroadcastNewCandle(INewCandle command);
|
||||
public Task BroadcastProcessedPrice(IProcessedPrice command);
|
||||
public Task Broadcast(INewPrice newPriceMessage);
|
||||
public Task Broadcast(TradeCommand command);
|
||||
public Task Broadcast(INewCandle command);
|
||||
public Task Broadcast(IProcessedPrice command);
|
||||
public Task Broadcast(IOrderbook orderbook);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
//Тесты
|
||||
[assembly: InternalsVisibleTo("KLHZ.Trader.Core.Tests")]
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Math.Utils
|
||||
namespace KLHZ.Trader.Core.Math.Common
|
||||
{
|
||||
public static class Lines
|
||||
{
|
||||
|
@ -14,7 +14,7 @@
|
|||
{
|
||||
var dtime = (float)(time2 - time1).TotalSeconds;
|
||||
|
||||
var dval1 = (val1_2 - val1_1);
|
||||
var dval1 = val1_2 - val1_1;
|
||||
var k1 = dval1 / dtime;
|
||||
var b1 = val1_1;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.Declisions.Dtos
|
||||
namespace KLHZ.Trader.Core.Math.Declisions.Dtos
|
||||
{
|
||||
internal readonly struct PeriodPricesInfoDto
|
||||
{
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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
|
||||
{
|
||||
|
@ -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 float[] Prices = new float[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)
|
||||
{
|
||||
Figi = figi;
|
|
@ -1,8 +1,7 @@
|
|||
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
|
||||
using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Intarfaces;
|
||||
using System.Reflection;
|
||||
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 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 float[] Prices = new float[_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 _pointer = -1;
|
||||
|
||||
private long _asksCount = 1;
|
||||
private long _bidsCount = 1;
|
||||
|
||||
public ValueTask AddData(INewPrice priceChange)
|
||||
{
|
||||
if (priceChange.Figi != Figi) return ValueTask.CompletedTask;
|
||||
lock (_locker)
|
||||
{
|
||||
_pointer++;
|
||||
|
@ -57,19 +82,30 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
|
|||
{
|
||||
if (_pointer < 0)
|
||||
{
|
||||
return ValueTask.FromResult((Array.Empty<DateTime>(), Array.Empty <float>()));
|
||||
return ValueTask.FromResult((Array.Empty<DateTime>(), Array.Empty<float>()));
|
||||
}
|
||||
else
|
||||
{
|
||||
var prices = new float[_length];
|
||||
var timestamps = new DateTime[_length];
|
||||
Array.Copy(Prices,1+ _pointer - _length, prices, 0, prices.Length);
|
||||
Array.Copy(Timestamps,1+ _pointer - _length, timestamps, 0, timestamps.Length);
|
||||
Array.Copy(Prices, 1 + _pointer - _length, prices, 0, prices.Length);
|
||||
Array.Copy(Timestamps, 1 + _pointer - _length, timestamps, 0, timestamps.Length);
|
||||
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)
|
||||
{
|
||||
Figi = figi;
|
||||
|
@ -85,7 +121,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Services
|
|||
.Skip(priceChanges.Length - CacheMaxLength)
|
||||
.ToArray();
|
||||
|
||||
foreach ( var pc in selectedPriceChanges)
|
||||
foreach (var pc in selectedPriceChanges)
|
||||
{
|
||||
AddData(pc);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
|
||||
using KLHZ.Trader.Core.Declisions.Dtos;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Services;
|
||||
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.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)
|
||||
{
|
||||
|
@ -25,7 +30,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
|
|||
int count = 0;
|
||||
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;
|
||||
}
|
||||
|
@ -88,7 +93,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
|
|||
if (k2 == 0 && k1 != 0) return 1000;
|
||||
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 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 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)
|
||||
{
|
||||
|
@ -180,9 +185,9 @@ namespace KLHZ.Trader.Core.Declisions.Utils
|
|||
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 prices = data.prices;
|
||||
int count = -1;
|
||||
|
@ -198,7 +203,7 @@ namespace KLHZ.Trader.Core.Declisions.Utils
|
|||
bound = i;
|
||||
shift = lastTime - time[i];
|
||||
}
|
||||
if (((lastTime - time[i]) >= shift + firstPeriod))
|
||||
if (lastTime - time[i] >= shift + firstPeriod)
|
||||
{
|
||||
start = i;
|
||||
|
||||
|
@ -212,12 +217,12 @@ namespace KLHZ.Trader.Core.Declisions.Utils
|
|||
{
|
||||
var diff1 = prices[bound] - prices[start];
|
||||
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;
|
||||
}
|
||||
|
||||
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 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)
|
||||
|
@ -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 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
{
|
||||
|
@ -208,7 +208,7 @@ namespace KLHZ.Trader.Core.Tests
|
|||
public void Test9()
|
||||
{
|
||||
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 });
|
||||
if (i >= 500)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using KLHZ.Trader.Core.DataLayer.Entities.Prices;
|
||||
using KLHZ.Trader.Core.Declisions.Utils;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Services;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Utils;
|
||||
|
||||
namespace KLHZ.Trader.Core.Tests
|
||||
{
|
||||
|
@ -45,7 +45,7 @@ namespace KLHZ.Trader.Core.Tests
|
|||
|
||||
var periodLength = 4;
|
||||
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 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 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 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 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 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 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 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 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 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 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 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 minValue2 = -100;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using KLHZ.Trader.Core.Math.Common;
|
||||
|
||||
namespace KLHZ.Trader.Core.Tests
|
||||
{
|
||||
public class LinesProcessingTest
|
||||
|
@ -14,7 +16,7 @@ namespace KLHZ.Trader.Core.Tests
|
|||
var val2_1 = -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]
|
||||
|
@ -29,7 +31,7 @@ namespace KLHZ.Trader.Core.Tests
|
|||
var val2_1 = 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
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 System.Collections.Concurrent;
|
||||
using System.Threading.Channels;
|
||||
|
@ -8,6 +8,7 @@ namespace KLHZ.Trader.Core.Common.Messaging.Services
|
|||
{
|
||||
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<INewCandle>> _candlesChannels = 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
await channel.Writer.WriteAsync(command);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Broadcast(IOrderbook orderbook)
|
||||
{
|
||||
foreach (var channel in _orderbooksChannels.Values)
|
||||
{
|
||||
await channel.Writer.WriteAsync(orderbook);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -6,7 +6,7 @@ using Microsoft.Extensions.Hosting;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace KLHZ.Trader.Core.Declisions.Services
|
||||
namespace KLHZ.Trader.Core.Common.Messaging.Services
|
||||
{
|
||||
public class ProcessedPricesLogger : IHostedService
|
||||
{
|
||||
|
@ -44,7 +44,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
|
|||
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;
|
||||
using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Declisions
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums
|
||||
{
|
||||
public enum DeclisionTradeAction
|
||||
{
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
|
||||
|
|
|
@ -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;
|
||||
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
|
||||
|
|
|
@ -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;
|
||||
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
|
||||
{
|
||||
public enum AssetType
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
|
||||
{
|
||||
public enum PositionType
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
|
||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades.Enums
|
||||
{
|
||||
public enum TradeDirection
|
||||
{
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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;
|
||||
|
@ -14,6 +15,7 @@ namespace KLHZ.Trader.Core.DataLayer
|
|||
public DbSet<PriceChange> PriceChanges { get; set; }
|
||||
public DbSet<ProcessedPrice> ProcessedPrices { get; set; }
|
||||
public DbSet<Candle> Candles { get; set; }
|
||||
public DbSet<OrderbookItem> OrderbookItems { get; set; }
|
||||
public TraderDbContext(DbContextOptions<TraderDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
|
@ -60,6 +62,15 @@ namespace KLHZ.Trader.Core.DataLayer
|
|||
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 =>
|
||||
{
|
||||
entity.HasKey(e1 => e1.Id);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,8 @@
|
|||
public decimal AccountCashPart { get; set; }
|
||||
public decimal AccountCashPartFutures { 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; } = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using Grpc.Core;
|
||||
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.DataLayer.Entities.Trades;
|
||||
using KLHZ.Trader.Core.Exchange.Extentions;
|
||||
|
@ -33,7 +35,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
_eventBus = eventBus;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_investApiClient = investApiClient;
|
||||
_instrumentsFigis = options.Value.AllowedInstrumentsFigis.ToArray();
|
||||
_instrumentsFigis = options.Value.DataRecievingInstrumentsFigis.ToArray();
|
||||
_logger = logger;
|
||||
_managedAccountNamePatterns = options.Value.ManagingAccountNamePatterns.ToArray();
|
||||
}
|
||||
|
@ -51,7 +53,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
var shares = await _investApiClient.Instruments.SharesAsync();
|
||||
foreach (var share in shares.Instruments)
|
||||
{
|
||||
//if (_instrumentsFigis.Contains(share.Figi))
|
||||
if (_instrumentsFigis.Contains(share.Figi))
|
||||
{
|
||||
_tickersCache.TryAdd(share.Figi, share.Ticker);
|
||||
}
|
||||
|
@ -59,7 +61,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
var futures = await _investApiClient.Instruments.FuturesAsync();
|
||||
foreach (var future in futures.Instruments)
|
||||
{
|
||||
//if (_instrumentsFigis.Contains(future.Figi))
|
||||
if (_instrumentsFigis.Contains(future.Figi))
|
||||
{
|
||||
_tickersCache.TryAdd(future.Figi, future.Ticker);
|
||||
}
|
||||
|
@ -100,6 +102,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
SubscriptionAction = SubscriptionAction.Subscribe
|
||||
};
|
||||
|
||||
var bookRequest = new SubscribeOrderBookRequest
|
||||
{
|
||||
SubscriptionAction = SubscriptionAction.Subscribe
|
||||
};
|
||||
|
||||
foreach (var f in _instrumentsFigis)
|
||||
{
|
||||
request.Instruments.Add(
|
||||
|
@ -113,24 +120,31 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
{
|
||||
InstrumentId = f
|
||||
});
|
||||
|
||||
bookRequest.Instruments.Add(
|
||||
new OrderBookInstrument()
|
||||
{
|
||||
InstrumentId = f,
|
||||
Depth = 10
|
||||
});
|
||||
}
|
||||
|
||||
await stream.RequestStream.WriteAsync(new MarketDataRequest
|
||||
{
|
||||
SubscribeLastPriceRequest = request,
|
||||
});
|
||||
|
||||
await stream.RequestStream.WriteAsync(new MarketDataRequest
|
||||
{
|
||||
SubscribeTradesRequest = tradesRequest,
|
||||
SubscribeOrderBookRequest = bookRequest
|
||||
});
|
||||
|
||||
using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
var pricesBuffer = new List<PriceChange>();
|
||||
var orderbookItemsBuffer = new List<OrderbookItem>();
|
||||
var tradesBuffer = new List<InstrumentTrade>();
|
||||
var lastWritePrices = DateTime.UtcNow;
|
||||
var lastWriteOrderbooks = DateTime.UtcNow;
|
||||
var lastWriteTrades = DateTime.UtcNow;
|
||||
var lastWritePrices = DateTime.UtcNow;
|
||||
var lastWrite = DateTime.UtcNow;
|
||||
await foreach (var response in stream.ResponseStream.ReadAllAsync())
|
||||
{
|
||||
if (response.LastPrice != null)
|
||||
|
@ -143,7 +157,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
Value = response.LastPrice.Price,
|
||||
IsHistoricalData = false,
|
||||
};
|
||||
await _eventBus.BroadcastNewPrice(message);
|
||||
await _eventBus.Broadcast(message);
|
||||
|
||||
pricesBuffer.Add(message);
|
||||
}
|
||||
|
@ -156,24 +170,62 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
Ticker = GetTickerByFigi(response.Trade.Figi),
|
||||
Price = response.Trade.Price,
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
//if (pricesBuffer.Count > 200 || (DateTime.UtcNow - lastWritePrices).TotalSeconds > 10)
|
||||
if (response.Orderbook != null)
|
||||
{
|
||||
lastWritePrices = DateTime.UtcNow;
|
||||
await context.PriceChanges.AddRangeAsync(pricesBuffer);
|
||||
pricesBuffer.Clear();
|
||||
await context.SaveChangesAsync();
|
||||
var asksSummary = new OrderbookItem()
|
||||
{
|
||||
Figi = response.Orderbook.Figi,
|
||||
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;
|
||||
await context.InstrumentTrades.AddRangeAsync(tradesBuffer);
|
||||
tradesBuffer.Clear();
|
||||
lastWrite = DateTime.UtcNow;
|
||||
if (orderbookItemsBuffer.Count > 0)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,9 +236,9 @@ namespace KLHZ.Trader.Core.Exchange.Services
|
|||
BoughtAt = DateTime.UtcNow,
|
||||
Count = res.LotsExecuted,
|
||||
Price = res.ExecutedOrderPrice,
|
||||
Position = DataLayer.Entities.Trades.PositionType.Long,
|
||||
Direction = DataLayer.Entities.Trades.TradeDirection.Buy,
|
||||
Asset = DataLayer.Entities.Trades.AssetType.Common,
|
||||
Position = DataLayer.Entities.Trades.Enums.PositionType.Long,
|
||||
Direction = DataLayer.Entities.Trades.Enums.TradeDirection.Buy,
|
||||
Asset = DataLayer.Entities.Trades.Enums.AssetType.Common,
|
||||
};
|
||||
|
||||
await context.Trades.AddAsync(newTrade);
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
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.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.DataLayer;
|
||||
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.Models;
|
||||
using KLHZ.Trader.Core.Exchange.Services;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Services;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Channels;
|
||||
using Tinkoff.InvestApi;
|
||||
using AssetType = KLHZ.Trader.Core.Exchange.Models.AssetType;
|
||||
|
||||
namespace KLHZ.Trader.Core.Declisions.Services
|
||||
namespace KLHZ.Trader.Core.Exchange.Services
|
||||
{
|
||||
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, IPriceHistoryCacheUnit> _historyCash = new();
|
||||
private readonly ITradingEventsDetector _tradingEventsDetector;
|
||||
private readonly ILogger<Trader> _logger;
|
||||
|
||||
|
||||
private readonly decimal _futureComission;
|
||||
|
@ -41,8 +43,10 @@ namespace KLHZ.Trader.Core.Declisions.Services
|
|||
private readonly string[] _managedAccountsNamePatterns = [];
|
||||
|
||||
private readonly Channel<INewPrice> _pricesChannel = Channel.CreateUnbounded<INewPrice>();
|
||||
private readonly Channel<IOrderbook> _ordersbookChannel = Channel.CreateUnbounded<IOrderbook>();
|
||||
|
||||
public Trader(
|
||||
ILogger<Trader> logger,
|
||||
ITradingEventsDetector tradingEventsDetector,
|
||||
BotModeSwitcher botModeSwitcher,
|
||||
IServiceProvider provider,
|
||||
|
@ -51,6 +55,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
|
|||
IDbContextFactory<TraderDbContext> dbContextFactory,
|
||||
InvestApiClient investApiClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_tradingEventsDetector = tradingEventsDetector;
|
||||
_botModeSwitcher = botModeSwitcher;
|
||||
_dataBus = dataBus;
|
||||
|
@ -86,10 +91,12 @@ namespace KLHZ.Trader.Core.Declisions.Services
|
|||
}
|
||||
|
||||
_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())
|
||||
{
|
||||
|
@ -107,7 +114,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
|
|||
|
||||
try
|
||||
{
|
||||
if (result.LongOpen)
|
||||
if ((result & TradingEvent.LongOpen) == TradingEvent.LongOpen)
|
||||
{
|
||||
using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
|
@ -122,7 +129,7 @@ namespace KLHZ.Trader.Core.Declisions.Services
|
|||
});
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
if (result.LongClose)
|
||||
if ((result & TradingEvent.LongClose) == TradingEvent.LongClose)
|
||||
{
|
||||
using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
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)
|
||||
{
|
||||
return Task.CompletedTask;
|
|
@ -82,7 +82,7 @@ namespace KLHZ.Trader.Core.TG.Services
|
|||
RecomendPrice = null,
|
||||
Figi = "BBG004730N88",
|
||||
};
|
||||
await _eventBus.BroadcastCommand(command);
|
||||
await _eventBus.Broadcast(command);
|
||||
break;
|
||||
}
|
||||
case "продать сбер":
|
||||
|
@ -95,7 +95,7 @@ namespace KLHZ.Trader.Core.TG.Services
|
|||
Count = 1,
|
||||
LotsCount = 1,
|
||||
};
|
||||
await _eventBus.BroadcastCommand(command);
|
||||
await _eventBus.Broadcast(command);
|
||||
break;
|
||||
}
|
||||
case "купить сбер":
|
||||
|
@ -107,7 +107,7 @@ namespace KLHZ.Trader.Core.TG.Services
|
|||
Figi = "BBG004730N88",
|
||||
Count = 1
|
||||
};
|
||||
await _eventBus.BroadcastCommand(command);
|
||||
await _eventBus.Broadcast(command);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ namespace KLHZ.Trader.Service.Controllers
|
|||
|
||||
foreach (var mess in data)
|
||||
{
|
||||
await _dataBus.BroadcastNewPrice(mess);
|
||||
await _dataBus.Broadcast(mess);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -3,9 +3,9 @@ using KLHZ.Trader.Core.Common.Messaging.Services;
|
|||
using KLHZ.Trader.Core.Contracts.Declisions.Interfaces;
|
||||
using KLHZ.Trader.Core.Contracts.Messaging.Interfaces;
|
||||
using KLHZ.Trader.Core.DataLayer;
|
||||
using KLHZ.Trader.Core.Declisions.Services;
|
||||
using KLHZ.Trader.Core.Exchange;
|
||||
using KLHZ.Trader.Core.Exchange.Services;
|
||||
using KLHZ.Trader.Core.Math.Declisions.Services.EventsDetection;
|
||||
using KLHZ.Trader.Core.TG;
|
||||
using KLHZ.Trader.Core.TG.Services;
|
||||
using KLHZ.Trader.Service.Infrastructure;
|
||||
|
@ -53,7 +53,7 @@ builder.Services.AddHostedService<Trader>();
|
|||
builder.Services.AddSingleton<IUpdateHandler, BotMessagesHandler>();
|
||||
builder.Services.AddSingleton<BotModeSwitcher>();
|
||||
builder.Services.AddSingleton<IDataBus, DataBus>();
|
||||
builder.Services.AddSingleton<ITradingEventsDetector, TradingEventsDetector>();
|
||||
builder.Services.AddSingleton<ITradingEventsDetector, IntervalsTradingEventsDetector>();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"ExchangeDataRecievingEnabled": true,
|
||||
"Token": "",
|
||||
"ManagingAccountNamePatterns": [ "автотрейд 1" ],
|
||||
"AllowedInstrumentsFigis": [ "BBG004730N88", "FUTIMOEXF000" ],
|
||||
"DataRecievingInstrumentsFigis": [ "BBG004730N88", "FUTIMOEXF000", "FUTGMKN09250", "FUTBR1025000" ],
|
||||
"TradingInstrumentsFigis": [ "FUTIMOEXF000" ],
|
||||
"FutureComission": 0.0025,
|
||||
"ShareComission": 0.0004,
|
||||
"AccountCashPart": 0.05,
|
||||
|
|
Loading…
Reference in New Issue