diff --git a/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TradingEvent.cs b/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TradingEvent.cs index 16a3066..8792906 100644 --- a/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TradingEvent.cs +++ b/KLHZ.Trader.Core.Contracts/Declisions/Dtos/Enums/TradingEvent.cs @@ -4,14 +4,9 @@ public enum TradingEvent { None = 0, - StopBuy = 1, - LongOpen = 2, - ShortClose = 4, - LongClose = 8, - ShortOpen = 16, - UptrendEnd = 32, - UptrendStart = 64, - DowntrendEnd = 128, - DowntrendStart = 256, + CloseLong = 1, + OpenLong = 2, + CloseShort = 4, + OpenShort = 8, } } diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs index a010c2f..61b4aae 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/LocalTrends.cs @@ -71,19 +71,19 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils var diff2 = y2_approximated[0] - y2_approximated[y2_approximated.Count - 1]; if (diff1 <= -meanfullDiff && diff2 >= meanfullDiff) { - res |= TradingEvent.DowntrendEnd; + res |= TradingEvent.CloseShort; } else if (diff1 >= meanfullDiff && diff2 <= -meanfullDiff) { - res |= TradingEvent.UptrendEnd; + res |= TradingEvent.CloseLong; } else if (diff1 <= -meanfullDiff && diff2 >= meanfullDiff) { - res |= TradingEvent.DowntrendEnd; + res |= TradingEvent.CloseShort; } else if (diff1 >= 0 && diff2 <= -meanfullDiff) { - res |= TradingEvent.DowntrendStart; + res |= TradingEvent.OpenShort; } success = true; } diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index 4d76882..fa9e066 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -97,7 +97,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils if (diffTotal >= uptrendEndingDetectionMeanfullStep && times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart) { - res |= TradingEvent.UptrendEnd; + res |= TradingEvent.CloseLong; } break; } @@ -115,7 +115,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils if (pricesForFinalComparison[crossings[0]] - pricesForFinalComparison[crossings[1]] <= uptrendStartingDetectionMeanfullStep && times[crossings[0]] - times[crossings[1]] >= timeForUptreandStart) { - res |= TradingEvent.UptrendStart; + res |= TradingEvent.OpenLong; } break; } @@ -193,7 +193,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils ) && dt > TimeSpan.FromSeconds(10))) { - res |= TradingEvent.UptrendEnd; + res |= TradingEvent.CloseLong; } break; } @@ -204,7 +204,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils // || d2 <= uptrendStartingDetectionMeanfullStep ) && dt > TimeSpan.FromSeconds(10))) { - res |= TradingEvent.UptrendStart; + res |= TradingEvent.OpenLong; } break; } diff --git a/KLHZ.Trader.Core.Tests/LocalTrendsTests.cs b/KLHZ.Trader.Core.Tests/LocalTrendsTests.cs index 1b7786c..d751f63 100644 --- a/KLHZ.Trader.Core.Tests/LocalTrendsTests.cs +++ b/KLHZ.Trader.Core.Tests/LocalTrendsTests.cs @@ -33,7 +33,7 @@ namespace KLHZ.Trader.Core.Tests if (LocalTrends.TryGetLocalTrends(data.timestamps, data.prices, TimeSpan.FromSeconds(49), TimeSpan.FromSeconds(49), 22, out var res)) { - Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.UptrendEnd); + Assert.That(res == Contracts.Declisions.Dtos.Enums.TradingEvent.CloseLong); } else { diff --git a/KLHZ.Trader.Core/DataLayer/Entities/Declisions/Enums/DeclisionTradeAction.cs b/KLHZ.Trader.Core/DataLayer/Entities/Declisions/Enums/DeclisionTradeAction.cs index 37748b3..1306f6f 100644 --- a/KLHZ.Trader.Core/DataLayer/Entities/Declisions/Enums/DeclisionTradeAction.cs +++ b/KLHZ.Trader.Core/DataLayer/Entities/Declisions/Enums/DeclisionTradeAction.cs @@ -3,8 +3,6 @@ public enum DeclisionTradeAction { Unknown = 0, - StopBuy = 1, - StopBuyShortTime = 2, OpenLong = 100, OpenLongReal = 101, CloseLong = 200, diff --git a/KLHZ.Trader.Core/Exchange/Models/Trading/PirsonCalculatingResult.cs b/KLHZ.Trader.Core/Exchange/Models/Trading/PirsonCalculatingResult.cs new file mode 100644 index 0000000..f844dd8 --- /dev/null +++ b/KLHZ.Trader.Core/Exchange/Models/Trading/PirsonCalculatingResult.cs @@ -0,0 +1,10 @@ +namespace KLHZ.Trader.Core.Exchange.Models.Trading +{ + internal class PirsonCalculatingResult + { + public bool Success { get; init; } + public decimal Pirson { get; init; } + public decimal PriceDiff { get; init; } + public decimal TradesDiff { get; init; } + } +} diff --git a/KLHZ.Trader.Core/Exchange/Models/Trading/Stops.cs b/KLHZ.Trader.Core/Exchange/Models/Trading/Stops.cs new file mode 100644 index 0000000..4542925 --- /dev/null +++ b/KLHZ.Trader.Core/Exchange/Models/Trading/Stops.cs @@ -0,0 +1,32 @@ +using KLHZ.Trader.Core.Contracts.Common.Enums; + +namespace KLHZ.Trader.Core.Exchange.Models.Trading +{ + public readonly struct Stops + { + public readonly decimal LongStopLossShift; + public readonly decimal LongTakeProfitShift; + public readonly decimal ShortStopLossShift; + public readonly decimal ShortTakeProfitShift; + + public Stops(decimal longStopLossShift, decimal longTakeProfitShift, decimal shortStopLossShift, decimal shortTakeProfitShift) + { + LongStopLossShift = longStopLossShift; + LongTakeProfitShift = longTakeProfitShift; + ShortStopLossShift = shortStopLossShift; + ShortTakeProfitShift = shortTakeProfitShift; + } + + public (decimal takeProfit, decimal stopLoss) GetStops(PositionType positionType) + { + if (positionType == PositionType.Short) + { + return (ShortTakeProfitShift, ShortStopLossShift); + } + else + { + return (LongTakeProfitShift, LongStopLossShift); + } + } + } +} diff --git a/KLHZ.Trader.Core/Exchange/Models/Trading/SupportLevel.cs b/KLHZ.Trader.Core/Exchange/Models/Trading/SupportLevel.cs new file mode 100644 index 0000000..a2806de --- /dev/null +++ b/KLHZ.Trader.Core/Exchange/Models/Trading/SupportLevel.cs @@ -0,0 +1,10 @@ +namespace KLHZ.Trader.Core.Exchange.Models.Trading +{ + public class SupportLevel + { + public decimal Value { get; init; } + public decimal LowValue { get; init; } + public decimal HighValue { get; init; } + public DateTime? LastLevelTime { get; init; } + } +} diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 1791ab1..a37f5e9 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -34,8 +34,9 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly ConcurrentDictionary TradingModes = new(); + private readonly ConcurrentDictionary SupportLevels = new(); private readonly ConcurrentDictionary DPirsonValues = new(); - private readonly ConcurrentDictionary DPricesValues = new(); + private readonly ConcurrentDictionary _supportLevelsCalculationTimes = new(); private readonly Channel _pricesChannel = Channel.CreateUnbounded(); private readonly Channel _commands = Channel.CreateUnbounded(); @@ -82,7 +83,8 @@ namespace KLHZ.Trader.Core.Exchange.Services { var fakeMessage = 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 stops = GetStops(fakeMessage, positionType); + var st = GetStops(fakeMessage); + var stops = st.GetStops(positionType); var accounts = _portfolioWrapper.Accounts .Where(a => !a.Value.Assets.ContainsKey(command.Figi)) .Take(1) @@ -120,7 +122,7 @@ namespace KLHZ.Trader.Core.Exchange.Services { var pricesCache1 = new Dictionary>(); var pricesCache2 = new Dictionary>(); - var timesCache = new Dictionary(); + while (await _pricesChannel.Reader.WaitToReadAsync()) { var message = await _pricesChannel.Reader.ReadAsync(); @@ -128,86 +130,22 @@ namespace KLHZ.Trader.Core.Exchange.Services { continue; } - var changeMods = TraderUtils.GetInitDict(0); + await CloseMarginPositionsIfNeed(message); + try { - //message = TraderUtils.FilterHighFreqValues(message, message.Direction == 1 ? pricesCache1 : pricesCache2); - - #region Добавление данных в кеши. - if (message.Figi == "BBG004730N88" || message.Figi == "FUTIMOEXF000") + if (message.IsHistoricalData) { - await _tradeDataProvider.AddData(message); + message = TraderUtils.FilterHighFreqValues(message, message.Direction == 1 ? pricesCache1 : pricesCache2); } - #endregion + if (_exchangeConfig.TradingInstrumentsFigis.Contains(message.Figi) && message.Direction == 1) { - var _15minCacheSize = TimeSpan.FromSeconds(400); - var smallWindow = TimeSpan.FromSeconds(180); - var bigWindow = TimeSpan.FromSeconds(360); - var meanWindow = TimeSpan.FromSeconds(360); - var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow; + await _tradeDataProvider.AddData(message); - var buys = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, _15minCacheSize, selector: (i) => i.Direction == 1); - var trades = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, _15minCacheSize); - if (trades.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Count, false, out var tradesDiff) - && buys.TryCalcTimeDiff(bigWindow, smallWindow, v => v.Price, true, out var pricesDiff)) + if (message.Figi == "FUTIMOEXF000") { - await _tradeDataProvider.LogPrice(message, "privcesDiff", pricesDiff); - await _tradeDataProvider.LogPrice(message, "tradevolume_diff", tradesDiff); - await _tradeDataProvider.AddData(message.Figi, "5min_diff", new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Value2 = tradesDiff, - Value = pricesDiff, - Figi = message.Figi, - Ticker = message.Ticker, - }); - - if (DPricesValues.TryGetValue(message.Figi, out var olddPrice)) - { - if (olddPrice < 0m && pricesDiff > 0) - { - //await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_in", message.Price); - } - if (olddPrice > 0m && pricesDiff < 0m) - { - //await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_in", message.Price); - } - } - DPricesValues[message.Figi] = pricesDiff; - var diffs = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, _15minCacheSize, "5min_diff"); - if (diffs.TryCalcPirsonCorrelation(meanWindow, out var pirson)) - { - var res = pirson; - await _tradeDataProvider.LogPrice(message, "diffs_pirson", (decimal)pirson); - await _tradeDataProvider.AddData(message.Figi, "diffs_pirson", new Contracts.Declisions.Dtos.CachedValue() - { - Time = message.Time, - Value = (decimal)pirson, - Figi = message.Figi, - Ticker = message.Ticker, - }); - if (DPirsonValues.TryGetValue(message.Figi, out var olddpirs)) - { - if (olddpirs < -0.3m && res > -0.3m && pricesDiff > 0 && (tradesDiff > 0)) - { - await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_in", message.Price); - } - if (olddpirs > 0.7m && res < 0.7m) - { - // await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_out", message.Price); - } - if (olddpirs > 0.3m && res < 0.3m && pricesDiff < 0 && (tradesDiff > 0)) - { - await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_in", message.Price); - } - if (olddpirs < -0.7m && res > -0.7m) - { - // await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_out", message.Price); - } - } - DPirsonValues[message.Figi] = res; - } + await ProcessIMOEXF(message); } } } @@ -219,6 +157,206 @@ namespace KLHZ.Trader.Core.Exchange.Services } } + private async Task ProcessIMOEXF(ITradeDataItem message) + { + if (message.Figi == "FUTIMOEXF000") + { + await CalcSupportLevels(message, 3, 5); + var stops = GetStops(message); + var pirson = await CalcPirson(message); + var declisionPirson = await ProcessPirson(pirson, message); + var declisionsSupportLevels = await ProcessSupportLevels(message); + var declisionsStops = ProcessStops(stops, 1.5m); + var res = TraderUtils.MergeResultsMult(declisionPirson, declisionsSupportLevels); + res = TraderUtils.MergeResultsMult(res, declisionsStops); + + await ExecuteDeclisions(res.ToImmutableDictionary(), message, stops, 1); + } + } + + private async Task> ProcessPirson(PirsonCalculatingResult pirson, ITradeDataItem message) + { + var res = TraderUtils.GetInitDict(0); + if (pirson.Success && DPirsonValues.TryGetValue(message.Figi, out var olddpirs)) + { + if (olddpirs < -0.3m && pirson.Pirson > -0.3m && pirson.PriceDiff > 0 && (pirson.TradesDiff > 0)) + { + res[TradingEvent.OpenLong] = Constants.PowerUppingCoefficient; + await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_in", message.Price); + } + if (olddpirs > 0.3m && pirson.Pirson < 0.3m && pirson.PriceDiff < 0 && (pirson.TradesDiff > 0)) + { + res[TradingEvent.OpenShort] = Constants.PowerUppingCoefficient; + await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_in", message.Price); + } + if (olddpirs > 0.7m && pirson.Pirson < 0.7m) + { + // await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_long_out", message.Price); + } + if (olddpirs < -0.7m && pirson.Pirson > -0.7m) + { + // await _tradeDataProvider.LogPrice(message, "diffs_pirson_diff_point_short_out", message.Price); + } + } + DPirsonValues[message.Figi] = pirson.Pirson; + return res.ToImmutableDictionary(); + } + + private async Task CalcPirson(ITradeDataItem message) + { + var cacheSize = TimeSpan.FromSeconds(400); + var smallWindow = TimeSpan.FromSeconds(180); + var bigWindow = TimeSpan.FromSeconds(360); + var meanWindowForCottelation = TimeSpan.FromSeconds(360); + var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow; + + var buys = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize, selector: (i) => i.Direction == 1); + var trades = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize); + if (trades.TryCalcTimeWindowsDiff(bigWindow, smallWindow, v => v.Count, false, out var tradesDiff) + && buys.TryCalcTimeDiff(bigWindow, smallWindow, v => v.Price, true, out var pricesDiff)) + { + await _tradeDataProvider.LogPrice(message, "privcesDiff", pricesDiff); + await _tradeDataProvider.LogPrice(message, "tradevolume_diff", tradesDiff); + await _tradeDataProvider.AddData(message.Figi, "5min_diff", new Contracts.Declisions.Dtos.CachedValue() + { + Time = message.Time, + Value2 = tradesDiff, + Value = pricesDiff, + Figi = message.Figi, + Ticker = message.Ticker, + }); + + var diffs = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, cacheSize, "5min_diff"); + if (diffs.TryCalcPirsonCorrelation(meanWindowForCottelation, out var pirson)) + { + var res = pirson; + await _tradeDataProvider.LogPrice(message, "diffs_pirson", (decimal)pirson); + //await _tradeDataProvider.AddData(message.Figi, "diffs_pirson", new Contracts.Declisions.Dtos.CachedValue() + //{ + // Time = message.Time, + // Value = (decimal)pirson, + // Figi = message.Figi, + // Ticker = message.Ticker, + //}); + return new PirsonCalculatingResult() + { + Pirson = res, + PriceDiff = pricesDiff, + TradesDiff = tradesDiff, + Success = true, + }; + } + } + return new PirsonCalculatingResult() + { + Success = false, + }; + } + + private async Task CloseMarginPositionsIfNeed(ITradeDataItem message) + { + var state = ExchangeScheduler.GetCurrentState(message.Time); + if (!message.IsHistoricalData && state == ExchangeState.ClearingTime) + { + var futuresFigis = _portfolioWrapper.Accounts.Values.SelectMany(v => v.Assets.Values.Where(a => a.Type == AssetType.Futures)).ToArray(); + await ClosePositions(futuresFigis, message, false); + } + } + + private async Task CalcSupportLevels(ITradeDataItem message, int leverage, decimal supportLevelWidth, int depthHours = 3) + { + if (_supportLevelsCalculationTimes.TryGetValue(message.Figi, out var lastTime)) + { + if ((message.Time - lastTime).TotalMinutes < 30) + { + return; + } + } + + var data = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, TimeSpan.FromHours(depthHours)); + if (data.Length > 0) + { + if (data[^1].Time - data[0].Time < TimeSpan.FromHours(depthHours - 1)) + { + data = await _tradeDataProvider.GetDataForTimeWindow(message.Figi, TimeSpan.FromHours(depthHours + 12)); + if (data[^1].Time - data[0].Time < TimeSpan.FromHours(depthHours - 1)) + { + return; + } + } + + var hist = Statistics.CalcHistogram(data); + var convs = Statistics.CalcConvolution(hist, leverage).ToList(); + var orderedConvs = convs.OrderByDescending(c => c.Sum).Take(5).ToList(); + orderedConvs = [.. orderedConvs.OrderBy(c => c.Value)]; + var levelsForAdd = new List(); + foreach (var c in orderedConvs) + { + var low = c.Value - supportLevelWidth; + var high = c.Value + supportLevelWidth; + + if (levelsForAdd.Count > 0) + { + var last = levelsForAdd.Last(); + if (last.HighValue < low) + { + levelsForAdd.Add(new SupportLevel() + { + HighValue = high, + LowValue = low, + Value = c.Value, + }); + } + else if (last.HighValue >= low && last.HighValue < high) + { + levelsForAdd[^1] = new SupportLevel() + { + LowValue = last.LowValue, + HighValue = high, + Value = last.LowValue + (high - last.LowValue) / 2 + }; + } + } + else + { + levelsForAdd.Add(new SupportLevel() + { + HighValue = high, + LowValue = low, + Value = c.Value, + }); + } + } + var finalLevels = new SupportLevel[levelsForAdd.Count]; + var i = 0; + foreach (var level in levelsForAdd) + { + + DateTime? time = null; + foreach (var item in data) + { + if (item.Price >= level.LowValue && item.Price < level.HighValue) + { + time = item.Time; + } + } + finalLevels[i] = new SupportLevel() + { + HighValue = level.HighValue, + LowValue = level.LowValue, + Value = level.Value, + LastLevelTime = time, + }; + i++; + } + + SupportLevels[message.Figi] = finalLevels; + await _tradeDataProvider.LogPrice(message, "support_level_calc", message.Price); + } + + _supportLevelsCalculationTimes[message.Figi] = message.Time; + } + private async Task ClosePositions(Asset[] assets, ITradeDataItem message, bool withProfitOnly = true) { var loggedDeclisions = 0; @@ -308,19 +446,20 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task ExecuteDeclisions(ITradeDataItem message, ImmutableDictionary result) + private async Task ExecuteDeclisions(ImmutableDictionary result, ITradeDataItem message, Stops st, int accountsForOpening = 1) { var state = ExchangeScheduler.GetCurrentState(); - if (result[TradingEvent.UptrendStart] >= Constants.UppingCoefficient + if (result[TradingEvent.OpenLong] >= Constants.UppingCoefficient && state == ExchangeState.Open ) { - var stops = GetStops(message, PositionType.Long); + var stops = st.GetStops(PositionType.Long); + if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { var accounts = _portfolioWrapper.Accounts .Where(a => !a.Value.Assets.ContainsKey(message.Figi)) - .Take(1) + .Take(accountsForOpening) .Select(a => a.Value) .ToArray(); await OpenPositions(accounts, message, PositionType.Long, stops.stopLoss, stops.takeProfit, 1); @@ -329,11 +468,11 @@ namespace KLHZ.Trader.Core.Exchange.Services await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, message.Price + stops.takeProfit, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsLong, message.Price - stops.stopLoss, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message); } - if (result[TradingEvent.DowntrendStart] >= Constants.UppingCoefficient + if (result[TradingEvent.OpenShort] >= Constants.UppingCoefficient && state == ExchangeState.Open ) { - var stops = GetStops(message, PositionType.Short); + var stops = st.GetStops(PositionType.Long); if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { var accounts = _portfolioWrapper.Accounts @@ -348,7 +487,7 @@ namespace KLHZ.Trader.Core.Exchange.Services await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, message.Price - stops.takeProfit, message.Time.AddMilliseconds(-RandomNumberGenerator.GetInt32(300, 1000)), message); await _tradeDataProvider.LogDeclision(DeclisionTradeAction.ResetStopsShort, message.Price + stops.stopLoss, message.Time.AddMilliseconds(RandomNumberGenerator.GetInt32(300, 1000)), message); } - if (result[TradingEvent.UptrendEnd] >= Constants.UppingCoefficient * 10) + if (result[TradingEvent.CloseLong] >= Constants.UppingCoefficient * 10) { if (!message.IsHistoricalData && BotModeSwitcher.CanSell()) { @@ -362,7 +501,7 @@ namespace KLHZ.Trader.Core.Exchange.Services } - if (result[TradingEvent.DowntrendEnd] >= Constants.UppingCoefficient * 10) + if (result[TradingEvent.CloseShort] >= Constants.UppingCoefficient * 10) { if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { @@ -409,11 +548,86 @@ namespace KLHZ.Trader.Core.Exchange.Services return res; } - private (decimal stopLoss, decimal takeProfit) GetStops(ITradeDataItem message, PositionType type) + private Stops GetStops(ITradeDataItem message) { - decimal stopLossShift = 2m; - decimal takeProfitShift = 6; - return (stopLossShift, takeProfitShift); + decimal longStopLossShift = message.Value * 0.99m; + decimal longTakeProfitShift = message.Value * 1.03m; + decimal shortStopLossShift = message.Value * 0.99m; + decimal shortTakeProfitShift = message.Value * 1.03m; + if (SupportLevels.TryGetValue(message.Figi, out var levels)) + { + if (levels.Length > 0) + { + var levelsByTime = levels.Where(l => l.LastLevelTime.HasValue) + .OrderByDescending(l => l.LastLevelTime) + .ToArray(); + if (message.Price >= levelsByTime[0].LowValue && message.Price < levelsByTime[0].HighValue) + { + longStopLossShift = message.Price - levelsByTime[0].LowValue; + shortStopLossShift = message.Price + levelsByTime[0].HighValue; + } + else + { + var levelsByDiff = levels.Where(l => l.LastLevelTime.HasValue) + .OrderBy(l => System.Math.Abs(l.Value - message.Price)) + .ToArray(); + var nearestLevel = levelsByDiff[0]; + longStopLossShift = message.Price - nearestLevel.HighValue; + shortStopLossShift = nearestLevel.LowValue - message.Price; + } + } + } + return new Stops(longStopLossShift, longTakeProfitShift, shortStopLossShift, shortTakeProfitShift); + } + + private static ImmutableDictionary ProcessStops(Stops stops, decimal meanfullLevel) + { + var res = TraderUtils.GetInitDict(1); + if (stops.LongTakeProfitShift < meanfullLevel || stops.LongStopLossShift < meanfullLevel) + { + res[TradingEvent.OpenLong] = Constants.BlockingCoefficient; + } + if (stops.ShortTakeProfitShift < meanfullLevel || stops.ShortStopLossShift < meanfullLevel) + { + res[TradingEvent.OpenShort] = Constants.BlockingCoefficient; + } + + return res.ToImmutableDictionary(); + } + + private async Task> ProcessSupportLevels(ITradeDataItem message) + { + var res = TraderUtils.GetInitDict(1); + if (SupportLevels.TryGetValue(message.Figi, out var levels)) + { + foreach (var lev in levels) + { + if (message.Price >= lev.LowValue && message.Price < lev.HighValue) + { + await _tradeDataProvider.LogPrice(message, "support_level", message.Price); + } + } + + if (levels.Length > 0) + { + var levelsByTime = levels.Where(l => l.LastLevelTime.HasValue) + .OrderByDescending(l => l.LastLevelTime) + .ToArray(); + + if (message.Price >= levelsByTime[0].LowValue && message.Price < levelsByTime[0].HighValue) + { + if (message.Price > levelsByTime[0].Value) + { + res[TradingEvent.OpenLong] = Constants.BlockingCoefficient; + } + if (message.Price < levelsByTime[0].Value) + { + res[TradingEvent.OpenShort] = Constants.BlockingCoefficient; + } + } + } + } + return res.ToImmutableDictionary(); } } } diff --git a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs index 520460e..1d79dcc 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs @@ -86,10 +86,12 @@ namespace KLHZ.Trader.Core.Exchange.Services } return ValueTask.FromResult(Array.Empty()); } + public ValueTask GetDataFrom20SecondsWindowCache2(string figi, string key) { return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(20), key); } + public ValueTask GetDataFrom1MinuteWindowCache2(string figi, string key) { return GetDataForTimeWindow(figi, TimeSpan.FromSeconds(60), key); @@ -140,7 +142,7 @@ namespace KLHZ.Trader.Core.Exchange.Services if (_isDataRecievingAllowed) { - var time = DateTime.UtcNow.AddHours(-1.5); + var time = DateTime.UtcNow.AddHours(-5); using var context1 = await _dbContextFactory.CreateDbContextAsync(); context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; var data = await context1.PriceChanges diff --git a/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs b/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs index dcbeaa9..2f28bfd 100644 --- a/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs +++ b/KLHZ.Trader.Core/Exchange/Utils/TraderUtils.cs @@ -4,33 +4,35 @@ using KLHZ.Trader.Core.Contracts.Messaging.Dtos.Interfaces; using KLHZ.Trader.Core.DataLayer.Entities.Prices; using KLHZ.Trader.Core.Exchange.Interfaces; using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; -using System.Collections.Immutable; namespace KLHZ.Trader.Core.Exchange.Utils { internal static class TraderUtils { - internal static Dictionary MergeResultsMult(Dictionary res, ImmutableDictionary data) + internal static Dictionary MergeResultsMult(IDictionary result, IDictionary data) { - foreach (var k in res.Keys) + var res = new Dictionary(); + foreach (var k in result.Keys) { - var valRes = res[k]; + var valRes = result[k]; var valData = data[k]; res[k] = valRes * valData; } return res; } - internal static Dictionary MergeResultsMax(Dictionary res, ImmutableDictionary data) + internal static Dictionary MergeResultsMax(IDictionary result, IDictionary data) { - foreach (var k in res.Keys) + var res = new Dictionary(); + foreach (var k in result.Keys) { - var valRes = res[k]; - var valData = data[k]; + var valRes = result[k]; + var valData = result[k]; res[k] = System.Math.Max(valRes, valData); } return res; } + internal static Dictionary GetInitDict(decimal initValue) { var values = Enum.GetValues(); diff --git a/KLHZ.Trader.Service/Controllers/PlayController.cs b/KLHZ.Trader.Service/Controllers/PlayController.cs index 3602481..d017fb0 100644 --- a/KLHZ.Trader.Service/Controllers/PlayController.cs +++ b/KLHZ.Trader.Service/Controllers/PlayController.cs @@ -286,12 +286,9 @@ namespace KLHZ.Trader.Service.Controllers var leverage = 3; var hist = Statistics.CalcHistogram(prices); var convs = Statistics.CalcConvolution(hist, leverage).ToList(); - //var result = new List(); - //Statistics.MergeConvolutionResults(convs, result); var orderedConvs = convs.OrderByDescending(c => c.Sum).Take(5).ToList(); orderedConvs = orderedConvs.OrderBy(c => c.Value).ToList(); var values = new List(); - var shifts = new List(); foreach (var c in orderedConvs) { @@ -354,7 +351,5 @@ namespace KLHZ.Trader.Service.Controllers } } } - - } }