From 6940c9f8075394f049077f33c87ef91f2aeeb549 Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Tue, 23 Sep 2025 02:01:50 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=B0=D1=8F=20=D1=82=D0=BE=D1=80=D0=B3=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Declisions/Utils/MovingAverage.cs | 8 +- KLHZ.Trader.Core/Exchange/Constants.cs | 6 + .../Exchange/Services/ExchangeDataReader.cs | 2 +- .../Exchange/Services/ManagedAccount.cs | 4 +- KLHZ.Trader.Core/Exchange/Services/Trader.cs | 643 ++++++++++-------- .../Controllers/PlayController.cs | 4 +- 6 files changed, 386 insertions(+), 281 deletions(-) diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index a4731c6..e53c5e0 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -127,7 +127,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean2(DateTime[] timestamps, - decimal[] prices, int size, int smallWindow, int bigWindow, TimeSpan timeForUptreandStart, + decimal[] prices, int size, int smallWindow, int bigWindow, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m) { var res = TradingEvent.None; @@ -182,8 +182,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils // если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2]) { - if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] >= uptrendEndingDetectionMeanfullStep - && times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart) + if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] >= uptrendEndingDetectionMeanfullStep) { res |= TradingEvent.UptrendEnd; } @@ -192,8 +191,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils // если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта if (twavss[size - 1] >= twavbs[size - 1] && twavss[size - 2] < twavbs[size - 2]) { - if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] <= uptrendStartingDetectionMeanfullStep - && times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart) + if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] <= uptrendStartingDetectionMeanfullStep) { res |= TradingEvent.UptrendStart; } diff --git a/KLHZ.Trader.Core/Exchange/Constants.cs b/KLHZ.Trader.Core/Exchange/Constants.cs index 90bc641..f770708 100644 --- a/KLHZ.Trader.Core/Exchange/Constants.cs +++ b/KLHZ.Trader.Core/Exchange/Constants.cs @@ -12,5 +12,11 @@ internal const string SmallWindowCrossingAverageProcessor = "Trader_small"; internal const string AreasRelationProcessor = "balancescalc30min"; internal readonly static TimeSpan AreasRelationWindow = TimeSpan.FromMinutes(15); + + internal const decimal PowerUppingCoefficient = 1.69m; + internal const decimal UppingCoefficient = 1.3m; + internal const decimal LowingCoefficient = .77m; + internal const decimal PowerLowingCoefficient = .6m; + internal const decimal BlockingCoefficient = 0m; } } diff --git a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs index 2483b90..f6997f7 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ExchangeDataReader.cs @@ -157,7 +157,7 @@ namespace KLHZ.Trader.Core.Exchange.Services }; await _tradeDataProvider.AddData(message, TimeSpan.FromHours(7)); - //await _eventBus.Broadcast(message); + await _eventBus.Broadcast(message); var exchangeState = ExchangeScheduler.GetCurrentState(); if (exchangeState == Models.Trading.ExchangeState.ClearingTime diff --git a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs index c4cd2a0..f011ef2 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs @@ -252,10 +252,10 @@ namespace KLHZ.Trader.Core.Exchange.Services ConfirmMarginTrade = true, }; - var stopsReq = new GetStopOrdersRequest() { AccountId = asset.AccountId}; + var stopsReq = new GetStopOrdersRequest() { AccountId = asset.AccountId }; var res = await _investApiClient.Orders.PostOrderAsync(req); - var stopOrders = await _investApiClient.StopOrders.GetStopOrdersAsync(stopsReq); + var stopOrders = await _investApiClient.StopOrders.GetStopOrdersAsync(stopsReq); if (stopOrders.StopOrders != null) { foreach (var stopOrder in stopOrders.StopOrders) diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index d6460f2..ca55ee6 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -16,6 +16,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; +using System.Collections.Immutable; using System.Threading.Channels; using Tinkoff.InvestApi; using Asset = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.Asset; @@ -132,7 +133,8 @@ namespace KLHZ.Trader.Core.Exchange.Services private async Task ProcessPrices() { - var pricesCache = new Dictionary>(); + var pricesCache1 = new Dictionary>(); + var pricesCache2 = new Dictionary>(); var timesCache = new Dictionary(); while (await _pricesChannel.Reader.WaitToReadAsync()) { @@ -140,14 +142,46 @@ namespace KLHZ.Trader.Core.Exchange.Services try { - #region Ускорение обработки исторических данных при отладке if (message.IsHistoricalData) { await _tradeDataProvider.AddData(message, TimeSpan.FromHours(6)); - if (!pricesCache.TryGetValue(message.Figi, out var list)) + } + #region Ускорение обработки исторических данных при отладке + if (message.Direction == 1) + { + if (!pricesCache1.TryGetValue(message.Figi, out var list)) { list = new List(); - pricesCache[message.Figi] = list; + pricesCache1[message.Figi] = list; + } + list.Add(message); + + if ((list.Last().Time - list.First().Time).TotalSeconds < 0.5) + { + list.Add(message); + continue; + } + else + { + message = new PriceChange() + { + Figi = message.Figi, + Ticker = message.Ticker, + Count = message.Count, + Direction = message.Direction, + IsHistoricalData = message.IsHistoricalData, + Time = message.Time, + Value = list.Sum(l => l.Value) / list.Count + }; + list.Clear(); + } + } + if (message.Direction == 2) + { + if (!pricesCache2.TryGetValue(message.Figi, out var list)) + { + list = new List(); + pricesCache2[message.Figi] = list; } list.Add(message); @@ -203,27 +237,6 @@ namespace KLHZ.Trader.Core.Exchange.Services Value = (decimal)message.Count }); } - - var sberSells5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minSellCacheKey); - var sberBuys5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._5minBuyCacheKey); - - var sberSells1min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._1minSellCacheKey); - var sberBuys1min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache(message.Figi, Constants._1minBuyCacheKey); - - var sells5min = sberSells5min.Sum(s => s.Value); - var buys5min = sberBuys5min.Sum(s => s.Value); - var sells1min = sberSells1min.Sum(s => s.Value); - var buys1min = sberBuys1min.Sum(s => s.Value); - - var su = sells5min + buys5min; - if (su != 0) - { - await LogPrice(message, "sellsbuysbalance", (sells5min / su - 0.5m) * 2); - await LogPrice(message, "trades_diff", (buys1min + sells1min) / (su)); - } - - - } #endregion if (_tradingInstrumentsFigis.Contains(message.Figi)) @@ -236,8 +249,67 @@ namespace KLHZ.Trader.Core.Exchange.Services if ((message.Time - dt).TotalSeconds > 10) { timesCache[message.Figi] = message.Time; + var newMod = await CalcTradingMode(message); + if (TradingModes.TryGetValue(message.Figi, out var oldMod)) + { + if ((oldMod == TradingMode.Growing || oldMod == TradingMode.Stable) + && oldMod != newMod) + { + if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) + { + var assetsForClose = _portfolioWrapper.Accounts + .SelectMany(a => a.Value.Assets.Values) + .Where(a => a.Figi == message.Figi && a.Count > 0) + .ToArray(); + await ClosePositions(assetsForClose, message); + } - TradingModes[message.Figi] = await CalcTradingMode(message); + await LogDeclision(DeclisionTradeAction.CloseLong, message); + } + if ((oldMod == TradingMode.Dropping || oldMod == TradingMode.SlowDropping) + && oldMod != newMod) + { + if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) + { + var assetsForClose = _portfolioWrapper.Accounts + .SelectMany(a => a.Value.Assets.Values) + .Where(a => a.Figi == message.Figi && a.Count < 0) + .ToArray(); + await ClosePositions(assetsForClose, message); + } + await LogDeclision(DeclisionTradeAction.CloseShort, message); + } + } + + if (newMod == TradingMode.Growing && newMod != oldMod) + { + var stops = GetStops(message, PositionType.Long); + if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) + { + var accounts = _portfolioWrapper.Accounts + .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) + .Take(1) + .Select(a => a.Value) + .ToArray(); + await OpenPositions(accounts, message, PositionType.Long, stops.stopLoss, stops.takeProfit, 1); + } + await LogDeclision(DeclisionTradeAction.OpenLong, message); + } + if (newMod == TradingMode.Dropping && newMod != oldMod) + { + var stops = GetStops(message, PositionType.Short); + if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) + { + var accounts = _portfolioWrapper.Accounts + .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) + .Take(1) + .Select(a => a.Value) + .ToArray(); + await OpenPositions(accounts, message, PositionType.Short, stops.stopLoss, stops.takeProfit, 1); + } + await LogDeclision(DeclisionTradeAction.OpenShort, message); + } + TradingModes[message.Figi] = newMod; } } else @@ -255,31 +327,12 @@ namespace KLHZ.Trader.Core.Exchange.Services } try { + if (message.Direction != 1) continue; ProcessStops(message, currentTime); var windowMaxSize = 2000; var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize); var state = ExchangeScheduler.GetCurrentState(message.Time); - - if (TradingModes[message.Figi] == TradingMode.Stable) - { - await ProcessNewPriceIMOEXF_Stable(data, state, message, windowMaxSize); - } - else if (TradingModes[message.Figi] == TradingMode.SlowDropping) - { - await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 2); - } - else if (TradingModes[message.Figi] == TradingMode.Dropping) - { - await ProcessNewPriceIMOEXF_Dropping(data, state, message, windowMaxSize, 6); - } - else if (TradingModes[message.Figi] == TradingMode.Growing) - { - await ProcessNewPriceIMOEXF_Growing(data, state, message, windowMaxSize); - } - else - { - await ProcessNewPriceIMOEXF2(data, state, message, windowMaxSize); - } + await ProcessNewPriceIMOEXF3(data, state, message, windowMaxSize); } catch (Exception ex) { @@ -295,61 +348,28 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task CheckByWindowAverageMean((DateTime[] timestamps, decimal[] prices) data, - INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m) - { - var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, - windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep); - if (resultMoveAvFull.bigWindowAv != 0) - { - await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv); - await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv); - } - return resultMoveAvFull.events; - } - - private async Task CheckByWindowAverageMean2((DateTime[] timestamps, decimal[] prices) data, int smallWindow, int bigWindow, - INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m) + private async Task> GetWindowAverageStartData((DateTime[] timestamps, decimal[] prices) data, int smallWindow, int bigWindow, +INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m, decimal initValue = 1) { var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean2(data.timestamps, data.prices, - windowMaxSize, smallWindow, bigWindow, TimeSpan.FromSeconds(20), uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep); + windowMaxSize, smallWindow, bigWindow, uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep); if (resultMoveAvFull.bigWindowAv != 0) { await LogPrice(message, Constants.BigWindowCrossingAverageProcessor, resultMoveAvFull.bigWindowAv); await LogPrice(message, Constants.SmallWindowCrossingAverageProcessor, resultMoveAvFull.smallWindowAv); } - return resultMoveAvFull.events; - } - - private Task CheckByWindowAverageMeanNolog((DateTime[] timestamps, decimal[] prices) data, - INewPrice message, int windowMaxSize, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m) - { - var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, - windowMaxSize, 30, 180, TimeSpan.FromSeconds(20), uptrendStartingDetectionMeanfullStep, uptrendEndingDetectionMeanfullStep); - return Task.FromResult(resultMoveAvFull.events); - } - - private Task CheckByWindowAverageMeanForShotrs((DateTime[] timestamps, decimal[] prices) data, - INewPrice message, int windowMaxSize) - { - var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, - windowMaxSize, 30, 240, TimeSpan.FromSeconds(20), -1m, 1m); - return Task.FromResult(resultMoveAvFull.events); - } - - private Task CheckByLocalTrends((DateTime[] timestamps, decimal[] prices) data, - INewPrice message, int windowMaxSize) - { - var res = TradingEvent.None; - if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(20), 1, out var resLocalTrends)) + var res = GetInitDict(Constants.PowerLowingCoefficient); + if ((resultMoveAvFull.events & TradingEvent.UptrendStart) == TradingEvent.UptrendStart) { - res |= (resLocalTrends & TradingEvent.UptrendStart); - if ((resLocalTrends & TradingEvent.UptrendStart) == TradingEvent.UptrendStart) - { - res |= TradingEvent.DowntrendEnd; - } + res[TradingEvent.UptrendStart] = initValue; + res[TradingEvent.DowntrendEnd] = initValue; } - return Task.FromResult(res); + if ((resultMoveAvFull.events & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd) + { + res[TradingEvent.UptrendEnd] = initValue; + res[TradingEvent.DowntrendStart] = initValue; + } + return res; } private async Task GetAreasRelation((DateTime[] timestamps, decimal[] prices) data, INewPrice message) @@ -378,16 +398,6 @@ namespace KLHZ.Trader.Core.Exchange.Services return position; } - private async Task CalcTrendDiff(INewPrice message) - { - var data = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromHours(1)); - if (data.isFullIntervalExists && LocalTrends.TryCalcTrendDiff(data.timestamps, data.prices, out var res)) - { - return res; - } - return null; - } - private async Task ClosePositions(Asset[] assets, INewPrice message, bool withProfitOnly = true) { var loggedDeclisions = 0; @@ -443,49 +453,44 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task ProcessNewPriceIMOEXF2((DateTime[] timestamps, decimal[] prices) data, - ExchangeState state, - INewPrice message, int windowMaxSize) + private async Task ProcessNewPriceIMOEXF3((DateTime[] timestamps, decimal[] prices) data, + ExchangeState state, + INewPrice message, int windowMaxSize) { if (data.timestamps.Length <= 4) { return; } + //var resTask1 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, -2m, 2m,3); + var resTask1 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, -0.5m, 0.5m, Constants.UppingCoefficient); + //var resTask3 = GetWindowAverageStartData(data, 30, 180, message, windowMaxSize, 0, 0,0.7m); + var getFFTModsTask = GetFFTMods(message); + var getAreasModsTask = GetAreasMods(data, message); + var getSellsDiffsModsTask = GetSellsDiffsMods(message); + var getTradingModeModsTask = GetTradingModeMods(message); - var sberSells = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._1minSellCacheKey); - var sberBuys = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._1minBuyCacheKey); - var sells = sberSells.Sum(s => s.Value); - var buys = sberBuys.Sum(s => s.Value); - var su = sells + buys; - if (su != 0) + await Task.WhenAll(resTask1, getFFTModsTask, getAreasModsTask, getSellsDiffsModsTask, getTradingModeModsTask); + var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); + if (resTask1.Result[TradingEvent.UptrendStart] >= 1) { - var dsell = (sells / su - 0.5m) * 2; + } + var result = resTask1.Result; + //result = MergeResults(result, resTask2.Result.ToImmutableDictionary()); + //result = MergeResults(result, resTask3.Result.ToImmutableDictionary()); + result = MergeResults(result, getFFTModsTask.Result); + result = MergeResults(result, getAreasModsTask.Result); + result = MergeResults(result, getSellsDiffsModsTask.Result); + result = MergeResults(result, getTradingModeModsTask.Result); - var mavTask = CheckByWindowAverageMean(data, message, windowMaxSize, -1, 2m); - var mavTaskEnds = CheckByWindowAverageMeanNolog(data, message, windowMaxSize, -1, 1m); - var mavTaskShorts = CheckByWindowAverageMeanForShotrs(data, message, windowMaxSize); - var ltTask = CheckByLocalTrends(data, message, windowMaxSize); - var areasTask = GetAreasRelation(data, message); - var positionTask = CheckPosition(message); - var trendTask = CalcTrendDiff(message); - - var ends = mavTaskEnds.Result & TradingEvent.UptrendEnd; - - await Task.WhenAll(mavTask, ltTask, areasTask, positionTask, trendTask, mavTaskShorts, mavTaskEnds); - var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); - var res = mavTask.Result | ltTask.Result; - res |= ends; - if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart + if (result[TradingEvent.UptrendStart] > Constants.UppingCoefficient && !LongOpeningStops.ContainsKey(message.Figi) - && trendTask.Result.HasValue && state == ExchangeState.Open - && areasTask.Result.HasValue - && (positionTask.Result == ValueAmplitudePosition.LowerThenMediana) ) { + var stops = GetStops(message, PositionType.Long); if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { var accounts = _portfolioWrapper.Accounts @@ -493,12 +498,34 @@ namespace KLHZ.Trader.Core.Exchange.Services .Take(1) .Select(a => a.Value) .ToArray(); - await OpenPositions(accounts, message, PositionType.Long, 7, 10, 1); + await OpenPositions(accounts, message, PositionType.Long, stops.stopLoss, stops.takeProfit, 1); } await LogDeclision(DeclisionTradeAction.OpenLong, message); + await LogDeclision(DeclisionTradeAction.OpenLong, message.Value + stops.takeProfit, message.Time.AddMilliseconds(-100), message); + await LogDeclision(DeclisionTradeAction.OpenLong, message.Value - stops.stopLoss, message.Time.AddMilliseconds(100), message); } - if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd) + if (result[TradingEvent.DowntrendStart] > Constants.PowerUppingCoefficient + && !ShortOpeningStops.ContainsKey(message.Figi) + && state == ExchangeState.Open + ) + { + var stops = GetStops(message, PositionType.Short); + if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) + { + var accounts = _portfolioWrapper.Accounts + .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) + .Take(1) + .Select(a => a.Value) + .ToArray(); + await OpenPositions(accounts, message, PositionType.Short, stops.stopLoss, stops.takeProfit, 1); + } + + await LogDeclision(DeclisionTradeAction.OpenShort, message); + await LogDeclision(DeclisionTradeAction.OpenShort, message.Value - stops.takeProfit, message.Time.AddMilliseconds(-100), message); + await LogDeclision(DeclisionTradeAction.OpenShort, message.Value + stops.stopLoss, message.Time.AddMilliseconds(100), message); + } + if (result[TradingEvent.UptrendEnd] > Constants.UppingCoefficient) { if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) { @@ -511,7 +538,7 @@ namespace KLHZ.Trader.Core.Exchange.Services await LogDeclision(DeclisionTradeAction.CloseLong, message); } - if ((res & TradingEvent.DowntrendEnd) == TradingEvent.DowntrendEnd) + if (result[TradingEvent.DowntrendEnd] > Constants.UppingCoefficient) { if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { @@ -525,142 +552,6 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task ProcessNewPriceIMOEXF_Stable( - (DateTime[] timestamps, decimal[] prices) data, - ExchangeState state, - INewPrice message, int windowMaxSize) - { - if (data.timestamps.Length <= 4 || state != ExchangeState.Open) - { - return; - } - - var mavTask = CheckByWindowAverageMean2(data, 30, 180, message, windowMaxSize, 0, 0); - - await Task.WhenAll(mavTask); - var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); - var res = mavTask.Result; - - if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart) - { - if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) - { - var accounts = _portfolioWrapper.Accounts - .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) - .Take(1) - .Select(a => a.Value) - .ToArray(); - - await OpenPositions(accounts, message, PositionType.Long, 5, 2, 1); - } - - await LogDeclision(DeclisionTradeAction.OpenLong, message); - } - } - - private async Task ProcessNewPriceIMOEXF_Growing( - (DateTime[] timestamps, decimal[] prices) data, - ExchangeState state, - INewPrice message, int windowMaxSize) - { - if (data.timestamps.Length <= 4 || state != ExchangeState.Open) - { - return; - } - - var mavTask = CheckByWindowAverageMean2(data, 30, 180, message, windowMaxSize, 0, 0); - - await Task.WhenAll(mavTask); - var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); - var res = mavTask.Result; - - if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart) - { - if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) - { - var accounts = _portfolioWrapper.Accounts - .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) - .Take(1) - .Select(a => a.Value) - .ToArray(); - - await OpenPositions(accounts, message, PositionType.Long, 10, 20, 1); - } - - await LogDeclision(DeclisionTradeAction.OpenLong, message); - } - - if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd) - { - if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) - { - var assetsForClose = _portfolioWrapper.Accounts - .SelectMany(a => a.Value.Assets.Values) - .Where(a => a.Figi == message.Figi && a.Count > 0) - .ToArray(); - - await ClosePositions(assetsForClose, message); - } - await LogDeclision(DeclisionTradeAction.CloseLong, message); - } - } - - private async Task ProcessNewPriceIMOEXF_Dropping( - (DateTime[] timestamps, decimal[] prices) data, - ExchangeState state, - INewPrice message, int windowMaxSize, decimal step) - { - if (data.timestamps.Length <= 4 && state != ExchangeState.Open) - { - return; - } - - var mavTask = CheckByWindowAverageMean2(data, 30, 180, message, windowMaxSize, 0, 0); - var positionTask = CheckPosition(message); - - await Task.WhenAll(mavTask, positionTask); - var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); - var res = mavTask.Result; - - if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd && (positionTask.Result != ValueAmplitudePosition.LowerThenMediana)) - { - if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) - { - var accounts = _portfolioWrapper.Accounts - .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) - .Take(1) - .Select(a => a.Value) - .ToArray(); - - - await OpenPositions(accounts, message, PositionType.Short, 10, 20, 1); - } - - await LogDeclision(DeclisionTradeAction.OpenShort, message); - } - - if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart) - { - if (!ShortClosingStops.ContainsKey(message.Figi)) - { - if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) - { - var assetsForClose = _portfolioWrapper.Accounts - .SelectMany(a => a.Value.Assets.Values) - .Where(a => a.Figi == message.Figi && a.Count < 0) - .ToArray(); - await ClosePositions(assetsForClose, message); - } - - if (message.IsHistoricalData) - { - ShortClosingStops[message.Figi] = message.Time.AddSeconds(30); - } - await LogDeclision(DeclisionTradeAction.CloseShort, message); - } - } - } - private void ProcessStops(INewPrice message, DateTime currentTime) { if (LongOpeningStops.TryGetValue(message.Figi, out var dt)) @@ -719,6 +610,20 @@ namespace KLHZ.Trader.Core.Exchange.Services }, false); } + private async Task LogDeclision(DeclisionTradeAction action, decimal price, DateTime time, INewPrice message) + { + await _tradeDataProvider.LogDeclision(new Declision() + { + AccountId = string.Empty, + Figi = message.Figi, + Ticker = message.Ticker, + Value = price, + Price = price, + Time = message.IsHistoricalData ? time : DateTime.UtcNow, + Action = action, + }, false); + } + public Task StopAsync(CancellationToken cancellationToken) { return Task.CompletedTask; @@ -785,10 +690,189 @@ namespace KLHZ.Trader.Core.Exchange.Services private async Task CalcTradingMode(INewPrice message) { var res = await CalcTradingMode(message.Figi); - //await LogPrice(message, "trading_mode", (int)res); return res; } + private (decimal stopLoss, decimal takeProfit) GetStops(INewPrice message, PositionType type) + { + var mode = TradingModes[message.Figi]; + decimal stopLossShift = 5; + decimal takeProfitShift = 10; + if (mode == TradingMode.Growing && type == PositionType.Long) + { + stopLossShift = 5; + takeProfitShift = 15; + } + if (mode == TradingMode.Growing && type == PositionType.Short) + { + stopLossShift = 2; + takeProfitShift = 2; + } + if (mode == TradingMode.Stable && type == PositionType.Long) + { + stopLossShift = 8; + takeProfitShift = 3; + } + if (mode == TradingMode.Stable && type == PositionType.Short) + { + stopLossShift = 5; + takeProfitShift = 2; + } + if (mode == TradingMode.SlowDropping && type == PositionType.Short) + { + stopLossShift = 5; + takeProfitShift = 2; + } + if (mode == TradingMode.SlowDropping && type == PositionType.Long) + { + stopLossShift = 6; + takeProfitShift = 2; + } + if (mode == TradingMode.Dropping && type == PositionType.Short) + { + stopLossShift = 10; + takeProfitShift = 13; + } + if (mode == TradingMode.Dropping && type == PositionType.Long) + { + stopLossShift = 4; + takeProfitShift = 2; + } + return (stopLossShift, takeProfitShift); + } + + private async Task> GetFFTMods(INewPrice message) + { + var res = GetInitDict(1); + var position = await CheckPosition(message); + + + if (position == ValueAmplitudePosition.LowerThenMediana) + { + res[TradingEvent.UptrendStart] = Constants.UppingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient; + res[TradingEvent.UptrendEnd] = Constants.LowingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.LowingCoefficient; + } + if (position == ValueAmplitudePosition.UpperThen30Decil) + { + res[TradingEvent.UptrendStart] = Constants.LowingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.LowingCoefficient; + res[TradingEvent.UptrendEnd] = Constants.UppingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient; + } + return res.ToImmutableDictionary(); + } + + private async Task> GetAreasMods((DateTime[] timestamps, decimal[] prices) data, INewPrice message) + { + var res = GetInitDict(1); + var areas = await GetAreasRelation(data, message); + if (areas.HasValue && areas.Value > 0.2m && areas.Value <= 0.8m) + { + res[TradingEvent.UptrendStart] = Constants.LowingCoefficient; + } + if (areas.HasValue && areas.Value > 0.8m) + { + //res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient; + //res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient; + } + return res.ToImmutableDictionary(); + } + + private async Task> GetSellsDiffsMods(INewPrice message) + { + var res = GetInitDict(1); + var sberSells5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._5minSellCacheKey); + var sberBuys5min = await _tradeDataProvider.GetDataFrom5MinuteWindowCache("BBG004730N88", Constants._5minBuyCacheKey); + + var sberSells1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache("BBG004730N88", Constants._1minSellCacheKey); + var sberBuys1min = await _tradeDataProvider.GetDataFrom1MinuteWindowCache("BBG004730N88", Constants._1minBuyCacheKey); + + var sells5min = sberSells5min.Sum(s => s.Value); + var buys5min = sberBuys5min.Sum(s => s.Value); + var sells1min = sberSells1min.Sum(s => s.Value); + var buys1min = sberBuys1min.Sum(s => s.Value); + + var su = sells5min + buys5min; + var uptrendEndMode = 1m; + var downtrendEndMode = 1m; + var downstartMode = 1m; + var uptrendStartMode = 1m; + + + if (su != 0) + { + var trades_rel = (sells5min / su - 0.5m) * 2; + var bys_rel = buys1min / su; + var sells_rel = sells1min / su; + await LogPrice(message, "trades_rel", trades_rel); + await LogPrice(message, "bys_rel", bys_rel); + await LogPrice(message, "sells_rel", sells_rel); + + if (trades_rel > 0.7m) + { + uptrendStartMode *= Constants.LowingCoefficient; + } + + if (System.Math.Abs(bys_rel) > 0.6m || System.Math.Abs(sells_rel) > 0.6m) + { + uptrendStartMode *= Constants.UppingCoefficient * Constants.UppingCoefficient; + downstartMode *= Constants.UppingCoefficient * Constants.UppingCoefficient; + } + else if (System.Math.Abs(bys_rel) > 0.3m || System.Math.Abs(sells_rel) > 0.3m) + { + uptrendStartMode *= Constants.UppingCoefficient; + downstartMode *= Constants.UppingCoefficient; + } + else if (System.Math.Abs(bys_rel) <= 0.2m && System.Math.Abs(sells_rel) <= 0.2m) + { + uptrendEndMode *= Constants.UppingCoefficient; + downtrendEndMode *= Constants.UppingCoefficient; + } + + res[TradingEvent.UptrendStart] = uptrendStartMode; + res[TradingEvent.UptrendEnd] = uptrendEndMode; + res[TradingEvent.DowntrendEnd] = downtrendEndMode; + res[TradingEvent.DowntrendStart] = downstartMode; + } + return res.ToImmutableDictionary(); + } + + private Task> GetTradingModeMods(INewPrice message) + { + var res = GetInitDict(1); + var mode = TradingModes[message.Figi]; + if (mode == TradingMode.Growing) + { + res[TradingEvent.UptrendEnd] = Constants.PowerLowingCoefficient; + res[TradingEvent.UptrendStart] = Constants.UppingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.BlockingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.BlockingCoefficient; + } + if (mode == TradingMode.Stable) + { + res[TradingEvent.UptrendEnd] = Constants.PowerLowingCoefficient; + res[TradingEvent.UptrendStart] = Constants.UppingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.BlockingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient; + } + if (mode == TradingMode.SlowDropping) + { + res[TradingEvent.UptrendStart] = Constants.LowingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.UppingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.UppingCoefficient; + } + if (mode == TradingMode.Dropping) + { + res[TradingEvent.UptrendEnd] = Constants.PowerUppingCoefficient; + res[TradingEvent.UptrendStart] = Constants.BlockingCoefficient; + res[TradingEvent.DowntrendStart] = Constants.PowerUppingCoefficient; + res[TradingEvent.DowntrendEnd] = Constants.PowerLowingCoefficient; + } + return Task.FromResult(res.ToImmutableDictionary()); + } + internal static bool IsOperationAllowed(IManagedAccount account, decimal boutPrice, decimal count, decimal accountCashPartFutures, decimal accountCashPart) { @@ -809,5 +893,22 @@ namespace KLHZ.Trader.Core.Exchange.Services return true; } + + private static Dictionary GetInitDict(decimal initValue) + { + var values = System.Enum.GetValues(); + return values.ToDictionary(v => v, v => initValue); + } + + private static Dictionary MergeResults(Dictionary res, ImmutableDictionary data) + { + foreach (var k in res.Keys) + { + var valRes = res[k]; + var valData = data[k]; + res[k] = valRes * valData; + } + return res; + } } } diff --git a/KLHZ.Trader.Service/Controllers/PlayController.cs b/KLHZ.Trader.Service/Controllers/PlayController.cs index 9f0958f..efc4048 100644 --- a/KLHZ.Trader.Service/Controllers/PlayController.cs +++ b/KLHZ.Trader.Service/Controllers/PlayController.cs @@ -26,7 +26,7 @@ namespace KLHZ.Trader.Service.Controllers } [HttpGet] - public async Task Run(DateTime? startDate = null) + public async Task Run(double? shift = null) { try { @@ -34,7 +34,7 @@ namespace KLHZ.Trader.Service.Controllers //var figi1 = "BBG004730N88"; var figi2 = "BBG004730N88"; //var figi2 = "FUTIMOEXF000"; - var time1 = startDate ?? DateTime.UtcNow.AddDays(-7); + var time1 = DateTime.UtcNow.AddDays(-shift ?? -7); //var time1 = new DateTime(2025, 9, 4, 14, 0, 0, DateTimeKind.Utc); //var time2 = DateTime.UtcNow.AddMinutes(18); using var context1 = await _dbContextFactory.CreateDbContextAsync();