diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs index 5d8466f..ccb7916 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/FFT.cs @@ -82,7 +82,6 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils return (resDt, resVs); } - public static (DateTime[] timestamps, decimal[] values) TrimValues(DateTime[] timestamps, decimal[] values, TimeSpan leftBound, TimeSpan rightBound) { var resDt = new List(); diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs index 3602baa..71051cd 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/MovingAverage.cs @@ -232,7 +232,7 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } public static (TradingEvent events, decimal bigWindowAv, decimal smallWindowAv) CheckByWindowAverageMean2(ITradeDataItem[] data, int size, int smallWindow, int bigWindow, -decimal? uptrendStartingDetectionMeanfullStep = null, decimal? uptrendEndingDetectionMeanfullStep = null) + decimal? uptrendStartingDetectionMeanfullStep = null, decimal? uptrendEndingDetectionMeanfullStep = null) { var res = TradingEvent.None; var bigWindowAv = 0m; diff --git a/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs b/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs index 2642dc0..0733a2d 100644 --- a/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs +++ b/KLHZ.Trader.Core.Math/Declisions/Utils/Statistics.cs @@ -5,7 +5,6 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils { public static class Statistics { - public static decimal MeanCount(this ITradeDataItem[] values) { return values.Sum(x => x.Count) / values.Length; @@ -197,25 +196,5 @@ namespace KLHZ.Trader.Core.Math.Declisions.Utils } return Array.Empty(); } - - public static void MergeConvolutionResults(List results, List mergedResults) - { - if (results.Count == 0) - { - return; - } - var resultsOrdered = results.OrderBy(r => r.Sum).ToList(); - var res = resultsOrdered[0]; - var b1 = res.Shift - res.Leverage; - var b2 = res.Shift + res.Leverage; - var forMerge = results.Where(r => r.Shift >= b1 && r.Shift <= b2).ToList(); - res.Sum = forMerge.Sum(r => r.Sum); - foreach (var m in forMerge) - { - results.Remove(m); - } - mergedResults.Add(res); - MergeConvolutionResults(results, mergedResults); - } } } diff --git a/KLHZ.Trader.Core/Exchange/Interfaces/IManagedAccount.cs b/KLHZ.Trader.Core/Exchange/Interfaces/IManagedAccount.cs index fb65c01..9966b30 100644 --- a/KLHZ.Trader.Core/Exchange/Interfaces/IManagedAccount.cs +++ b/KLHZ.Trader.Core/Exchange/Interfaces/IManagedAccount.cs @@ -1,5 +1,6 @@ using KLHZ.Trader.Core.Contracts.Common.Enums; using KLHZ.Trader.Core.Exchange.Models.AssetsAccounting; +using KLHZ.Trader.Core.Exchange.Models.Trading; using System.Collections.Immutable; namespace KLHZ.Trader.Core.Exchange.Interfaces @@ -14,8 +15,8 @@ namespace KLHZ.Trader.Core.Exchange.Interfaces Task Init(string accountId, string? accountName = null); Task LoadPortfolio(); ImmutableDictionary Assets { get; } - public Task OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1); - public Task ClosePosition(string figi); + public Task OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1, decimal pointPrice = 1); + public Task ClosePosition(string figi); public Task ResetStops(string figi, decimal stopLossShift, decimal takeProfitShift); } } diff --git a/KLHZ.Trader.Core/Exchange/Models/Configs/InstrumentSettings.cs b/KLHZ.Trader.Core/Exchange/Models/Configs/InstrumentSettings.cs index 43b73dd..1d919a9 100644 --- a/KLHZ.Trader.Core/Exchange/Models/Configs/InstrumentSettings.cs +++ b/KLHZ.Trader.Core/Exchange/Models/Configs/InstrumentSettings.cs @@ -5,6 +5,6 @@ public required string Figi { get; init; } public decimal ShortLeverage { get; init; } public decimal LongLeverage { get; init; } - public decimal PriceToRubConvertationCoefficient { get; init; } = 1; + public decimal PointPriceRub { get; init; } = 1; } } diff --git a/KLHZ.Trader.Core/Exchange/Models/Trading/TradeResult.cs b/KLHZ.Trader.Core/Exchange/Models/Trading/TradeResult.cs new file mode 100644 index 0000000..021a791 --- /dev/null +++ b/KLHZ.Trader.Core/Exchange/Models/Trading/TradeResult.cs @@ -0,0 +1,10 @@ +namespace KLHZ.Trader.Core.Exchange.Models.Trading +{ + public class TradeResult + { + public bool Success { get; init; } + public decimal ExecutedPrice { get; init; } + public decimal Comission { get; init; } + public long Count { get; init; } + } +} diff --git a/KLHZ.Trader.Core/Exchange/Models/Trading/TradingMode.cs b/KLHZ.Trader.Core/Exchange/Models/Trading/TradingMode.cs deleted file mode 100644 index 7ef26d8..0000000 --- a/KLHZ.Trader.Core/Exchange/Models/Trading/TradingMode.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace KLHZ.Trader.Core.Exchange.Models.Trading -{ - public enum TradingMode - { - None = 0, - Stable = 1, - SlowDropping = -1, - SlowGrowing = 2, - Growing = 3, - Dropping = -2, - } -} diff --git a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs index 29b7582..9fa37fa 100644 --- a/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs +++ b/KLHZ.Trader.Core/Exchange/Services/ManagedAccount.cs @@ -4,6 +4,7 @@ using KLHZ.Trader.Core.DataLayer; using KLHZ.Trader.Core.Exchange.Extentions; using KLHZ.Trader.Core.Exchange.Interfaces; using KLHZ.Trader.Core.Exchange.Models.Configs; +using KLHZ.Trader.Core.Exchange.Models.Trading; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -164,8 +165,9 @@ namespace KLHZ.Trader.Core.Exchange.Services Balance = portfolio.TotalAmountCurrencies; } - public async Task OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1) + public async Task OpenPosition(string figi, PositionType positionType, decimal stopLossShift, decimal takeProfitShift, long count = 1, decimal pointPrice = 1) { + TradeResult? result = null; try { await _semaphore.WaitAsync2(_defaultLockTimeSpan); @@ -186,58 +188,54 @@ namespace KLHZ.Trader.Core.Exchange.Services var res = await _investApiClient.Orders.PostOrderAsync(req); _usedOrderIds.TryAdd(res.OrderId, DateTime.UtcNow); - var executedPrice = res.ExecutedOrderPrice / 10; + var executedPrice = res.ExecutedOrderPrice / pointPrice; - await Task.Delay(1000); - - if (stopLossShift == 0) + if (stopLossShift != 0) { - stopLossShift = 0.002m * executedPrice; + var pricesl = positionType == PositionType.Long ? executedPrice - stopLossShift : executedPrice + stopLossShift; + var slReq = new PostStopOrderRequest() + { + AccountId = AccountId, + ConfirmMarginTrade = false, + InstrumentId = figi, + Direction = stopOrdersDirection, + PriceType = PriceType.Point, + Quantity = count, + StopOrderType = StopOrderType.StopLoss, + StopPrice = pricesl, + ExchangeOrderType = ExchangeOrderType.Market, + ExpirationType = StopOrderExpirationType.GoodTillCancel, + }; + var slOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(slReq); } - if (takeProfitShift == 0) + + if (takeProfitShift != 0) { - takeProfitShift = 0.01m * executedPrice; + var pricetp = positionType == PositionType.Long ? executedPrice + takeProfitShift : executedPrice - takeProfitShift; + var tpReq = new PostStopOrderRequest() + { + AccountId = AccountId, + ConfirmMarginTrade = false, + InstrumentId = figi, + Direction = stopOrdersDirection, + PriceType = PriceType.Point, + Quantity = count, + StopOrderType = StopOrderType.TakeProfit, + StopPrice = pricetp, + ExchangeOrderType = ExchangeOrderType.Market, + ExpirationType = StopOrderExpirationType.GoodTillCancel, + }; + var tpOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(tpReq); } - 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, - PriceType = PriceType.Point, - Quantity = count, - StopOrderType = StopOrderType.StopLoss, - StopPrice = pricesl, - ExchangeOrderType = ExchangeOrderType.Market, - ExpirationType = StopOrderExpirationType.GoodTillCancel, - }; - var slOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(slReq); - - var tpReq = new PostStopOrderRequest() - { - AccountId = AccountId, - ConfirmMarginTrade = false, - InstrumentId = figi, - Direction = stopOrdersDirection, - PriceType = PriceType.Point, - Quantity = count, - StopOrderType = StopOrderType.TakeProfit, - StopPrice = pricetp, - ExchangeOrderType = ExchangeOrderType.Market, - ExpirationType = StopOrderExpirationType.GoodTillCancel, - }; - var tpOrderRes = await _investApiClient.StopOrders.PostStopOrderAsync(tpReq); await LoadPortfolioNolock(); + result = new TradeResult() + { + Success = true, + Comission = res.ExecutedCommission, + Count = res.LotsExecuted, + ExecutedPrice = res.ExecutedOrderPrice, + }; } } catch (TaskCanceledException) { } @@ -247,6 +245,8 @@ namespace KLHZ.Trader.Core.Exchange.Services } _semaphore.Release(); + result ??= new TradeResult(); + return result; } public async Task ResetStops(string figi, decimal stopLossShift, decimal takeProfitShift) @@ -317,8 +317,9 @@ namespace KLHZ.Trader.Core.Exchange.Services _semaphore.Release(); } - public async Task ClosePosition(string figi) + public async Task ClosePosition(string figi) { + TradeResult? result = null; try { await _semaphore.WaitAsync2(_defaultLockTimeSpan); @@ -355,6 +356,13 @@ namespace KLHZ.Trader.Core.Exchange.Services } await LoadPortfolioNolock(); + result = new TradeResult() + { + Success = true, + Comission = res.ExecutedCommission, + Count = res.LotsExecuted, + ExecutedPrice = res.ExecutedOrderPrice, + }; } } catch (TaskCanceledException) { } @@ -364,6 +372,8 @@ namespace KLHZ.Trader.Core.Exchange.Services } _semaphore.Release(); + result ??= new TradeResult(); + return result; } private async Task CyclingOperations() diff --git a/KLHZ.Trader.Core/Exchange/Services/Trader.cs b/KLHZ.Trader.Core/Exchange/Services/Trader.cs index 2d7da78..5ff8128 100644 --- a/KLHZ.Trader.Core/Exchange/Services/Trader.cs +++ b/KLHZ.Trader.Core/Exchange/Services/Trader.cs @@ -32,12 +32,10 @@ namespace KLHZ.Trader.Core.Exchange.Services private readonly ExchangeConfig _exchangeConfig; private readonly ILogger _logger; - private readonly ConcurrentDictionary TradingModes = new(); - private readonly ConcurrentDictionary DeferredDeclisions = new(); private readonly ConcurrentDictionary SupportLevels = new(); private readonly ConcurrentDictionary _pirsonValues = new(); - private readonly ConcurrentDictionary _dpirsonValues = new(); + private readonly ConcurrentDictionary _supportLevelsCalculationTimes = new(); private readonly ConcurrentDictionary _usedSupportLevels = new(); private readonly ConcurrentDictionary _usedSupportLevelsForClosing = new(); @@ -59,10 +57,6 @@ namespace KLHZ.Trader.Core.Exchange.Services _logger = logger; _dataBus = dataBus; _exchangeConfig = options.Value; - foreach (var f in _exchangeConfig.TradingInstrumentsFigis) - { - TradingModes[f] = TradingMode.None; - } } public Task StartAsync(CancellationToken cancellationToken) @@ -481,32 +475,27 @@ namespace KLHZ.Trader.Core.Exchange.Services { Asset? assetForClose = null; string? mess = null; + var profit = 0m; if (withProfitOnly) { - var profit = 0m; - - if (assetType == AssetType.Futures) + if (_tradeDataProvider.Orderbooks.TryGetValue(message.Figi, out var orderbook)) { - if (_tradeDataProvider.Orderbooks.TryGetValue(message.Figi, out var orderbook)) + if (asset.Count < 0 && orderbook.Asks.Length > 0) { - if (asset.Count < 0 && orderbook.Asks.Length > 0) - { - price = orderbook.Asks[0].Price; - } - else if (orderbook.Bids.Length > 0) - { - price = orderbook.Bids[0].Price; - } + price = orderbook.Asks[0].Price; + } + else if (orderbook.Bids.Length > 0) + { + price = orderbook.Bids[0].Price; } - - profit = TradingCalculator.CaclProfit(asset.BoughtPrice, price, - GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); } + + profit = TradingCalculator.CaclProfit(asset.BoughtPrice, price, + GetComission(assetType), GetLeverage(message.Figi, asset.Count < 0), asset.Count < 0); if (profit > 0) { profit = System.Math.Round(profit, 2); assetForClose = asset; - mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}, профит {profit}"; if (loggedDeclisions == 0) { loggedDeclisions++; @@ -516,13 +505,23 @@ namespace KLHZ.Trader.Core.Exchange.Services } else { - mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{price}"; assetForClose = asset; } - if (assetForClose != null && mess != null) + if (assetForClose != null) { - await _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi); + var settings = _exchangeConfig.InstrumentsSettings.FirstOrDefault(l => l.Figi == message.Figi); + var closingResult = await _portfolioWrapper.Accounts[asset.AccountId].ClosePosition(message.Figi); + if (closingResult.Success) + { + var profitText = profit == 0 ? string.Empty : ", профит {profit}"; + mess = $"Закрываю позицию {asset.Figi} ({(asset.Count > 0 ? "лонг" : "шорт")}) на счёте {_portfolioWrapper.Accounts[asset.AccountId].AccountName}. Количество {(long)asset.Count}, цена ~{closingResult.ExecutedPrice / (settings?.PointPriceRub ?? 1)}, комиссия {closingResult.Comission}" + profitText; + } + else + { + mess = $"Закрытие позиции прошло с ошибками."; + } + await _dataBus.Broadcast(new MessageForAdmin() { Text = mess }); } } @@ -537,12 +536,20 @@ namespace KLHZ.Trader.Core.Exchange.Services { if (TraderUtils.IsOperationAllowed(acc, message.Price, count, _exchangeConfig.AccountCashPartFutures, _exchangeConfig.AccountCashPart)) { - await acc.OpenPosition(message.Figi, positionType, stopLossShift, takeProfitShift, count); + var settings = _exchangeConfig.InstrumentsSettings.FirstOrDefault(l => l.Figi == message.Figi); + takeProfitShift = takeProfitShift * 2; + takeProfitShift = System.Math.Round(takeProfitShift); + takeProfitShift = takeProfitShift / 2; + + stopLossShift = stopLossShift * 2; + stopLossShift = System.Math.Round(stopLossShift); + stopLossShift = stopLossShift / 2; + var openingResult = await acc.OpenPosition(message.Figi, positionType, stopLossShift, takeProfitShift, count, settings?.PointPriceRub ?? 1); await _dataBus.Broadcast(new MessageForAdmin() { Text = $"Открываю позицию {message.Figi} ({(positionType == PositionType.Long ? "лонг" : "шорт")}) " + - $"на счёте {acc.AccountName}. Количество {(positionType == PositionType.Long ? "" : "-")}{count}, " + - $"цена ~{System.Math.Round(message.Price, 2)}. Стоп лосс: {(positionType == PositionType.Long ? "-" : "+")}{stopLossShift}. " + + $"на счёте {acc.AccountName}. Количество {(positionType == PositionType.Long ? "" : "-")}{openingResult.Count}, " + + $"цена ~{System.Math.Round(openingResult.ExecutedPrice / (settings?.PointPriceRub ?? 1), 2)}. Комиссия:{System.Math.Round(openingResult.Comission, 2)}. Стоп лосс: {(positionType == PositionType.Long ? "-" : "+")}{stopLossShift}. " + $"Тейк профит: {(positionType == PositionType.Long ? "+" : "-")}{takeProfitShift}" }); } diff --git a/KLHZ.Trader.Service/appsettings.json b/KLHZ.Trader.Service/appsettings.json index ea712f5..9edba1a 100644 --- a/KLHZ.Trader.Service/appsettings.json +++ b/KLHZ.Trader.Service/appsettings.json @@ -22,7 +22,7 @@ "Figi": "FUTIMOEXF000", "LongLeverage": 10.3, "ShortLeverage": 7.9, - "PriceToRubConvertationCoefficient" : 1 + "PointPriceRub": 10 } ] },