From f95a34e5a164627fb6878750f8b075e8fb533523 Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Fri, 5 Sep 2025 17:02:31 +0300 Subject: [PATCH] IsBuyAllowed --- .../Declisions/Utils/LocalTrends.cs | 2 +- .../KLHZ.Trader.Core.Tests.csproj | 1 + KLHZ.Trader.Core.Tests/TraderTests.cs | 107 ++++++++++++++++++ KLHZ.Trader.Core/Common/BotModeSwitcher.cs | 20 ++-- KLHZ.Trader.Core/Exchange/Services/Trader.cs | 26 ++--- .../Exchange/Utils/TraderFuncs.cs | 25 ++++ .../TG/Services/BotMessagesHandler.cs | 12 +- KLHZ.Trader.Service/Program.cs | 1 - 8 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 KLHZ.Trader.Core.Tests/TraderTests.cs create mode 100644 KLHZ.Trader.Core/Exchange/Utils/TraderFuncs.cs diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs index 3d3aecd..eb632ed 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs @@ -21,7 +21,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils internal static TradingEvent CheckUptrendStart(DateTime[] times, decimal[] prices, TimeSpan firstPeriod, TimeSpan secondPeriod, decimal meanfullDiff, int boundIndex) { var periodStat = GetTwoPeriodsProcessingData(times, prices, firstPeriod, secondPeriod, boundIndex, meanfullDiff); - var isStartOk = periodStat.Success && periodStat.DiffStart < -meanfullDiff; + var isStartOk = periodStat.Success && periodStat.DiffStart < 0.5m * meanfullDiff; var isEndOk = periodStat.Success && periodStat.DiffEnd >= meanfullDiff; return isStartOk && isEndOk && prices[periodStat.Start] - prices[periodStat.End] >= meanfullDiff ? TradingEvent.UptrendStart : TradingEvent.None; } diff --git a/KLHZ.Trader.Core.Tests/KLHZ.Trader.Core.Tests.csproj b/KLHZ.Trader.Core.Tests/KLHZ.Trader.Core.Tests.csproj index 161b44d..e0694a3 100644 --- a/KLHZ.Trader.Core.Tests/KLHZ.Trader.Core.Tests.csproj +++ b/KLHZ.Trader.Core.Tests/KLHZ.Trader.Core.Tests.csproj @@ -12,6 +12,7 @@ + diff --git a/KLHZ.Trader.Core.Tests/TraderTests.cs b/KLHZ.Trader.Core.Tests/TraderTests.cs new file mode 100644 index 0000000..0c82188 --- /dev/null +++ b/KLHZ.Trader.Core.Tests/TraderTests.cs @@ -0,0 +1,107 @@ +using Castle.Core.Logging; +using KLHZ.Trader.Core.Common; +using KLHZ.Trader.Core.Common.Messaging.Services; +using KLHZ.Trader.Core.DataLayer; +using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; +using KLHZ.Trader.Core.Exchange.Models.Configs; +using KLHZ.Trader.Core.Math.Common; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NSubstitute; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KLHZ.Trader.Core.Tests +{ + public class TraderTests + { + [Test] + public void IsBuyAllowedTest1() + { + + var account = new ManagedAccount("111"); + account.Total = 10000; + account.Balance = 9000; + account.Assets["123"] = new Asset() + { + AccountId = account.AccountId, + Figi = "123", + Ticker = "123", + Type = AssetType.Futures, + }; + Assert.IsTrue(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.3m)); + } + + [Test] + public void IsBuyAllowedTest2() + { + + var account = new ManagedAccount("111"); + account.Total = 10000; + account.Balance = 5000; + account.Assets["123"] = new Asset() + { + AccountId = account.AccountId, + Figi = "123", + Ticker = "123", + Type = AssetType.Futures, + }; + Assert.IsFalse(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.3m)); + } + + [Test] + public void IsBuyAllowedTest3() + { + + var account = new ManagedAccount("111"); + account.Total = 10000; + account.Balance = 5000; + account.Assets["123"] = new Asset() + { + AccountId = account.AccountId, + Figi = "123", + Ticker = "123", + Type = AssetType.Futures, + }; + Assert.IsFalse(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 1500, 2, 0.5m, 0.3m)); + } + + [Test] + public void IsBuyAllowedTest4() + { + + var account = new ManagedAccount("111"); + account.Total = 10000; + account.Balance = 3000; + account.Assets["123"] = new Asset() + { + AccountId = account.AccountId, + Figi = "123", + Ticker = "123", + Type = AssetType.Futures, + }; + Assert.IsFalse(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 1500, 1, 0.5m, 0.3m)); + } + + [Test] + public void IsBuyAllowedTest5() + { + + var account = new ManagedAccount("111"); + account.Total = 10000; + account.Balance = 5000; + account.Assets["123"] = new Asset() + { + AccountId = account.AccountId, + Figi = "123", + Ticker = "123", + Type = AssetType.Common, + }; + Assert.IsTrue(KLHZ.Trader.Core.Exchange.Services.Trader.IsBuyAllowed(account, 3000, 1, 0.5m, 0.1m)); + } + } +} diff --git a/KLHZ.Trader.Core/Common/BotModeSwitcher.cs b/KLHZ.Trader.Core/Common/BotModeSwitcher.cs index 6d0f373..438658c 100644 --- a/KLHZ.Trader.Core/Common/BotModeSwitcher.cs +++ b/KLHZ.Trader.Core/Common/BotModeSwitcher.cs @@ -1,42 +1,42 @@ namespace KLHZ.Trader.Core.Common { - public class BotModeSwitcher + public static class BotModeSwitcher { - private readonly object _locker = new(); - private bool _canSell = true; - private bool _canPurchase = true; + private readonly static object _locker = new(); + private static bool _canSell = true; + private static bool _canPurchase = true; - public bool CanSell() + public static bool CanSell() { lock (_locker) return _canSell; } - public bool CanPurchase() + public static bool CanPurchase() { lock (_locker) return _canPurchase; } - public void StopSelling() + public static void StopSelling() { lock (_locker) _canSell = false; } - public void StopPurchase() + public static void StopPurchase() { lock (_locker) _canPurchase = false; } - public void StartSelling() + public static void StartSelling() { lock (_locker) _canSell = true; } - public void StartPurchase() + public static void StartPurchase() { lock (_locker) _canPurchase = true; diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 1938e5f..48106b3 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -28,12 +28,11 @@ namespace KLHZ.Trader.Core.Exchange.Services public class Trader : IHostedService { private readonly IDataBus _dataBus; - private readonly BotModeSwitcher _botModeSwitcher; private readonly IDbContextFactory _dbContextFactory; private readonly TradeDataProvider _tradeDataProvider; private readonly ILogger _logger; - private readonly ConcurrentDictionary DeferredLongOpens = new(); - private readonly ConcurrentDictionary DeferredLongCloses = new(); + internal readonly ConcurrentDictionary DeferredLongOpens = new(); + internal readonly ConcurrentDictionary DeferredLongCloses = new(); private readonly ConcurrentDictionary OpeningStops = new(); private readonly ConcurrentDictionary Leverages = new(); private readonly ConcurrentDictionary _historyCash = new(); @@ -44,7 +43,6 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly decimal _shareComission; private readonly decimal _accountCashPart; private readonly decimal _accountCashPartFutures; - private readonly decimal _defaultBuyPartOfAccount; private readonly string[] _tradingInstrumentsFigis = []; private readonly Channel _pricesChannel = Channel.CreateUnbounded(); @@ -52,8 +50,6 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly CancellationTokenSource _cts = new(); public Trader( ILogger logger, - BotModeSwitcher botModeSwitcher, - IServiceProvider provider, IOptions options, IDataBus dataBus, IDbContextFactory dbContextFactory, @@ -62,14 +58,12 @@ namespace KLHZ.Trader.Core.Exchange.Services { _tradeDataProvider = tradeDataProvider; _logger = logger; - _botModeSwitcher = botModeSwitcher; _dataBus = dataBus; _dbContextFactory = dbContextFactory; _futureComission = options.Value.FutureComission; _shareComission = options.Value.ShareComission; _accountCashPart = options.Value.AccountCashPart; _accountCashPartFutures = options.Value.AccountCashPartFutures; - _defaultBuyPartOfAccount = options.Value.DefaultBuyPartOfAccount; _tradingInstrumentsFigis = options.Value.TradingInstrumentsFigis; _buyStopLength = (double)options.Value.StopBuyLengthMinuts; @@ -133,7 +127,7 @@ namespace KLHZ.Trader.Core.Exchange.Services .ToArray(); foreach (var acc in accounts) { - if (IsBuyAllowed(acc.Value, message.Value, 1)) + if (IsBuyAllowed(acc.Value, message.Value, 1, _accountCashPartFutures, _accountCashPart)) { await _dataBus.Broadcast(new TradeCommand() { @@ -211,6 +205,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var state = ExchangeScheduler.GetCurrentState(message.Time); if (state == ExchangeState.ClearingTime + && !message.IsHistoricalData && data.timestamps.Length > 1 && (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2]) > TimeSpan.FromMinutes(3)) { @@ -236,7 +231,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 45, 180, 2.5m); var resultLongClose = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, windowMaxSize, 15, 120, 2.5m).events; - var uptrendStarts = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(15), 2m, 10); + var uptrendStarts = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(120), TimeSpan.FromSeconds(20), 1.5m, 15); //var uptrendStarts2 = LocalTrends.CheckByLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(3), 1.5m, 2); @@ -252,7 +247,7 @@ namespace KLHZ.Trader.Core.Exchange.Services } if ((resultLongClose & TradingEvent.StopBuy) == TradingEvent.StopBuy) { - var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(_buyStopLength); + var stopTo = (message.IsHistoricalData ? message.Time : DateTime.UtcNow).AddMinutes(_buyStopLength/2); OpeningStops.AddOrUpdate(message.Figi, stopTo, (k, v) => stopTo); //LogDeclision(declisionsForSave, DeclisionTradeAction.StopBuy, message); } @@ -405,9 +400,10 @@ namespace KLHZ.Trader.Core.Exchange.Services return res; } - private bool IsBuyAllowed(ManagedAccount account, decimal boutPrice, decimal count) + internal static bool IsBuyAllowed(ManagedAccount account, decimal boutPrice, decimal count, + decimal accountCashPartFutures, decimal accountCashPart) { - if (!_botModeSwitcher.CanPurchase()) return false; + if (!BotModeSwitcher.CanPurchase()) return false; var balance = account.Balance; var total = account.Total; @@ -415,11 +411,11 @@ namespace KLHZ.Trader.Core.Exchange.Services var futures = account.Assets.Values.FirstOrDefault(v => v.Type == AssetType.Futures); if (futures != null) { - if ((balance - boutPrice * count) / total < _accountCashPartFutures) return false; + if ((balance - boutPrice * count) / total < accountCashPartFutures) return false; } else { - if ((balance - boutPrice * count) / total < _accountCashPart) return false; + if ((balance - boutPrice * count) / total < accountCashPart) return false; } return true; diff --git a/KLHZ.Trader.Core/Exchange/Utils/TraderFuncs.cs b/KLHZ.Trader.Core/Exchange/Utils/TraderFuncs.cs new file mode 100644 index 0000000..0531730 --- /dev/null +++ b/KLHZ.Trader.Core/Exchange/Utils/TraderFuncs.cs @@ -0,0 +1,25 @@ +using KLHZ.Trader.Core.Contracts.Messaging.Dtos; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; +using KLHZ.Trader.Core.DataLayer.Entities.Declisions; +using KLHZ.Trader.Core.DataLayer.Entities.Declisions.Enums; +using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; +using KLHZ.Trader.Core.Exchange.Services; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KLHZ.Trader.Core.Exchange.Utils +{ + internal static class TraderFuncs + { + //internal static TradeCommand[] CreateBuyCommands(ConcurrentDictionary accounts, INewPrice message, List declisions) + //{ + // var accs = accounts.Where(a => !a.Value.Assets.ContainsKey(message.Figi)) + // .ToArray(); + + //} + } +} diff --git a/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs b/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs index 7743b3f..ee64c65 100644 --- a/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs +++ b/KLHZ.Trader.Core/TG/Services/BotMessagesHandler.cs @@ -15,13 +15,11 @@ namespace KLHZ.Trader.Core.TG.Services public class BotMessagesHandler : IUpdateHandler { private readonly ImmutableArray _admins = []; - private readonly BotModeSwitcher _botModeSwitcher; private readonly IDataBus _eventBus; private readonly ILogger _logger; - public BotMessagesHandler(BotModeSwitcher botModeSwitcher, IDataBus eventBus, IOptions options, ILogger logger) + public BotMessagesHandler(IDataBus eventBus, IOptions options, ILogger logger) { _logger = logger; - _botModeSwitcher = botModeSwitcher; _eventBus = eventBus; _admins = ImmutableArray.CreateRange(options.Value.Admins); } @@ -52,25 +50,25 @@ namespace KLHZ.Trader.Core.TG.Services } case Constants.BotCommandsButtons.EnableSelling: { - _botModeSwitcher.StartSelling(); + BotModeSwitcher.StartSelling(); await botClient.SendMessage(update.Message.Chat, "Продажи начаты!"); break; } case Constants.BotCommandsButtons.DisableSelling: { - _botModeSwitcher.StopSelling(); + BotModeSwitcher.StopSelling(); await botClient.SendMessage(update.Message.Chat, "Продажи остановлены!"); break; } case Constants.BotCommandsButtons.EnablePurchases: { - _botModeSwitcher.StartPurchase(); + BotModeSwitcher.StartPurchase(); await botClient.SendMessage(update.Message.Chat, "Покупки начаты!"); break; } case Constants.BotCommandsButtons.DisablePurchases: { - _botModeSwitcher.StopPurchase(); + BotModeSwitcher.StopPurchase(); await botClient.SendMessage(update.Message.Chat, "Покупки остановлены!"); break; } diff --git a/KLHZ.Trader.Service/Program.cs b/KLHZ.Trader.Service/Program.cs index f49b8a6..75b2fb5 100644 --- a/KLHZ.Trader.Service/Program.cs +++ b/KLHZ.Trader.Service/Program.cs @@ -53,7 +53,6 @@ builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); -builder.Services.AddSingleton(); builder.Services.AddSingleton(); for (int i = 0; i < 10; i++)