From 4332e3b0979418f4d47e6541281327d0eacf036f Mon Sep 17 00:00:00 2001 From: vlad zverzhkhovskiy Date: Tue, 14 Oct 2025 18:01:00 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B8=D1=86=D0=B8=D0=B9=20=D1=81=20=D0=B2=D1=80=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BE=D0=BA=D0=BE=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Declisions/Utils/MovingAverage.cs | 106 ++++++++++++++++++ .../Exchange/Services/ManagedAccount.cs | 31 ++++- KLHZ.Trader.Core/Exchange/Services/Trader.cs | 62 ++++++++-- .../Exchange/Utils/TraderUtils.cs | 2 +- 4 files changed, 188 insertions(+), 13 deletions(-) diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index fa9e066..3602baa 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -1,4 +1,5 @@ using KLHZ.Trader.Core.Contracts.Declisions.Dtos.Enums; +using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.Math.Common; namespace KLHZ.Trader.Core.Math.Declisions.Utils @@ -20,6 +21,21 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils return (startTime, sum / count); } + internal static (DateTime time, decimal value) CalcTimeWindowAverageValue(ITradeDataItem[] data, int window, int shift = 0) + { + var sum = data[data.Length - 1 - shift].Price; + var count = 1m; + var startTime = data[data.Length - 1 - shift].Time; + for (int i = 2; i + shift < data.Length + && startTime - data[data.Length - i - shift].Time < TimeSpan.FromSeconds(window); i++) + { + var k = data.Length - i - shift; + sum += data[data.Length - i - shift].Price; + count++; + } + return (startTime, sum / count); + } + public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean(DateTime[] timestamps, decimal[] prices, int size, int smallWindow, int bigWindow, TimeSpan timeForUptreandStart, decimal uptrendStartingDetectionMeanfullStep = 0m, decimal uptrendEndingDetectionMeanfullStep = 3m) @@ -214,5 +230,95 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } return (res, bigWindowAv, smallWindowAv); } + + public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean2(ITradeDataItem[] data, int size, int smallWindow, int bigWindow, +decimal? uptrendStartingDetectionMeanfullStep = null, decimal? uptrendEndingDetectionMeanfullStep = null) + { + var res = TradingEvent.None; + var bigWindowAv = 0m; + var smallWindowAv = 0m; + var s = 0; + var pricesForFinalComparison = new decimal[size]; + var timesForFinalComparison = new DateTime[size]; + var twavss = new decimal[size]; + var twavbs = new decimal[size]; + var times = new DateTime[size]; + var crossings = new List(); + var crossingValues = new List(); + for (int shift = 0; shift < size - 1 && shift < data.Length - 1; shift++) + { + s = shift; + var i2 = size - 1 - shift; + var i1 = size - 2 - shift; + var twavs = CalcTimeWindowAverageValue(data, smallWindow, shift); + var twavb = CalcTimeWindowAverageValue(data, bigWindow, shift); + pricesForFinalComparison[i2] = data[data.Length - 1 - shift].Price; + timesForFinalComparison[i2] = data[data.Length - 1 - shift].Time; + + if (shift == 0) + { + bigWindowAv = twavb.value; + smallWindowAv = twavs.value; + } + twavss[i2] = twavs.value; + twavbs[i2] = twavb.value; + times[i2] = twavb.time; + + if (shift > 0) + { + var isCrossing = Lines.IsLinesCrossing( + times[i1 + 1], + times[i2 + 1], + twavss[i1 + 1], + twavss[i2 + 1], + twavbs[i1 + 1], + twavbs[i2 + 1]); + + if (shift == 1 && !isCrossing.res) //если нет пересечения скользящих средний с окном 120 и 15 секунд между + //текущей и предыдущей точкой - можно не продолжать выполнение. + { + break; + } + + if (isCrossing.res) + { + crossings.Add(i2); + crossingValues.Add(isCrossing.y); + if (crossings.Count == 2) + { + var dt = timesForFinalComparison[crossings[0]] - timesForFinalComparison[crossings[1]]; + var d1 = pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]]; + var d2 = crossingValues[0] - crossingValues[1]; + // если фильтрация окном 15 наползает на окно 120 сверху, потенциальное время закрытия лонга и возможно открытия шорта + if (twavss[size - 1] <= twavbs[size - 1] && twavss[size - 2] > twavbs[size - 2]) + { + if (!uptrendEndingDetectionMeanfullStep.HasValue || ((d1 >= uptrendEndingDetectionMeanfullStep + //|| d2 >= uptrendEndingDetectionMeanfullStep + ) + && dt > TimeSpan.FromSeconds(10))) + { + res |= TradingEvent.CloseLong; + res |= TradingEvent.OpenShort; + } + break; + } + // если фильтрация окном 120 наползает на окно 15 сверху, потенциальное время открытия лонга и закрытия шорта + if (twavss[size - 1] >= twavbs[size - 1] && twavss[size - 2] < twavbs[size - 2]) + { + if (!uptrendStartingDetectionMeanfullStep.HasValue || ((d1 <= uptrendStartingDetectionMeanfullStep + // || d2 <= uptrendStartingDetectionMeanfullStep + ) && dt > TimeSpan.FromSeconds(10))) + { + res |= TradingEvent.OpenLong; + res |= TradingEvent.CloseShort; + } + break; + } + } + } + } + } + return (res, bigWindowAv, smallWindowAv); + } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs index af98de5..d9b46a7 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs @@ -1,4 +1,5 @@ -using KLHZ.Trader.Core.Common.Extentions; +using Google.Protobuf.WellKnownTypes; +using KLHZ.Trader.Core.Common.Extentions; using KLHZ.Trader.Core.Contracts.Common.Enums; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.Exchange.Extentions; @@ -9,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Collections.Concurrent; using System.Collections.Immutable; +using Telegram.Bot.Types; using Tinkoff.InvestApi; using Tinkoff.InvestApi.V1; using Asset = KLHZ.Trader.Core.Exchange.Models.AssetsAccounting.Asset; @@ -187,16 +189,37 @@ namespace KLHZ.Trader.Core.Exchange.Services _usedOrderIds.TryAdd(res.OrderId, DateTime.UtcNow); var executedPrice = res.ExecutedOrderPrice / 10; + + await Task.Delay(1000); + + if (stopLossShift == 0) + { + stopLossShift = 0.002m * executedPrice; + } + if (takeProfitShift == 0) + { + takeProfitShift = 0.01m * executedPrice; + } + takeProfitShift = takeProfitShift * 2; + takeProfitShift = System.Math.Round(takeProfitShift); + takeProfitShift = takeProfitShift / 2; + + stopLossShift = stopLossShift * 2; + stopLossShift = System.Math.Round(stopLossShift); + stopLossShift = stopLossShift / 2; + + var pricesl = positionType == PositionType.Long ? executedPrice - stopLossShift : executedPrice + stopLossShift; + var pricetp = positionType == PositionType.Long ? executedPrice + takeProfitShift : executedPrice - takeProfitShift; var slReq = new PostStopOrderRequest() { AccountId = AccountId, ConfirmMarginTrade = false, InstrumentId = figi, - Direction = stopOrdersDirection, + Direction = stopOrdersDirection, PriceType = PriceType.Point, Quantity = count, StopOrderType = StopOrderType.StopLoss, - StopPrice = positionType == PositionType.Long ? executedPrice - stopLossShift : executedPrice + stopLossShift, + StopPrice = pricesl, ExchangeOrderType = ExchangeOrderType.Market, ExpirationType = StopOrderExpirationType.GoodTillCancel, }; @@ -211,7 +234,7 @@ namespace KLHZ.Trader.Core.Exchange.Services PriceType = PriceType.Point, Quantity = count, StopOrderType = StopOrderType.TakeProfit, - StopPrice = positionType == PositionType.Long ? executedPrice + takeProfitShift : executedPrice - takeProfitShift, + StopPrice = pricetp, ExchangeOrderType = ExchangeOrderType.Market, ExpirationType = StopOrderExpirationType.GoodTillCancel, }; diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 32ea84a..cbc7ad2 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -86,16 +86,25 @@ namespace KLHZ.Trader.Core.Exchange.Services if (command.CommandType == TradeCommandType.OpenLong || command.CommandType == TradeCommandType.OpenShort) { - var fakeMessage = new TradeDataItem() { Figi = command.Figi, Ticker = "", Count = command.Count, Direction = 1, IsHistoricalData = false, Time = DateTime.UtcNow, Price = command.RecomendPrice ?? 0m }; + ITradeDataItem message; + if (_oldItems.TryGetValue(command.Figi, out var message1)) + { + message = message1; + } + else + { + message = new TradeDataItem() { Figi = command.Figi, Ticker = "", Count = command.Count, Direction = 1, IsHistoricalData = false, Time = DateTime.UtcNow, Price = command.RecomendPrice ?? 0m }; + } + var positionType = command.CommandType == TradeCommandType.OpenLong ? PositionType.Long : PositionType.Short; - var st = GetStops(fakeMessage); + var st = GetStops(message); var stops = st.GetStops(positionType); var accounts = _portfolioWrapper.Accounts .Where(a => !a.Value.Assets.ContainsKey(command.Figi)) .Take(1) .Select(a => a.Value) .ToArray(); - await OpenPositions(accounts, fakeMessage, positionType, stops.stopLoss, stops.takeProfit, System.Math.Abs(command.Count)); + await OpenPositions(accounts, message, positionType, stops.stopLoss, stops.takeProfit, System.Math.Abs(command.Count)); } else { @@ -203,11 +212,13 @@ namespace KLHZ.Trader.Core.Exchange.Services await CalcSupportLevels(message, 3, 5); var stops = GetStops(message); var pirson = await CalcPirson(message); + var mavRes = await CalcTimeWindowAverageValue(message); var declisionPirson = await ProcessPirson(pirson, message); var declisionsSupportLevels = await ProcessSupportLevels(message); var declisionsStops = ProcessStops(stops, 2m); var res = TraderUtils.MergeResultsMult(declisionPirson, declisionsSupportLevels); res = TraderUtils.MergeResultsMult(res, declisionsStops); + res = TraderUtils.MergeResultsMax(res, mavRes); await ExecuteDeclisions(res.ToImmutableDictionary(), message, stops, 1); @@ -228,18 +239,50 @@ namespace KLHZ.Trader.Core.Exchange.Services } } + private async Task> CalcTimeWindowAverageValue(ITradeDataItem message) + { + var res = TraderUtils.GetInitDict(Constants.BlockingCoefficient); + var cacheSize = TimeSpan.FromSeconds(60*60); + var data = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize, selector: (i) => i.Direction == 1); + var closings = MovingAverage.CheckByWindowAverageMean2(data, data.Length, 15, 300, -5m, 5m); + //var re = MovingAverage.CheckByWindowAverageMean2(data, 100, 15, 300, -4m, 4m); + if (closings.smallWindowAv != 0) + { + await _tradeDataProvider.LogPrice(message, "maw_small", closings.smallWindowAv); + await _tradeDataProvider.LogPrice(message, "maw_big", closings.bigWindowAv); + } + //if ((re.events & TradingEvent.OpenShort) == TradingEvent.OpenShort) + //{ + // res[TradingEvent.OpenShort] = Constants.PowerUppingCoefficient; + //} + //if ((re.events & TradingEvent.OpenLong) == TradingEvent.OpenLong) + //{ + // res[TradingEvent.OpenLong] = Constants.PowerUppingCoefficient; + //} + if ((closings.events & TradingEvent.CloseShort) == TradingEvent.CloseShort) + { + res[TradingEvent.CloseShort] = Constants.PowerUppingCoefficient; + } + if ((closings.events & TradingEvent.CloseLong) == TradingEvent.CloseLong) + { + res[TradingEvent.CloseLong] = Constants.PowerUppingCoefficient; + } + + return res.ToImmutableDictionary(); + } + private async Task> ProcessPirson(PirsonCalculatingResult pirson, ITradeDataItem message) { var res = TraderUtils.GetInitDict(Constants.BlockingCoefficient); if (pirson.Success && _pirsonValues.TryGetValue(message.Figi, out var olddpirs)) { - if (olddpirs < 0 && pirson.Pirson > 0 && pirson.PriceDiff > 0 && (pirson.TradesDiffRelative > 0.2m)) + if (olddpirs < -0.3m && pirson.Pirson > -0.3m && pirson.PriceDiff > 0 && (pirson.TradesDiffRelative > 0.2m)) { res[TradingEvent.OpenLong] = Constants.PowerUppingCoefficient; } - if (olddpirs > 0 && pirson.Pirson < 0 && pirson.PriceDiff < 0 && (pirson.TradesDiffRelative > 0.2m)) + if (olddpirs > 0.3m && pirson.Pirson < 0.3m && pirson.PriceDiff < 0 && (pirson.TradesDiffRelative > 0.2m)) { res[TradingEvent.OpenShort] = Constants.PowerUppingCoefficient; } @@ -328,7 +371,7 @@ namespace KLHZ.Trader.Core.Exchange.Services { if (_supportLevelsCalculationTimes.TryGetValue(message.Figi, out var lastTime)) { - if ((message.Time - lastTime).TotalMinutes < 30) + if ((message.Time - lastTime).TotalMinutes < 10) { return; } @@ -489,6 +532,7 @@ namespace KLHZ.Trader.Core.Exchange.Services { var loggedDeclisions = 0; var sign = positionType == PositionType.Long ? 1 : 1; + foreach (var acc in accounts) { if (TraderUtils.IsOperationAllowed(acc, message.Price, count, _exchangeConfig.AccountCashPartFutures, _exchangeConfig.AccountCashPart)) @@ -533,7 +577,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var valLow = message.Price - stops.stopLoss; var valHigh = message.Price + stops.takeProfit; await _tradeDataProvider.LogDeclision(DeclisionTradeAction.OpenLong, val, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message); - await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, valHigh, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); + //await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, valHigh, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, valLow, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message); } if (result[TradingEvent.OpenShort] >= Constants.UppingCoefficient @@ -554,7 +598,7 @@ namespace KLHZ.Trader.Core.Exchange.Services var valLow = message.Price - stops.takeProfit; var valHigh = message.Price + stops.stopLoss; await _tradeDataProvider.LogDeclision(DeclisionTradeAction.OpenShort, val, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(-100, 100)), message); - await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, valLow, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); + //await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, valLow, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, valHigh, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message); } if (result[TradingEvent.CloseLong] >= Constants.UppingCoefficient) @@ -751,10 +795,12 @@ namespace KLHZ.Trader.Core.Exchange.Services if (message.Price < levelByTime.LowValue) { res[TradingEvent.OpenShort] = Constants.ForceExecuteCoefficient; + res[TradingEvent.OpenLong] = Constants.ForceExecuteCoefficient; _usedSupportLevels[message.Figi] = levelByTime.CalculatedAt; } else if (message.Price > levelByTime.HighValue) { + res[TradingEvent.OpenShort] = Constants.ForceExecuteCoefficient; res[TradingEvent.OpenLong] = Constants.ForceExecuteCoefficient; _usedSupportLevels[message.Figi] = levelByTime.CalculatedAt; } diff --git a/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs b/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs index 2f28bfd..4a190b5 100644 --- a/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs +++ b/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs @@ -27,7 +27,7 @@ namespace KLHZ.Trader.Core.Exchange.Utils foreach (var k in result.Keys) { var valRes = result[k]; - var valData = result[k]; + var valData = data[k]; res[k] = System.Math.Max(valRes, valData); } return res;