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 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;
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
|
@ -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
|
||||||
{
|
{
|
|
@ -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
|
||||||
{
|
{
|
|
@ -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
|
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
|
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
|
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;
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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;
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
|
@ -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;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
|
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;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
|
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;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace KLHZ.Trader.Core.DataLayer.Entities.Prices
|
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
|
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
|
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
|
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
|
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
|
namespace KLHZ.Trader.Core.DataLayer.Entities.Trades
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 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; } = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
foreach (var mess in data)
|
||||||
{
|
{
|
||||||
await _dataBus.BroadcastNewPrice(mess);
|
await _dataBus.Broadcast(mess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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.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++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue