добавлена обработка расписания торгов

main
vlad zverzhkhovskiy 2025-09-05 00:10:53 +03:00
parent ccc44c0f57
commit 9749791b4c
6 changed files with 153 additions and 5 deletions

View File

@ -0,0 +1,63 @@
using KLHZ.Trader.Core.Exchange.Utils;
namespace KLHZ.Trader.Core.Tests
{
public class ExchangeSchedulerTests
{
[Test]
public void Test1()
{
var dt = new DateTime(2025, 9, 6, 0, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.Close);
}
[Test]
public void Test2()
{
var dt = new DateTime(2025, 9, 5, 7, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.Open);
}
[Test]
public void Test3()
{
var dt = new DateTime(2025, 9, 5, 6, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.Close);
}
[Test]
public void Test4()
{
var dt = new DateTime(2025, 9, 5, 11, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.ClearingTime);
}
[Test]
public void Test5()
{
var dt = new DateTime(2025, 9, 7, 11, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.Open);
}
[Test]
public void Test6()
{
var dt = new DateTime(2025, 9, 5, 16, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.ClearingTime);
}
[Test]
public void Test7()
{
var dt = new DateTime(2025, 9, 7, 15, 0, 0, DateTimeKind.Utc);
var res = ExchangeScheduler.GetCurrentState(dt);
Assert.IsTrue(res == Exchange.Models.ExchangeState.Open);
}
}
}

View File

@ -2,6 +2,7 @@
{
public class Asset
{
public long? TradeId { get; init; }
public decimal BlockedItems { get; init; }
public AssetType Type { get; init; }
public PositionType Position { get; init; }

View File

@ -0,0 +1,10 @@
namespace KLHZ.Trader.Core.Exchange.Models
{
internal enum ExchangeState
{
None,
Open,
Close,
ClearingTime
}
}

View File

@ -128,6 +128,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
#pragma warning disable CS0612 // Тип или член устарел
var asset = new Models.Assets.Asset()
{
TradeId = trade?.Id,
AccountId = AccountId,
Figi = position.Figi,
Ticker = position.Ticker,

View File

@ -11,6 +11,7 @@ using KLHZ.Trader.Core.DataLayer.Entities.Prices;
using KLHZ.Trader.Core.Exchange.Extentions;
using KLHZ.Trader.Core.Exchange.Models;
using KLHZ.Trader.Core.Exchange.Models.Assets;
using KLHZ.Trader.Core.Exchange.Utils;
using KLHZ.Trader.Core.Math.Declisions.Services.Cache;
using KLHZ.Trader.Core.Math.Declisions.Utils;
using Microsoft.EntityFrameworkCore;
@ -110,11 +111,14 @@ namespace KLHZ.Trader.Core.Exchange.Services
var processedPrices = new List<ProcessedPrice>();
while (await _pricesChannel.Reader.WaitToReadAsync())
{
var bigWindowProcessor = nameof(Trader) + "_big";
var smallWindowProcessor = nameof(Trader) + "_small";
var message = await _pricesChannel.Reader.ReadAsync();
if (_tradingInstrumentsFigis.Contains(message.Figi))
{
var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
if (_historyCash.TryGetValue(message.Figi, out var unit))
{
await unit.AddData(message);
@ -128,12 +132,11 @@ namespace KLHZ.Trader.Core.Exchange.Services
{
if (message.Figi == "FUTIMOEXF000")
{
DeferredTrade? longOpen;
DeferredLongOpens.TryGetValue(message.Figi, out longOpen);
if (longOpen != null)
{
var t = message.IsHistoricalData ? message.Time : DateTime.UtcNow;
var t = currentTime;
if (longOpen.Time <= t
&& t - longOpen.Time < TimeSpan.FromMinutes(3))
{
@ -149,7 +152,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
DeferredLongCloses.TryGetValue(message.Figi, out longClose);
if (longClose != null)
{
if (longClose.Time <= (message.IsHistoricalData ? message.Time : DateTime.UtcNow))
if (longClose.Time <= currentTime)
{
DeferredLongCloses.TryRemove(message.Figi, out _);
if (longClose.Price - message.Value < 1)
@ -161,9 +164,18 @@ namespace KLHZ.Trader.Core.Exchange.Services
var windowMaxSize = 100;
var data = await unit.GetData(windowMaxSize);
var state = ExchangeScheduler.GetCurrentState(message.Time);
if (state == ExchangeState.ClearingTime
&& data.timestamps.Length > 1
&& (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2]) > TimeSpan.FromMinutes(3))
{
await UpdateFuturesPrice(message, data.prices[data.prices.Length - 2]);
}
if (OpeningStops.TryGetValue(message.Figi, out var dt))
{
if (dt < (message.IsHistoricalData ? message.Time : DateTime.UtcNow))
if (dt < currentTime)
{
OpeningStops.TryRemove(message.Figi, out _);
}
@ -171,7 +183,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
if ((unit.BidsCount / unit.AsksCount) < 0.5m || (unit.BidsCount / unit.AsksCount) > 2m)
{
var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(3);
var stopTo = currentTime.AddMinutes(3);
//OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo);
//LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuyShortTime, message);
}
@ -203,6 +215,7 @@ namespace KLHZ.Trader.Core.Exchange.Services
if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart
&& !OpeningStops.TryGetValue(message.Figi, out _)
&& state == ExchangeState.Open
&& data.timestamps.Length > 1
&& (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2] < TimeSpan.FromMinutes(1)))
{
@ -265,6 +278,18 @@ namespace KLHZ.Trader.Core.Exchange.Services
}
}
private async Task UpdateFuturesPrice(INewPrice newPrice, decimal newPriceValue)
{
using var context = await _dbContextFactory.CreateDbContextAsync();
await context.Trades
.Where(t => t.Figi == newPrice.Figi && t.ArchiveStatus == 0)
.ExecuteUpdateAsync(t => t.SetProperty(tr => tr.Price, newPriceValue));
foreach (var account in Accounts.Values)
{
await account.SyncPortfolio();
}
}
private static void LogPrice(List<ProcessedPrice> prices, INewPrice message, string processor, decimal value)
{
prices.Add(new ProcessedPrice()

View File

@ -0,0 +1,48 @@
using KLHZ.Trader.Core.Exchange.Models;
namespace KLHZ.Trader.Core.Exchange.Utils
{
internal static class ExchangeScheduler
{
private readonly static TimeOnly _openTimeMain = new(6, 10);
private readonly static TimeOnly _closeTimeMain = new(20, 45);
private readonly static TimeOnly _openTimeHoliday = new(7, 10);
private readonly static TimeOnly _closeTimeHoliday = new(17, 45);
private readonly static TimeOnly _firstClearingStart = new(10, 55);
private readonly static TimeOnly _firstClearingEnd = new(11, 10);
private readonly static TimeOnly _mainClearingStart = new(15, 50);
private readonly static TimeOnly _mainClearingEnd = new(16, 5);
internal static ExchangeState GetCurrentState(DateTime? currentDt = null)
{
var dt = currentDt ?? DateTime.UtcNow;
var day = dt.DayOfWeek;
var time = TimeOnly.FromDateTime(dt);
if (day == DayOfWeek.Sunday || day == DayOfWeek.Saturday)
{
if (time > _openTimeHoliday && time < _closeTimeHoliday)
{
return ExchangeState.Open;
}
}
else
{
if (time > _openTimeMain && time < _closeTimeMain)
{
if (time > _firstClearingStart && time < _firstClearingEnd || time > _mainClearingStart && time < _mainClearingEnd)
{
return ExchangeState.ClearingTime;
}
return ExchangeState.Open;
}
}
return ExchangeState.Close;
}
}
}