diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/ShapeAreaCalculator.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/ShapeAreaCalculator.cs index 60f07be..135a114 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/ShapeAreaCalculator.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/ShapeAreaCalculator.cs @@ -63,5 +63,36 @@ { return System.Math.Sqrt(System.Math.Pow(x2 - x1, 2) + System.Math.Pow(y2 - y1, 2)); } + + public static bool TryGetAreasRelation(DateTime[] times, decimal[] values, decimal currentValue, TimeSpan boundTimeSpan, out double relation) + { + var upArea = 0d; + var downArea = 0d; + var startTime = times[times.Length - 1]; + for (int i = 1; i < times.Length - 2; i++) + { + var k = values.Length - i; + if (startTime - times[k] > boundTimeSpan) + { + break; + } + var point = (double)(values[k] - currentValue); + var time = times[k]; + var timePrev = times[k - 1]; + var dt = (time - timePrev).TotalSeconds; + var ar = dt * point; + if (ar > 0) + { + upArea += ar; + } + else + { + downArea += System.Math.Abs(ar); + } + } + var area = downArea + upArea; + relation = area != 0 ? (double)upArea / area * 100 : 0; + return area != 0; + } } } diff --git a/KLHZ.Trader.Core.Tests/TraderTests.cs b/KLHZ.Trader.Core.Tests/TraderTests.cs index 7799fd2..8dd80c4 100644 --- a/KLHZ.Trader.Core.Tests/TraderTests.cs +++ b/KLHZ.Trader.Core.Tests/TraderTests.cs @@ -94,7 +94,7 @@ namespace KLHZ.Trader.Core.Tests [Test] public void CalcProfitTest() { - var profit = TradingCalculator.CaclProfit(2990, 2991.5m, 0.0025m, 10.3m, false); + var profit = TradingCalculator.CaclProfit(2990, 2985m, 0.0025m, 10.3m, false); } } } diff --git a/KLHZ.Trader.Core/Common/BotModeSwitcher.cs b/KLHZ.Trader.Core/Common/BotModeSwitcher.cs index 5efaa4e..438658c 100644 --- a/KLHZ.Trader.Core/Common/BotModeSwitcher.cs +++ b/KLHZ.Trader.Core/Common/BotModeSwitcher.cs @@ -3,8 +3,8 @@ public static class BotModeSwitcher { private readonly static object _locker = new(); - private static bool _canSell = false; - private static bool _canPurchase = false; + private static bool _canSell = true; + private static bool _canPurchase = true; public static bool CanSell() { diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index bf0061a..abf3481 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -77,10 +77,17 @@ namespace KLHZ.Trader.Core.Exchange.Services private async Task ProcessPrices() { + var buffer = new LinkedList<(DateTime, double)>(); + var tradesBufferBuys = new LinkedList<(DateTime, double)>(); + var tradesBufferSells = new LinkedList<(DateTime, double)>(); + var tradesRelBuffer = new LinkedList<(DateTime, decimal)>(); while (await _pricesChannel.Reader.WaitToReadAsync()) { var message = await _pricesChannel.Reader.ReadAsync(); - + if (message.IsHistoricalData) + { + await _tradeDataProvider.AddData(message, TimeSpan.FromHours(6)); + } if (_tradingInstrumentsFigis.Contains(message.Figi)) { var currentTime = message.IsHistoricalData ? message.Time : DateTime.UtcNow; @@ -92,14 +99,18 @@ namespace KLHZ.Trader.Core.Exchange.Services if (message.Figi == "FUTIMOEXF000") { var windowMaxSize = 1000; - + await SellAssetsIfNeed(message); var data = await _tradeDataProvider.GetData(message.Figi, windowMaxSize); + if (data.timestamps.Length <= 1) + { + buffer.Clear(); + } var state = ExchangeScheduler.GetCurrentState(message.Time); await ProcessClearing(data, state, message); //await SellOldAssetsIfCan(message); ProcessOpeningStops(message, currentTime); - await ProcessNewPriceIMOEXF(data, state, message, windowMaxSize); + await ProcessNewPriceIMOEXF(data, state, message, windowMaxSize, buffer, tradesBufferBuys, tradesBufferSells, tradesRelBuffer); } } catch (Exception ex) @@ -110,18 +121,19 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task SellOldAssetsIfCan(INewPrice message) + private async Task SellAssetsIfNeed(INewPrice message) { var accounts = _tradeDataProvider.Accounts.Values.ToArray(); var assetType = _tradeDataProvider.GetAssetTypeByFigi(message.Figi); foreach (var acc in accounts) { - var assets = acc.Assets.Values.Where(a => a.Figi == message.Figi && (DateTime.UtcNow - a.BoughtAt > TimeSpan.FromHours(4))).ToArray(); + var assets = acc.Assets.Values.Where(a => a.Figi == message.Figi).ToArray(); foreach (var asset in assets) { var profit = TradingCalculator.CaclProfit(asset.BoughtPrice, message.Value, GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); - if (profit > 0) + + if (message.Time - asset.BoughtAt > TimeSpan.FromMinutes(4) && profit<-66m) { await _dataBus.Broadcast(new TradeCommand() { @@ -131,6 +143,23 @@ namespace KLHZ.Trader.Core.Exchange.Services : Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, Count = (long)asset.Count, RecomendPrice = null, + EnableMargin = false, + }); + OpeningStops[message.Figi] = DateTime.UtcNow.AddMinutes(10); + await LogDeclision(DeclisionTradeAction.CloseLong, message, profit); + } + + if (message.Time - asset.BoughtAt > TimeSpan.FromHours(4) && profit> 100) + { + await _dataBus.Broadcast(new TradeCommand() + { + AccountId = asset.AccountId, + Figi = message.Figi, + CommandType = asset.Count < 0 ? Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketBuy + : Contracts.Messaging.Dtos.Enums.TradeCommandType.MarketSell, + Count = (long)asset.Count, + RecomendPrice = null, + EnableMargin = false, }); await LogDeclision(DeclisionTradeAction.CloseLong, message, profit); } @@ -138,9 +167,11 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task ProcessNewPriceIMOEXF((DateTime[] timestamps, decimal[] prices, decimal bidsCount, decimal asksCount) data, + private async Task ProcessNewPriceIMOEXF((DateTime[] timestamps, decimal[] prices) data, ExchangeState state, - INewPrice message, int windowMaxSize) + INewPrice message, int windowMaxSize, LinkedList<(DateTime time, double val)> areasBuffer, + LinkedList<(DateTime time, double val)> tradesBufferBuys, LinkedList<(DateTime time, double val)> tradesBufferSells, + LinkedList<(DateTime time, decimal val)> tradesRelBufferSells) { var res = TradingEvent.None; var resultMoveAvFull = MovingAverage.CheckByWindowAverageMean(data.timestamps, data.prices, @@ -154,6 +185,70 @@ namespace KLHZ.Trader.Core.Exchange.Services await LogPrice(message, _smallWindowProcessor, resultMoveAvFull.smallWindowAv); } + //var oldTotalSales = (decimal)tradesBufferSells.Sum(s => s.val); + //var oldTotalBuys = (decimal)tradesBufferBuys.Sum(s => s.val); + //var oldTotalTrades = oldTotalSales + oldTotalBuys; + + //if (message.Direction == 1) + //{ + // tradesBufferBuys.AddLast((message.Time, message.Count)); + + // if (tradesBufferBuys.Last != null && tradesBufferBuys.First != null + // && tradesBufferBuys.Last.Value.time - tradesBufferBuys.First.Value.time > TimeSpan.FromSeconds(60)) + // { + // tradesBufferBuys.RemoveFirst(); + // } + //} + + //if (message.Direction == 2) + //{ + // tradesBufferSells.AddLast((message.Time, message.Count)); + + // if (tradesBufferSells.Last != null && tradesBufferSells.First != null + // && tradesBufferSells.Last.Value.time - tradesBufferSells.First.Value.time > TimeSpan.FromSeconds(60)) + // { + // tradesBufferSells.RemoveFirst(); + // } + //} + + //var totalSales = (decimal)tradesBufferSells.Sum(s => s.val); + //var totalBuys = (decimal)tradesBufferBuys.Sum(s => s.val); + //var totalTrades = totalSales + totalBuys; + //var tradesRelation = -100m; + //var oldTradesRelation = -100m; + //await LogPrice(message, "tradesvolume", totalTrades); + + //if (totalTrades > 0 && oldTotalTrades > 0) + //{ + // tradesRelation = (totalBuys - totalSales) / totalTrades; + // oldTradesRelation = (oldTotalBuys - oldTotalSales) / oldTotalTrades; + + // tradesRelBufferSells.AddLast((message.Time, tradesRelation - oldTradesRelation)); + + // if (tradesRelBufferSells.Last != null && tradesRelBufferSells.First != null + // && tradesRelBufferSells.Last.Value.time - tradesRelBufferSells.First.Value.time > TimeSpan.FromSeconds(10)) + // { + // tradesRelBufferSells.RemoveFirst(); + // } + + // if (tradesRelBufferSells.Count > 0) + // { + // await LogPrice(message, "tradesrelation", tradesRelBufferSells.Sum(e => e.val) / tradesRelBufferSells.Count); + // } + //} + + var areasRel = -1m; + if (ShapeAreaCalculator.TryGetAreasRelation(data.timestamps, data.prices, message.Value, TimeSpan.FromMinutes(15), out var rel)) + { + areasBuffer.AddLast((message.Time, rel)); + if (areasBuffer.Last != null && areasBuffer.First != null + && areasBuffer.Last.Value.time - areasBuffer.First.Value.time > TimeSpan.FromMinutes(1)) + { + areasBuffer.RemoveFirst(); + } + areasRel = (decimal)areasBuffer.Sum(a => a.val) / areasBuffer.Count; + await LogPrice(message, "balancescalc30min", areasRel); + } if ((res & TradingEvent.UptrendStart) == TradingEvent.UptrendStart && !OpeningStops.TryGetValue(message.Figi, out _) && state == ExchangeState.Open @@ -161,8 +256,7 @@ namespace KLHZ.Trader.Core.Exchange.Services && (data.timestamps[data.timestamps.Length - 1] - data.timestamps[data.timestamps.Length - 2] < TimeSpan.FromMinutes(1)) ) { - var fullData = await _tradeDataProvider.GetData(message.Figi, TimeSpan.FromMinutes(30)); - if (fullData.isFullIntervalExists && fullData.prices.Last() - fullData.prices.First() > -8) + if (areasRel >= 20 && areasRel < 75) { if (!message.IsHistoricalData && BotModeSwitcher.CanPurchase()) { @@ -196,6 +290,10 @@ namespace KLHZ.Trader.Core.Exchange.Services } await LogDeclision(DeclisionTradeAction.OpenLong, message); } + //else if (areasRel >=75) + //{ + // await LogDeclision(DeclisionTradeAction.OpenShort, message); + //} } if ((res & TradingEvent.UptrendEnd) == TradingEvent.UptrendEnd) @@ -245,7 +343,7 @@ namespace KLHZ.Trader.Core.Exchange.Services } } - private async Task ProcessClearing((DateTime[] timestamps, decimal[] prices, decimal bidsCount, decimal asksCount) data, ExchangeState state, INewPrice message) + private async Task ProcessClearing((DateTime[] timestamps, decimal[] prices) data, ExchangeState state, INewPrice message) { if (state == ExchangeState.ClearingTime && !message.IsHistoricalData diff --git a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs index fd4e5d5..89798c0 100644 --- a/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs +++ b/KLHZ.Trader.Core/Exchange/Services/TraderDataProvider.cs @@ -64,14 +64,14 @@ namespace KLHZ.Trader.Core.Exchange.Services return (Array.Empty(), Array.Empty(), false); } - public async ValueTask<(DateTime[] timestamps, decimal[] prices, decimal bidsCount, decimal asksCount)> GetData(string figi, int? length = null) + public async ValueTask<(DateTime[] timestamps, decimal[] prices)> GetData(string figi, int? length = null) { if (_historyCash.TryGetValue(figi, out var unit)) { var res = await unit.GetData(length); - return (res.timestamps, res.prices, unit.BidsCount, unit.AsksCount); + return (res.timestamps, res.prices); } - return (Array.Empty(), Array.Empty(), 1, 1); + return (Array.Empty(), Array.Empty()); } public async ValueTask AddData(INewPrice message, TimeSpan? clearingInterval = null) diff --git a/KLHZ.Trader.Service/Controllers/PlayController.cs b/KLHZ.Trader.Service/Controllers/PlayController.cs index fbe4dbc..2929a5b 100644 --- a/KLHZ.Trader.Service/Controllers/PlayController.cs +++ b/KLHZ.Trader.Service/Controllers/PlayController.cs @@ -3,6 +3,7 @@ using KLHZ.Trader.Core.Contracts.Messaging.Interfaces; using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.DataLayer.Entities.Orders; using KLHZ.Trader.Core.DataLayer.Entities.Prices; +using KLHZ.Trader.Core.Exchange.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -12,13 +13,15 @@ namespace KLHZ.Trader.Service.Controllers [Route("[controller]/[action]")] public class PlayController : ControllerBase { + private readonly TraderDataProvider _traderDataProvider; private readonly IDataBus _dataBus; private readonly IDbContextFactory _dbContextFactory; - public PlayController(IDataBus dataBus, IDbContextFactory dbContextFactory) + public PlayController(IDataBus dataBus, IDbContextFactory dbContextFactory, TraderDataProvider traderDataProvider) { _dbContextFactory = dbContextFactory; _dataBus = dataBus; + _traderDataProvider = traderDataProvider; } [HttpGet] @@ -26,8 +29,8 @@ namespace KLHZ.Trader.Service.Controllers { try { - var time1 = DateTime.UtcNow.AddDays(-7); - var time2 = DateTime.UtcNow.AddMinutes(-30); + var time1 = DateTime.UtcNow.AddDays(-30); + var time2 = DateTime.UtcNow.AddMinutes(30); using var context1 = await _dbContextFactory.CreateDbContextAsync(); context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; var data = await context1.PriceChanges @@ -39,7 +42,9 @@ namespace KLHZ.Trader.Service.Controllers Ticker = c.Ticker, Time = c.Time, Value = c.Value, - IsHistoricalData = true + IsHistoricalData = true, + Direction = c.Direction, + Count = c.Count, }) .ToArrayAsync(); @@ -193,6 +198,95 @@ namespace KLHZ.Trader.Service.Controllers } } + + + //[HttpGet] + //public async Task GetBalance(string figi) + //{ + // try + // { + // var time1 = DateTime.UtcNow.AddDays(-7); + // using var context1 = await _dbContextFactory.CreateDbContextAsync(); + // context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + // var data = await context1.PriceChanges + // .Where(c => c.Figi == figi && c.Time >= time1) + // .OrderBy(c => c.Time) + // .Select(c => new NewPriceMessage() + // { + // Figi = figi, + // Ticker = c.Ticker, + // Time = c.Time, + // Value = c.Value, + // IsHistoricalData = true + // }) + // .ToArrayAsync(); + // var buffer = new List(); + // var list = new LinkedList<(DateTime time, double value)>(); + // var previous = -1000d; + // foreach (var mess in data) + // { + // await _traderDataProvider.AddData(mess, TimeSpan.FromHours(6)); + // var dataFromCache = await _traderDataProvider.GetData(figi, TimeSpan.FromMinutes(15)); + // if (dataFromCache.isFullIntervalExists) + // { + // if (ShapeAreaCalculator.TryGetAreasRelation(dataFromCache.timestamps, dataFromCache.prices,mess.Value,out var res)) + // { + // if (list.Count > 0 && mess.Time - list.Last().time > TimeSpan.FromMinutes(5)) + // { + // list.Clear(); + // } + // list.AddLast((mess.Time, res)); + // if (list.Last().time - list.First().time > TimeSpan.FromMinutes(1)) + // { + // list.RemoveFirst(); + // } + // var newRes = (decimal)(list.Sum(i => i.value) / list.Count); + // if (list.Count > 0) + // { + // try + // { + // buffer.Add(new ProcessedPrice() + // { + // Figi = figi, + // Processor = "balancescalc30min", + // Ticker = mess.Ticker, + // Time = mess.Time, + // Value = newRes, + // //Value = (decimal)res, + // }); + // } + // catch(Exception ex) + // { + + // } + + // } + // } + // } + // else + // { + // previous = -1d; + // } + // if (buffer.Count > 10000) + // { + // await context1.ProcessedPrices.AddRangeAsync(buffer); + // await context1.SaveChangesAsync(); + // buffer.Clear(); + // } + // } + // if (buffer.Count > 0) + // { + // await context1.ProcessedPrices.AddRangeAsync(buffer); + // await context1.SaveChangesAsync(); + // buffer.Clear(); + // } + // } + // catch (Exception ex) + // { + + // } + //} + ////[HttpGet] //public async Task LoadTradesToHistory(string figi) //{