From 204bd87e6a3800a80d018bab3b466097f982f3d1 Mon Sep 17 00:00:00 2001 From: Oda <102867384+CryptoOda@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:52:42 +0200 Subject: [PATCH] Balance for bot (#20) * Add bot balance * Update amount to trade * fix initial trading balance * Update MM modal * fix backtest * stop bot if no more balance * Add constant for minimum trading * Add constant --- .../Controllers/BacktestController.cs | 11 - src/Managing.Api/Controllers/BotController.cs | 18 +- .../Controllers/TradingController.cs | 1 + .../Models/Requests/StartBotRequest.cs | 31 +- src/Managing.Application.Tests/BaseTests.cs | 4 +- src/Managing.Application.Tests/BotsTests.cs | 4 - .../PositionTests.cs | 3 +- .../RiskHelpersTests.cs | 1 - .../StatisticService.cs | 2 - .../Abstractions/IBotFactory.cs | 8 +- .../Abstractions/IBotService.cs | 9 +- .../Backtesting/Backtester.cs | 10 +- .../Bots/Base/BotFactory.cs | 28 +- src/Managing.Application/Bots/FlippingBot.cs | 2 + src/Managing.Application/Bots/ScalpingBot.cs | 2 + src/Managing.Application/Bots/TradingBot.cs | 88 ++++- .../ManageBot/BotService.cs | 32 +- .../ManageBot/Commands/StartBotCommand.cs | 10 +- .../ManageBot/StartBotCommandHandler.cs | 48 ++- .../MoneyManagementService.cs | 42 ++- .../Shared/SettingsService.cs | 1 - .../Trading/Commands/OpenPositionRequest.cs | 31 +- .../Trading/OpenPositionCommandHandler.cs | 33 +- .../Workflows/Flows/Trading/OpenPosition.cs | 5 +- src/Managing.Common/Constants.cs | 2 + .../MoneyManagements/MoneyManagement.cs | 3 - .../Shared/Helpers/RiskHelpers.cs | 12 - .../Shared/Helpers/TradingBox.cs | 113 +++--- .../MongoDb/MongoMappers.cs | 8 +- .../Discord/DiscordCommands.cs | 3 +- .../Discord/DiscordService.cs | 6 +- .../mollecules/Modal/BotNameModal.tsx | 29 +- .../organism/Backtest/backtestCards.tsx | 37 +- .../organism/Backtest/backtestModal.tsx | 67 ++-- .../organism/Backtest/backtestTable.tsx | 36 +- .../CustomMoneyManagement.tsx | 28 +- .../src/generated/ManagingApi.ts | 2 +- .../moneymanagement/moneyManagementModal.tsx | 352 ++++++++---------- .../moneymanagement/moneymanagementTable.tsx | 24 +- 39 files changed, 600 insertions(+), 546 deletions(-) diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index 056ac73..d92f66e 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -93,17 +93,6 @@ public class BacktestController : BaseController return Ok(_backtester.DeleteBacktestByUser(user, id)); } - /// - /// Deletes all backtests. - /// - /// An ActionResult indicating the outcome of the operation. - [HttpDelete] - [Route("deleteAll")] - public ActionResult DeleteBacktests() - { - return Ok(_backtester.DeleteBacktests()); - } - /// /// Runs a backtest with the specified parameters. /// diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index 98565ec..cb7a18b 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -3,6 +3,7 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Hubs; using Managing.Application.ManageBot.Commands; +using Managing.Common; using Managing.Domain.Trades; using MediatR; using Microsoft.AspNetCore.Authorization; @@ -123,9 +124,16 @@ public class BotController : BaseController return BadRequest("Money management not found"); } + // Validate initialTradingBalance + if (request.InitialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) + { + return BadRequest( + $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}"); + } + var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker, request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user, - request.IsForWatchOnly)); + request.IsForWatchOnly, request.InitialTradingBalance)); await NotifyBotSubscriberAsync(); return Ok(result); @@ -430,7 +438,7 @@ public class BotController : BaseController var activeBots = _botService.GetActiveBots(); var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot; - + if (bot == null) { return NotFound($"Bot {request.BotName} not found or is not a trading bot"); @@ -474,7 +482,7 @@ public class BotController : BaseController var activeBots = _botService.GetActiveBots(); var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot; - + if (bot == null) { return NotFound($"Bot {request.BotName} not found or is not a trading bot"); @@ -508,7 +516,7 @@ public class BotController : BaseController // Close the position at market price await bot.CloseTrade(signal, position, position.Open, lastCandle.Close, true); - + await NotifyBotSubscriberAsync(); return Ok(position); } @@ -529,7 +537,7 @@ public class ClosePositionRequest /// The name of the bot /// public string BotName { get; set; } - + /// /// The ID of the position to close /// diff --git a/src/Managing.Api/Controllers/TradingController.cs b/src/Managing.Api/Controllers/TradingController.cs index 617c7af..7d07806 100644 --- a/src/Managing.Api/Controllers/TradingController.cs +++ b/src/Managing.Api/Controllers/TradingController.cs @@ -150,6 +150,7 @@ public class TradingController : BaseController PositionInitiator.User, DateTime.UtcNow, user, + 100m, // Default trading balance for user-initiated trades isForPaperTrading: isForPaperTrading, price: openPrice); var result = await _openTradeCommandHandler.Handle(command); diff --git a/src/Managing.Api/Models/Requests/StartBotRequest.cs b/src/Managing.Api/Models/Requests/StartBotRequest.cs index a477733..e60de8a 100644 --- a/src/Managing.Api/Models/Requests/StartBotRequest.cs +++ b/src/Managing.Api/Models/Requests/StartBotRequest.cs @@ -5,21 +5,20 @@ namespace Managing.Api.Models.Requests { public class StartBotRequest { + [Required] public BotType BotType { get; set; } + [Required] public string BotName { get; set; } + [Required] public Ticker Ticker { get; set; } + [Required] public Timeframe Timeframe { get; set; } + [Required] public bool IsForWatchOnly { get; set; } + [Required] public string Scenario { get; set; } + [Required] public string AccountName { get; set; } + [Required] public string MoneyManagementName { get; set; } + + /// + /// Initial trading balance in USD for the bot + /// [Required] - public BotType BotType { get; set; } - [Required] - public string BotName { get; set; } - [Required] - public Ticker Ticker { get; set; } - [Required] - public Timeframe Timeframe { get; set; } - [Required] - public bool IsForWatchOnly { get; set; } - [Required] - public string Scenario { get; set; } - [Required] - public string AccountName { get; set; } - [Required] - public string MoneyManagementName { get; set; } + [Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")] + public decimal InitialTradingBalance { get; set; } } -} +} \ No newline at end of file diff --git a/src/Managing.Application.Tests/BaseTests.cs b/src/Managing.Application.Tests/BaseTests.cs index 59ad46a..802145c 100644 --- a/src/Managing.Application.Tests/BaseTests.cs +++ b/src/Managing.Application.Tests/BaseTests.cs @@ -2,6 +2,7 @@ using Managing.Application.Abstractions.Services; using Managing.Domain.Accounts; using Managing.Domain.MoneyManagements; +using Managing.Domain.Users; using Managing.Infrastructure.Tests; using Moq; using static Managing.Common.Enums; @@ -24,7 +25,6 @@ public class BaseTests _moneyManagementService = new Mock(); MoneyManagement = new MoneyManagement() { - BalanceAtRisk = 1m, // 30% Leverage = 2, // x2 Timeframe = Timeframe.FifteenMinutes, StopLoss = 0.008m, // 0.8% @@ -32,7 +32,7 @@ public class BaseTests Name = "Default MM" }; - _ = _moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny(), It.IsAny())) + _ = _moneyManagementService.Setup(m => m.GetMoneyMangement(It.IsAny(), It.IsAny())) .Returns(Task.FromResult(MoneyManagement)); _accountService.Setup(a => a.GetAccount(It.IsAny(), It.IsAny(), It.IsAny())) diff --git a/src/Managing.Application.Tests/BotsTests.cs b/src/Managing.Application.Tests/BotsTests.cs index 986ca32..f3d0ba1 100644 --- a/src/Managing.Application.Tests/BotsTests.cs +++ b/src/Managing.Application.Tests/BotsTests.cs @@ -9,7 +9,6 @@ using Managing.Core; using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios; -using MongoDB.Driver.Linq; using Moq; using Newtonsoft.Json; using Xunit; @@ -115,7 +114,6 @@ namespace Managing.Application.Tests var moneyManagement = new MoneyManagement() { - BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = 0.01m, @@ -179,7 +177,6 @@ namespace Managing.Application.Tests { var moneyManagement = new MoneyManagement() { - BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = s, @@ -293,7 +290,6 @@ namespace Managing.Application.Tests { var moneyManagement = new MoneyManagement() { - BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = s, diff --git a/src/Managing.Application.Tests/PositionTests.cs b/src/Managing.Application.Tests/PositionTests.cs index 7feabcb..dffb9cb 100644 --- a/src/Managing.Application.Tests/PositionTests.cs +++ b/src/Managing.Application.Tests/PositionTests.cs @@ -25,6 +25,7 @@ public class PositionTests : BaseTests PositionInitiator.User, DateTime.UtcNow, _account.User, + 100m, isForPaperTrading: false, signalIdentifier: new Guid().ToString()); var handler = new OpenPositionCommandHandler( @@ -40,7 +41,7 @@ public class PositionTests : BaseTests [Fact] public async Task Shoud_Close_Position() { - // var hexPositions = "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed00000000000000000000000070d95587d40a2caf56bd97485ab3eec10bee6336000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000fc11babdd3961cb7d1bacca000000000000000000000000000000000000000000000000000002049cca7c844e20000000000000000000000000000000000000000000000000000000000986f7e0000000000000000000000000000000000000001df508d0801ecf73c8058280500000000000000000000000000000000000000000000000df3ff1939e2b50cdd0000000000000000000000000000000000000000026bf951a8f07f8cc891bf920000000000000000000000000000000000000000000000001ac6ce5c364c6c570000000000000000000000000000000000000000000000000000000066db675e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d78200000000000000000000000000000000000000000000000000000b8a74a0cf2200000000000000000000000000000000000000000000000000000000000034ea00000000000000000000000000000000000000000000001481cd70725c3c1c03000000000000000000000000000000000000000004798d9b51ab57ddc2209ef50000000000000000000000000000000000000000000000002430a13e0606cefd000000000000000000000000000000000000000df008568002fcb46874db7fa100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000004ab88d39efb5958075000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c9f4c00dde985003ca14b5000000000000000000000000000000000000000000c9f4c00dde985003ca14b5000000000000000000000000000000000000000000001f04ef12cb04cf15800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ab88d39efb5958075000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000024d7830000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffbd4eecf9670c9bc2d3c2942e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a323f06526aa4eff43bd81573b1d00000000000000000000001031bcf5ca9bcbacd1e14f4423ec048d2860e6be400000000000000000000000149385b7c14fb69bfecc8920f26e8908c05b23600000000000000000000000001031bcf5ca9bcbacd19e9e311d531128eb34a9526e0000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed00000000000000000000000047c031236e19d024b42f8ae6780e44a573170703000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000052c3a7bfeaccb2af542a7eec1f000000000000000000000000000000000000000000000000000000000000184e100000000000000000000000000000000000000000000000000000000031fce8100000000000000000000000000000000000000026bdba31976b8f5eb6839942f000000000000000000000000000000000000000000000013051f6fe1b99e5f29000000000000000000000000000000000000000000000000002318fb454fb7ef000000000000000000000000000000000000000000000000a633867ed0a624d5000000000000000000000000000000000000000000000000000000006793580e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000008d16000000000000000000000000000000000000000000000013051f6fe1b99e5f290000000000000000000000000000000000000000000000000023aeb7ce5e2ae0000000000000000000000000000000000000000000000000aafa986b91e6895200000000000000000000000000000000000000007044e6adc56544d604423d6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ab88d39efb5958075000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c9f4c00dde985003ca14b5000000000000000000000000000000000000000000c9f4c00dde985003ca14b50000000000000000000000000000000000000000000014adf4b7320334b9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ab88d39efb595807500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064db77dc6667d396fff8db0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014285629eea2ec15d977e8b13dec5d0000000000000000000000000000001e9eda061bea538c139c4c474bf9c33e100000000000000000000000000000001e9eda061bea538c139c4c474bf9c33e100000000000000000000000000000001e9eda061c4f2f03f002b41ae2f9bc191a"; + // var hexPositions = "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed00000000000000000000000070d95587d40a2caf56bd97485ab3eec10bee6336000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000fc11babdd3961cb7d1bacca000000000000000000000000000000000000000000000000000002049cca7c844e20000000000000000000000000000000000000000000000000000000000986f7e0000000000000000000000000000000000000001df508d0801ecf73c8058280500000000000000000000000000000000000000000000000df3ff1939e2b50cdd0000000000000000000000000000000000000000026bf951a8f07f8cc891bf920000000000000000000000000000000000000000000000001ac6ce5c364c6c570000000000000000000000000000000000000000000000000000000066db675e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024d78200000000000000000000000000000000000000000000000000000b8a74a0cf2200000000000000000000000000000000000000000000000000000000000034ea00000000000000000000000000000000000000000000001481cd70725c3c1c03000000000000000000000000000000000000000004798d9b51ab57ddc2209ef50000000000000000000000000000000000000000000000002430a13e0606cefd000000000000000000000000000000000000000df008568002fcb46874db7fa100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000004ab88d39efb5958075000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c9f4c00dde985003ca14b5000000000000000000000000000000000000000000c9f4c00dde985003ca14b5000000000000000000000000000000000000000000001f04ef12cb04cf15800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ab88d39efb5958075000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000024d7830000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffbd4eecf9670c9bc2d3c2942e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a323f06526aa4eff43bd81573b1d00000000000000000000001031bcf5ca9bcbacd1e14f4423ec048d2860e6be400000000000000000000000149385b7c14fb69bfecc8920f26e8908c05b23600000000000000000000000001031bcf5ca9bcbacd19e9e311d531128eb34a9526e0000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed00000000000000000000000047c031236e19d024b42f8ae6780e44a573170703000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000052c3a7bfeaccb2af542a7eec1f000000000000000000000000000000000000000000000000000000000000184e100000000000000000000000000000000000000000000000000000000031fce8100000000000000000000000000000000000000026bdba31976b8f5eb6839942f000000000000000000000000000000000000000000000013051f6fe1b99e5f29000000000000000000000000000000000000000000000000002318fb454fb7ef000000000000000000000000000000000000000000000000a633867ed0a624d5000000000000000000000000000000000000000000000000000000006793580e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000425deab364e9121f7ca284129da854fd5cf22ed0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000008d16000000000000000000000000000000000000000000000013051f6fe1b99e5f290000000000000000000000000000000000000000000000000023aeb7ce5e2ae0000000000000000000000000000000000000000000000000aafa986b91e6895200000000000000000000000000000000000000007044e6adc56544d604423d6b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ab88d39efb595807500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c9f4c00dde985003ca14b5000000000000000000000000000000000000000000c9f4c00dde985003ca14b50000000000000000000000000000000000000000000014adf4b7320334b9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ab88d39efb59580750000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064db77dc6667d396fff8db0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014285629eea2ec15d977e8b13dec5d0000000000000000000000000000001e9eda061bea538c139c4c474bf9c33e100000000000000000000000000000001e9eda061bea538c139c4c474bf9c33e100000000000000000000000000000001e9eda061c4f2f03f002b41ae2f9bc191a"; // _ = new GetAccountPositioqwnInfoListOutputDTO().DecodeOutput(hexPositions).d // var openTrade = await _exchangeService.GetTrade(_account, "", Ticker.GMX); diff --git a/src/Managing.Application.Tests/RiskHelpersTests.cs b/src/Managing.Application.Tests/RiskHelpersTests.cs index 6bdf5e0..6ae4cbe 100644 --- a/src/Managing.Application.Tests/RiskHelpersTests.cs +++ b/src/Managing.Application.Tests/RiskHelpersTests.cs @@ -12,7 +12,6 @@ namespace Managing.Application.Tests { _moneyManagement = new MoneyManagement() { - BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = Timeframe.OneDay, StopLoss = 0.008m, diff --git a/src/Managing.Application.Workers/StatisticService.cs b/src/Managing.Application.Workers/StatisticService.cs index 6e8546c..9ed935f 100644 --- a/src/Managing.Application.Workers/StatisticService.cs +++ b/src/Managing.Application.Workers/StatisticService.cs @@ -2,7 +2,6 @@ using Managing.Application.Abstractions.Services; using Managing.Application.Workers.Abstractions; using Managing.Domain.Accounts; -using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios; using Managing.Domain.Shared.Helpers; @@ -243,7 +242,6 @@ public class StatisticService : IStatisticService { var moneyManagement = new MoneyManagement() { - BalanceAtRisk = 0.05m, Leverage = 1, Timeframe = timeframe, StopLoss = 0.008m, diff --git a/src/Managing.Application/Abstractions/IBotFactory.cs b/src/Managing.Application/Abstractions/IBotFactory.cs index d29fa0d..9c91569 100644 --- a/src/Managing.Application/Abstractions/IBotFactory.cs +++ b/src/Managing.Application/Abstractions/IBotFactory.cs @@ -7,10 +7,10 @@ namespace Managing.Application.Abstractions { public interface IBotFactory { - ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); - ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); - ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); - ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly); + ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance); + ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance); + ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance); + ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance); IBot CreateSimpleBot(string botName, Workflow workflow); } } diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index 741a169..4e26d00 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -1,5 +1,4 @@ using Managing.Common; -using Managing.Domain.Accounts; using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; using Managing.Domain.Workflows; @@ -18,16 +17,16 @@ public interface IBotService BotBackup GetBotBackup(string name); ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, - string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount); ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker, - string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount); ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, - string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount); ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker, - string scenario, Enums.Timeframe interval, bool isForWatchingOnly); + string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount); IBot CreateSimpleBot(string botName, Workflow workflow); Task StopBot(string requestName); diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs index e9f31de..9a52523 100644 --- a/src/Managing.Application/Backtesting/Backtester.cs +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -11,10 +11,10 @@ using Managing.Domain.Scenarios; using Managing.Domain.Shared.Helpers; using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; +using Managing.Domain.Users; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; -using Managing.Domain.Users; namespace Managing.Application.Backtesting { @@ -67,7 +67,7 @@ namespace Managing.Application.Backtesting List initialCandles = null) { var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, isForWatchingOnly); + timeframe, isForWatchingOnly, balance); scalpingBot.LoadScenario(scenario.Name); await scalpingBot.LoadAccount(); var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate); @@ -121,7 +121,7 @@ namespace Managing.Application.Backtesting List initialCandles = null) { var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, false); + timeframe, false, balance); flippingBot.LoadScenario(scenario.Name); await flippingBot.LoadAccount(); @@ -152,7 +152,7 @@ namespace Managing.Application.Backtesting { var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, false); + timeframe, false, balance); bot.LoadScenario(scenario.Name); await bot.LoadAccount(); @@ -173,7 +173,7 @@ namespace Managing.Application.Backtesting { var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, false); + timeframe, false, balance); bot.LoadScenario(scenario.Name); await bot.LoadAccount(); diff --git a/src/Managing.Application/Bots/Base/BotFactory.cs b/src/Managing.Application/Bots/Base/BotFactory.cs index ea49f14..2cc03c2 100644 --- a/src/Managing.Application/Bots/Base/BotFactory.cs +++ b/src/Managing.Application/Bots/Base/BotFactory.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Logging; -using static Managing.Common.Enums; -using Managing.Application.Abstractions; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; +using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; using Managing.Domain.Workflows; -using Managing.Domain.Bots; +using Microsoft.Extensions.Logging; +using static Managing.Common.Enums; namespace Managing.Application.Bots.Base { @@ -38,7 +38,7 @@ namespace Managing.Application.Bots.Base return new SimpleBot(botName, _tradingBotLogger, workflow, _botService); } - ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { return new ScalpingBot( accountName, @@ -53,10 +53,11 @@ namespace Managing.Application.Bots.Base _accountService, _messengerService, _botService, + initialTradingBalance, isForWatchingOnly: isForWatchingOnly); } - ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { return new ScalpingBot( accountName, @@ -71,11 +72,12 @@ namespace Managing.Application.Bots.Base _accountService, _messengerService, _botService, - true, - isForWatchingOnly); + initialTradingBalance, + isForBacktest: true, + isForWatchingOnly: isForWatchingOnly); } - public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { return new FlippingBot( accountName, @@ -90,10 +92,11 @@ namespace Managing.Application.Bots.Base _accountService, _messengerService, _botService, + initialTradingBalance, isForWatchingOnly: isForWatchingOnly); } - public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly) + public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { return new FlippingBot( accountName, @@ -108,8 +111,9 @@ namespace Managing.Application.Bots.Base _accountService, _messengerService, _botService, - true, - isForWatchingOnly); + initialTradingBalance, + isForBacktest: true, + isForWatchingOnly: isForWatchingOnly); } } } diff --git a/src/Managing.Application/Bots/FlippingBot.cs b/src/Managing.Application/Bots/FlippingBot.cs index 956a130..d740d96 100644 --- a/src/Managing.Application/Bots/FlippingBot.cs +++ b/src/Managing.Application/Bots/FlippingBot.cs @@ -20,6 +20,7 @@ namespace Managing.Application.Bots IAccountService accountService, IMessengerService messengerService, IBotService botService, + decimal initialTradingBalance, bool isForBacktest = false, bool isForWatchingOnly = false) : base(accountName, @@ -34,6 +35,7 @@ namespace Managing.Application.Bots accountService, messengerService, botService, + initialTradingBalance, isForBacktest, isForWatchingOnly, flipPosition: true) diff --git a/src/Managing.Application/Bots/ScalpingBot.cs b/src/Managing.Application/Bots/ScalpingBot.cs index 37e3d1b..a0611fd 100644 --- a/src/Managing.Application/Bots/ScalpingBot.cs +++ b/src/Managing.Application/Bots/ScalpingBot.cs @@ -20,6 +20,7 @@ namespace Managing.Application.Bots IAccountService accountService, IMessengerService messengerService, IBotService botService, + decimal initialTradingBalance, bool isForBacktest = false, bool isForWatchingOnly = false) : base(accountName, @@ -34,6 +35,7 @@ namespace Managing.Application.Bots accountService, messengerService, botService, + initialTradingBalance, isForBacktest, isForWatchingOnly) { diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 65c7da3..2ec66db 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -2,6 +2,7 @@ using Managing.Application.Abstractions.Services; using Managing.Application.Trading; using Managing.Application.Trading.Commands; +using Managing.Common; using Managing.Core.FixedSizedQueue; using Managing.Domain.Accounts; using Managing.Domain.Bots; @@ -50,6 +51,11 @@ public class TradingBot : Bot, ITradingBot public Dictionary StrategiesValues { get; set; } public DateTime StartupTime { get; set; } + /// + /// The dedicated trading balance for this bot in USD + /// + public decimal BotTradingBalance { get; set; } + public TradingBot( string accountName, MoneyManagement moneyManagement, @@ -63,6 +69,7 @@ public class TradingBot : Bot, ITradingBot IAccountService accountService, IMessengerService messengerService, IBotService botService, + decimal initialTradingBalance, bool isForBacktest = false, bool isForWatchingOnly = false, bool flipPosition = false) @@ -74,6 +81,13 @@ public class TradingBot : Bot, ITradingBot TradingService = tradingService; BotService = botService; + if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) + { + throw new ArgumentException( + $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}", + nameof(initialTradingBalance)); + } + IsForWatchingOnly = isForWatchingOnly; FlipPosition = flipPosition; AccountName = accountName; @@ -83,6 +97,7 @@ public class TradingBot : Bot, ITradingBot Timeframe = timeframe; IsForBacktest = isForBacktest; Logger = logger; + BotTradingBalance = initialTradingBalance; Strategies = new HashSet(); Signals = new HashSet(); @@ -172,6 +187,17 @@ public class TradingBot : Bot, ITradingBot { if (!IsForBacktest) { + // Check broker balance before running + var balance = await ExchangeService.GetBalance(Account, false); + if (balance < Constants.GMX.Config.MinimumPositionAmount) + { + await LogWarning( + $"Balance on broker is below {Constants.GMX.Config.MinimumPositionAmount} USD (actual: {balance}). Stopping bot and saving backup."); + SaveBackup(); + Stop(); + return; + } + Logger.LogInformation($"____________________{Name}____________________"); Logger.LogInformation( $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}"); @@ -315,7 +341,8 @@ public class TradingBot : Bot, ITradingBot if (WalletBalances.Count == 0) { - WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest); + // WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest); + WalletBalances[date] = BotTradingBalance; return; } @@ -555,10 +582,9 @@ public class TradingBot : Bot, ITradingBot PositionInitiator.Bot, signal.Date, User, + BotTradingBalance, IsForBacktest, lastPrice, - balance: WalletBalances.LastOrDefault().Value, - fee: Fee, signalIdentifier: signal.Identifier); var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService) @@ -591,7 +617,6 @@ public class TradingBot : Bot, ITradingBot // Keep signal open for debug purpose //SetSignalStatus(signal.Identifier, SignalStatus.Expired); SetSignalStatus(signal.Identifier, SignalStatus.Expired); - await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}"); } } @@ -599,8 +624,9 @@ public class TradingBot : Bot, ITradingBot private bool CanOpenPosition(Signal signal) { - if (ExecutionCount < 1) + if (!IsForBacktest && ExecutionCount < 1) return false; + if (Positions.Count == 0) return true; @@ -639,7 +665,6 @@ public class TradingBot : Bot, ITradingBot } else { - position.Initiator = PositionInitiator.Bot; var command = new ClosePositionCommand(position, lastPrice); try { @@ -681,6 +706,18 @@ public class TradingBot : Bot, ITradingBot await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished); Logger.LogInformation( $"Position {position.SignalIdentifier} type correctly close. Pnl on position : {position.ProfitAndLoss?.Realized}"); + + // Update the bot's trading balance after position is closed + if (position.ProfitAndLoss != null) + { + // Add PnL (could be positive or negative) + BotTradingBalance += position.ProfitAndLoss.Realized; + + // Subtract fees + BotTradingBalance -= GetPositionFees(position); + + Logger.LogInformation($"Updated bot trading balance to: {BotTradingBalance}"); + } } else { @@ -735,8 +772,12 @@ public class TradingBot : Bot, ITradingBot private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus) { - await LogInformation($"Position {signalIdentifier} is now {positionStatus}"); - Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus; + if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus)) + { + await LogInformation($"Position {signalIdentifier} is now {positionStatus}"); + Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus; + } + SetSignalStatus(signalIdentifier, positionStatus == PositionStatus.Filled ? SignalStatus.PositionOpen : SignalStatus.Expired); } @@ -799,6 +840,29 @@ public class TradingBot : Bot, ITradingBot return fees; } + /// + /// Calculates the total fees for a specific position + /// + /// The position to calculate fees for + /// The total fees for the position + private decimal GetPositionFees(Position position) + { + decimal fees = 0; + + fees += position.Open.Fee; + fees += position.StopLoss.Status == TradeStatus.Filled ? position.StopLoss.Fee : 0; + fees += position.TakeProfit1.Status == TradeStatus.Filled ? position.TakeProfit1.Fee : 0; + + if (position.IsFinished() && + position.StopLoss.Status != TradeStatus.Filled && position.TakeProfit1.Status != TradeStatus.Filled) + fees += position.Open.Fee; + + if (position.TakeProfit2 != null) + fees += position.TakeProfit2.Status == TradeStatus.Filled ? position.TakeProfit2.Fee : 0; + + return fees; + } + public async Task ToggleIsForWatchOnly() { IsForWatchingOnly = (!IsForWatchingOnly); @@ -813,6 +877,7 @@ public class TradingBot : Bot, ITradingBot private async Task LogWarning(string message) { + message = $"[{Name}][{Identifier}] {message}"; Logger.LogWarning(message); SentrySdk.CaptureException(new Exception(message)); await SendTradeMessage(message, true); @@ -841,6 +906,7 @@ public class TradingBot : Bot, ITradingBot IsForWatchingOnly = IsForWatchingOnly, WalletBalances = WalletBalances, MoneyManagement = MoneyManagement, + BotTradingBalance = BotTradingBalance, StartupTime = StartupTime, }; BotService.SaveOrUpdateBotBackup(Name, BotType, JsonConvert.SerializeObject(data)); @@ -858,6 +924,7 @@ public class TradingBot : Bot, ITradingBot ScenarioName = data.ScenarioName; AccountName = data.AccountName; IsForWatchingOnly = data.IsForWatchingOnly; + BotTradingBalance = data.BotTradingBalance; // Restore the startup time if it was previously saved if (data.StartupTime != DateTime.MinValue) @@ -902,10 +969,6 @@ public class TradingBot : Bot, ITradingBot throw new Exception("Failed to open position"); } - // Removed manual setting of SL/TP, as MoneyManagement should handle it - // position.StopLoss.Price = stopLossPrice; - // position.TakeProfit1.Price = takeProfitPrice; - Logger.LogInformation($"Manually opened position {position.Identifier} for signal {signal.Identifier}"); return position; } @@ -925,4 +988,5 @@ public class TradingBotBackup public Dictionary WalletBalances { get; set; } public MoneyManagement MoneyManagement { get; set; } public DateTime StartupTime { get; set; } + public decimal BotTradingBalance { get; set; } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 906c17e..7c801d9 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -55,7 +55,7 @@ namespace Managing.Application.ManageBot if (backup != null) { backup.Data = data; - _botRepository.UpdateBackupBot(backup); + _botRepository.UpdateBackupBot(backup); } else { @@ -136,7 +136,8 @@ namespace Managing.Application.ManageBot scalpingBotData.Ticker, scalpingBotData.ScenarioName, scalpingBotData.Timeframe, - scalpingBotData.IsForWatchingOnly); + scalpingBotData.IsForWatchingOnly, + scalpingBotData.BotTradingBalance); botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot)); break; case Enums.BotType.FlippingBot: @@ -150,7 +151,8 @@ namespace Managing.Application.ManageBot flippingBotData.Ticker, flippingBotData.ScenarioName, flippingBotData.Timeframe, - flippingBotData.IsForWatchingOnly); + flippingBotData.IsForWatchingOnly, + flippingBotData.BotTradingBalance); botTask = Task.Run(InitBot((ITradingBot)bot, backupBot)); break; } @@ -243,7 +245,8 @@ namespace Managing.Application.ManageBot } public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + decimal initialTradingBalance) { return new ScalpingBot( accountName, @@ -258,11 +261,13 @@ namespace Managing.Application.ManageBot _accountService, _messengerService, this, + initialTradingBalance, isForWatchingOnly: isForWatchingOnly); } public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + decimal initialTradingBalance) { return new ScalpingBot( accountName, @@ -277,12 +282,14 @@ namespace Managing.Application.ManageBot _accountService, _messengerService, this, - true, - isForWatchingOnly); + initialTradingBalance, + isForBacktest: true, + isForWatchingOnly: isForWatchingOnly); } public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + decimal initialTradingBalance) { return new FlippingBot( accountName, @@ -297,11 +304,13 @@ namespace Managing.Application.ManageBot _accountService, _messengerService, this, + initialTradingBalance, isForWatchingOnly: isForWatchingOnly); } public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly) + Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + decimal initialTradingBalance) { return new FlippingBot( accountName, @@ -316,8 +325,9 @@ namespace Managing.Application.ManageBot _accountService, _messengerService, this, - true, - isForWatchingOnly); + initialTradingBalance, + isForBacktest: true, + isForWatchingOnly: isForWatchingOnly); } } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs b/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs index 664fad5..a536997 100644 --- a/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs +++ b/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs @@ -1,5 +1,5 @@ -using MediatR; -using Managing.Domain.Users; +using Managing.Domain.Users; +using MediatR; using static Managing.Common.Enums; namespace Managing.Application.ManageBot.Commands @@ -15,6 +15,7 @@ namespace Managing.Application.ManageBot.Commands public string AccountName { get; internal set; } public string MoneyManagementName { get; internal set; } public User User { get; internal set; } + public decimal InitialTradingBalance { get; internal set; } public StartBotCommand(BotType botType, string name, @@ -24,7 +25,8 @@ namespace Managing.Application.ManageBot.Commands string accountName, string moneyManagementName, User user, - bool isForWatchingOnly = false) + bool isForWatchingOnly = false, + decimal initialTradingBalance = 0) { BotType = botType; Name = name; @@ -35,6 +37,8 @@ namespace Managing.Application.ManageBot.Commands AccountName = accountName; MoneyManagementName = moneyManagementName; User = user; + InitialTradingBalance = initialTradingBalance > 0 ? initialTradingBalance : + throw new ArgumentException("Initial trading balance must be greater than zero", nameof(initialTradingBalance)); } } } diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs index 81519ce..3daa702 100644 --- a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -1,7 +1,8 @@ -using MediatR; -using static Managing.Common.Enums; -using Managing.Application.Abstractions; +using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; using Managing.Application.ManageBot.Commands; +using MediatR; +using static Managing.Common.Enums; namespace Managing.Application.ManageBot { @@ -10,38 +11,61 @@ namespace Managing.Application.ManageBot private readonly IBotFactory _botFactory; private readonly IBotService _botService; private readonly IMoneyManagementService _moneyManagementService; + private readonly IExchangeService _exchangeService; + private readonly IAccountService _accountService; public StartBotCommandHandler(IBotFactory botFactory, IBotService botService, - IMoneyManagementService moneyManagementService) + IMoneyManagementService moneyManagementService, IExchangeService exchangeService, + IAccountService accountService) { _botFactory = botFactory; _botService = botService; _moneyManagementService = moneyManagementService; + _exchangeService = exchangeService; + _accountService = accountService; } - public Task Handle(StartBotCommand request, CancellationToken cancellationToken) + public async Task Handle(StartBotCommand request, CancellationToken cancellationToken) { BotStatus botStatus = BotStatus.Down; - var moneyManagement = _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName).Result; + + var account = await _accountService.GetAccount(request.AccountName, true, true); + + if (account == null) + { + throw new Exception($"Account {request.AccountName} not found"); + } + + var usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString()); + + if (usdcBalance == null || usdcBalance.Value < request.InitialTradingBalance) + { + throw new Exception($"Account {request.AccountName} has no USDC balance or not enough balance"); + } + + var moneyManagement = + await _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName); switch (request.BotType) { case BotType.SimpleBot: var bot = _botFactory.CreateSimpleBot(request.Name, null); _botService.AddSimpleBotToCache(bot); - return Task.FromResult(bot.GetStatus()); + return bot.GetStatus(); case BotType.ScalpingBot: var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, - request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly); + request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly, + request.InitialTradingBalance); _botService.AddTradingBotToCache(sBot); - return Task.FromResult(sBot.GetStatus()); + return sBot.GetStatus(); case BotType.FlippingBot: var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, - request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly); + request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly, + request.InitialTradingBalance); _botService.AddTradingBotToCache(fBot); - return Task.FromResult(fBot.GetStatus()); + return fBot.GetStatus(); } - return Task.FromResult(botStatus.ToString()); + return botStatus.ToString(); } } } \ No newline at end of file diff --git a/src/Managing.Application/MoneyManagements/MoneyManagementService.cs b/src/Managing.Application/MoneyManagements/MoneyManagementService.cs index ba9521c..4a10942 100644 --- a/src/Managing.Application/MoneyManagements/MoneyManagementService.cs +++ b/src/Managing.Application/MoneyManagements/MoneyManagementService.cs @@ -1,8 +1,8 @@ -using Managing.Domain.MoneyManagements; -using Managing.Application.Abstractions; -using Microsoft.Extensions.Logging; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; +using Managing.Domain.MoneyManagements; using Managing.Domain.Users; +using Microsoft.Extensions.Logging; namespace Managing.Application.MoneyManagements; @@ -23,12 +23,12 @@ public class MoneyManagementService : IMoneyManagementService { // Try to get user-specific strategy first var moneyManagement = await _settingsRepository.GetMoneyManagementByUser(user, request.Name); - + // Fall back to regular lookup if user-specific endpoint is not implemented if (moneyManagement == null) { moneyManagement = await _settingsRepository.GetMoneyManagement(request.Name); - + // If found by name but doesn't belong to this user, treat as new if (moneyManagement != null && moneyManagement.User?.Name != user.Name) { @@ -47,16 +47,16 @@ public class MoneyManagementService : IMoneyManagementService // Additional check to ensure user's ownership if (moneyManagement.User?.Name != user.Name) { - throw new UnauthorizedAccessException("You do not have permission to update this money management strategy."); + throw new UnauthorizedAccessException( + "You do not have permission to update this money management strategy."); } - + moneyManagement.StopLoss = request.StopLoss; moneyManagement.TakeProfit = request.TakeProfit; - moneyManagement.BalanceAtRisk = request.BalanceAtRisk; moneyManagement.Leverage = request.Leverage; moneyManagement.Timeframe = request.Timeframe; moneyManagement.User = user; - + _settingsRepository.UpdateMoneyManagement(moneyManagement); return moneyManagement; } @@ -69,7 +69,7 @@ public class MoneyManagementService : IMoneyManagementService public IEnumerable GetMoneyMangements(User user) { - try + try { // Try to use user-specific repository method first return _settingsRepository.GetMoneyManagementsByUser(user); @@ -85,7 +85,7 @@ public class MoneyManagementService : IMoneyManagementService public async Task GetMoneyMangement(User user, string name) { MoneyManagement moneyManagement; - + try { // Try to use user-specific repository method first @@ -95,14 +95,14 @@ public class MoneyManagementService : IMoneyManagementService { // Fall back to regular lookup if user-specific endpoint is not implemented moneyManagement = await _settingsRepository.GetMoneyManagement(name); - + // Filter by user if (moneyManagement != null && moneyManagement.User?.Name != user.Name) { moneyManagement = null; } } - + return moneyManagement; } @@ -119,15 +119,16 @@ public class MoneyManagementService : IMoneyManagementService { // Fall back to verifying user ownership before deletion var moneyManagement = _settingsRepository.GetMoneyManagement(name).Result; - + if (moneyManagement != null && moneyManagement.User?.Name != user.Name) { - throw new UnauthorizedAccessException("You do not have permission to delete this money management strategy."); + throw new UnauthorizedAccessException( + "You do not have permission to delete this money management strategy."); } - + _settingsRepository.DeleteMoneyManagement(name); } - + return true; } catch (Exception ex) @@ -150,9 +151,10 @@ public class MoneyManagementService : IMoneyManagementService { // This fallback is not ideal as it would delete all money managements regardless of user // In a real implementation, we would need a filtered repository method - _logger.LogWarning("DeleteMoneyManagementsByUser not implemented, cannot delete user-specific money managements"); + _logger.LogWarning( + "DeleteMoneyManagementsByUser not implemented, cannot delete user-specific money managements"); } - + return true; } catch (Exception ex) @@ -161,4 +163,4 @@ public class MoneyManagementService : IMoneyManagementService return false; } } -} +} \ No newline at end of file diff --git a/src/Managing.Application/Shared/SettingsService.cs b/src/Managing.Application/Shared/SettingsService.cs index 8ff68bb..d6a5ca0 100644 --- a/src/Managing.Application/Shared/SettingsService.cs +++ b/src/Managing.Application/Shared/SettingsService.cs @@ -200,7 +200,6 @@ public class SettingsService : ISettingsService { Name = "Personal-Hourly", Timeframe = Timeframe.OneHour, - BalanceAtRisk = 25, // 25% StopLoss = 2, // 2% TakeProfit = 4, // 4% Leverage = 1, diff --git a/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs b/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs index e0d10b2..f5e0ea1 100644 --- a/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs +++ b/src/Managing.Application/Trading/Commands/OpenPositionRequest.cs @@ -16,25 +16,28 @@ namespace Managing.Application.Trading.Commands PositionInitiator initiator, DateTime date, User user, + decimal amountToTrade, bool isForPaperTrading = false, decimal? price = null, - decimal? balance = 1000, - decimal? fee = null, - bool? ignoreSLTP = false, string signalIdentifier = null) { AccountName = accountName; MoneyManagement = moneyManagement; Direction = direction; Ticker = ticker; + Initiator = initiator; + Date = date; + User = user; + + if (amountToTrade <= 10) + { + throw new ArgumentException("Bot trading balance must be greater than zero", nameof(amountToTrade)); + } + + AmountToTrade = amountToTrade; + IsForPaperTrading = isForPaperTrading; Price = price; - Date = date; - Balance = balance; - Initiator = initiator; - Fee = fee; - IgnoreSLTP = ignoreSLTP; - User = user; SignalIdentifier = signalIdentifier; } @@ -45,11 +48,9 @@ namespace Managing.Application.Trading.Commands public Ticker Ticker { get; } public bool IsForPaperTrading { get; } public decimal? Price { get; } - public decimal? Fee { get; } - public bool? IgnoreSLTP { get; } - public decimal? Balance { get; } - public DateTime Date { get; set; } - public PositionInitiator Initiator { get; internal set; } - public User User { get; internal set; } + public decimal AmountToTrade { get; } + public DateTime Date { get; } + public PositionInitiator Initiator { get; } + public User User { get; } } } \ No newline at end of file diff --git a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs index 996331e..08f92d7 100644 --- a/src/Managing.Application/Trading/OpenPositionCommandHandler.cs +++ b/src/Managing.Application/Trading/OpenPositionCommandHandler.cs @@ -1,6 +1,7 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Trading.Commands; +using Managing.Common; using Managing.Domain.Shared.Helpers; using Managing.Domain.Trades; using static Managing.Common.Enums; @@ -36,22 +37,20 @@ namespace Managing.Application.Trading position.SignalIdentifier = request.SignalIdentifier; } - var balance = request.IsForPaperTrading - ? request.Balance.GetValueOrDefault() - : exchangeService.GetBalance(account, request.IsForPaperTrading).Result; - var balanceAtRisk = RiskHelpers.GetBalanceAtRisk(balance, request.MoneyManagement); + // Always use BotTradingBalance directly as the balance to risk + decimal balanceToRisk = request.AmountToTrade; - if (balanceAtRisk < 7) + // Minimum check + if (balanceToRisk < Constants.GMX.Config.MinimumPositionAmount) { - throw new Exception($"Try to risk {balanceAtRisk} $ but inferior to minimum to trade"); + throw new Exception( + $"Bot trading balance of {balanceToRisk} USD is less than the minimum {Constants.GMX.Config.MinimumPositionAmount} USD required to trade"); } var price = request.IsForPaperTrading && request.Price.HasValue ? request.Price.Value : exchangeService.GetPrice(account, request.Ticker, DateTime.Now); - var quantity = balanceAtRisk / price; - // var expectedStatus = GetExpectedStatus(request); - // position.Open = TradingPolicies.OpenPosition(expectedStatus).Execute(async () => { }); + var quantity = balanceToRisk / price; var openPrice = request.IsForPaperTrading || request.Price.HasValue ? request.Price.Value @@ -71,20 +70,19 @@ namespace Managing.Application.Trading TradeType.Limit, isForPaperTrading: request.IsForPaperTrading, currentDate: request.Date, - stopLossPrice: stopLossPrice, // Pass determined SL price - takeProfitPrice: takeProfitPrice); // Pass determined TP price + stopLossPrice: stopLossPrice, + takeProfitPrice: takeProfitPrice); - //trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange); position.Open = trade; var closeDirection = request.Direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long; - // Stop loss - Use the determined price + // Stop loss position.StopLoss = exchangeService.BuildEmptyTrade( request.Ticker, - stopLossPrice, // Use determined SL price + stopLossPrice, position.Open.Quantity, closeDirection, request.MoneyManagement.Leverage, @@ -92,13 +90,10 @@ namespace Managing.Application.Trading request.Date, TradeStatus.Requested); - // position.StopLoss.Fee = TradingHelpers.GetFeeAmount(fee, - // position.StopLoss.Price * position.StopLoss.Quantity, account.Exchange); - - // Take profit - Use the determined price + // Take profit position.TakeProfit1 = exchangeService.BuildEmptyTrade( request.Ticker, - takeProfitPrice, // Use determined TP price + takeProfitPrice, quantity, closeDirection, request.MoneyManagement.Leverage, diff --git a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs index a1d2944..aba5355 100644 --- a/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs +++ b/src/Managing.Application/Workflows/Flows/Trading/OpenPosition.cs @@ -147,10 +147,9 @@ public class OpenPosition : FlowBase PositionInitiator.Bot, signal.Date, account.User, + 100m, // Default bot trading balance OpenPositionParameters.IsForBacktest, - lastPrice, - balance: WalletBalances.LastOrDefault().Value, - fee: Fee); + lastPrice); var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService) .Handle(command); diff --git a/src/Managing.Common/Constants.cs b/src/Managing.Common/Constants.cs index d268758..ffce839 100644 --- a/src/Managing.Common/Constants.cs +++ b/src/Managing.Common/Constants.cs @@ -84,6 +84,8 @@ { public const int USD = 30; } + + public const decimal MinimumPositionAmount = 10m; } public class TokenAddress diff --git a/src/Managing.Domain/MoneyManagements/MoneyManagement.cs b/src/Managing.Domain/MoneyManagements/MoneyManagement.cs index 6321941..469e53f 100644 --- a/src/Managing.Domain/MoneyManagements/MoneyManagement.cs +++ b/src/Managing.Domain/MoneyManagements/MoneyManagement.cs @@ -11,8 +11,6 @@ namespace Managing.Domain.MoneyManagements [Required] public Timeframe Timeframe { get; set; } [Required] - public decimal BalanceAtRisk { get; set; } - [Required] public decimal StopLoss { get; set; } [Required] public decimal TakeProfit { get; set; } @@ -25,7 +23,6 @@ namespace Managing.Domain.MoneyManagements { StopLoss /= 100; TakeProfit /= 100; - BalanceAtRisk /= 100; } } } diff --git a/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs b/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs index 53d839e..95e4423 100644 --- a/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs +++ b/src/Managing.Domain/Shared/Helpers/RiskHelpers.cs @@ -12,18 +12,6 @@ namespace Managing.Domain.Shared.Helpers price += price * moneyManagement.StopLoss; } - public static decimal GetBalanceAtRisk(decimal balance, MoneyManagement moneyManagement) - { - decimal amountToRisk = balance * moneyManagement.BalanceAtRisk; - - if (amountToRisk <= 1) - { - throw new Exception("Cannot open trade, not enough balance"); - } - - return amountToRisk; - } - public static decimal GetTakeProfitPrice(TradeDirection direction, decimal price, MoneyManagement moneyManagement, int count = 1) { decimal percentage = moneyManagement.TakeProfit * count; diff --git a/src/Managing.Domain/Shared/Helpers/TradingBox.cs b/src/Managing.Domain/Shared/Helpers/TradingBox.cs index 4eab908..6301314 100644 --- a/src/Managing.Domain/Shared/Helpers/TradingBox.cs +++ b/src/Managing.Domain/Shared/Helpers/TradingBox.cs @@ -134,7 +134,6 @@ public static class TradingBox moneyManagement.StopLoss = stoplossPercentage.Average(); moneyManagement.TakeProfit = takeProfitsPercentage.Average(); - moneyManagement.BalanceAtRisk = originMoneyManagement.BalanceAtRisk * 100; moneyManagement.Timeframe = originMoneyManagement.Timeframe; moneyManagement.Leverage = originMoneyManagement.Leverage; moneyManagement.Name = "Optimized"; @@ -196,32 +195,32 @@ public static class TradingBox public static decimal GetTotalVolumeTraded(List positions) { decimal totalVolume = 0; - + foreach (var position in positions) { // Add entry volume totalVolume += position.Open.Quantity * position.Open.Price; - + // Add exit volumes from stop loss or take profits if they were executed if (position.StopLoss.Status == TradeStatus.Filled) { totalVolume += position.StopLoss.Quantity * position.StopLoss.Price; } - + if (position.TakeProfit1.Status == TradeStatus.Filled) { totalVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price; } - + if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled) { totalVolume += position.TakeProfit2.Quantity * position.TakeProfit2.Price; } } - + return totalVolume; } - + /// /// Calculates the volume traded in the last 24 hours /// @@ -231,38 +230,38 @@ public static class TradingBox { decimal last24hVolume = 0; DateTime cutoff = DateTime.UtcNow.AddHours(-24); - + foreach (var position in positions) { // Check if any part of this position was traded in the last 24 hours - + // Add entry volume if it was within the last 24 hours if (position.Open.Date >= cutoff) { last24hVolume += position.Open.Quantity * position.Open.Price; } - + // Add exit volumes if they were executed within the last 24 hours if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) { last24hVolume += position.StopLoss.Quantity * position.StopLoss.Price; } - + if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date >= cutoff) { last24hVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price; } - - if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && + + if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date >= cutoff) { last24hVolume += position.TakeProfit2.Quantity * position.TakeProfit2.Price; } } - + return last24hVolume; } - + /// /// Gets the win/loss counts from positions /// @@ -272,7 +271,7 @@ public static class TradingBox { int wins = 0; int losses = 0; - + foreach (var position in positions) { // Only count finished positions @@ -288,10 +287,10 @@ public static class TradingBox } } } - + return (wins, losses); } - + /// /// Calculates the ROI for the last 24 hours /// @@ -302,25 +301,26 @@ public static class TradingBox decimal profitLast24h = 0; decimal investmentLast24h = 0; DateTime cutoff = DateTime.UtcNow.AddHours(-24); - + foreach (var position in positions) { // Only count positions that were opened or closed within the last 24 hours - if (position.IsFinished() && - (position.Open.Date >= cutoff || - (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) || + if (position.IsFinished() && + (position.Open.Date >= cutoff || + (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date >= cutoff) || (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date >= cutoff) || - (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date >= cutoff))) + (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && + position.TakeProfit2.Date >= cutoff))) { profitLast24h += position.ProfitAndLoss != null ? position.ProfitAndLoss.Realized : 0; investmentLast24h += position.Open.Quantity * position.Open.Price; } } - + // Avoid division by zero if (investmentLast24h == 0) return 0; - + return (profitLast24h / investmentLast24h) * 100; } @@ -339,10 +339,10 @@ public static class TradingBox .Where(p => p.IsFinished() && p.ProfitAndLoss != null) .Sum(p => p.ProfitAndLoss.Realized); } - + // Convert time filter to a DateTime DateTime cutoffDate = DateTime.UtcNow; - + switch (timeFilter) { case "24H": @@ -361,17 +361,18 @@ public static class TradingBox cutoffDate = DateTime.UtcNow.AddYears(-1); break; } - + // Include positions that were closed within the time range return positions .Where(p => p.IsFinished() && p.ProfitAndLoss != null && - (p.Date >= cutoffDate || - (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) || - (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) || - (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate))) + (p.Date >= cutoffDate || + (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) || + (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) || + (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && + p.TakeProfit2.Date >= cutoffDate))) .Sum(p => p.ProfitAndLoss.Realized); } - + /// /// Calculates ROI for positions within a specific time range /// @@ -385,10 +386,10 @@ public static class TradingBox { return 0; } - + // Convert time filter to a DateTime DateTime cutoffDate = DateTime.UtcNow; - + if (timeFilter != "Total") { switch (timeFilter) @@ -410,29 +411,30 @@ public static class TradingBox break; } } - + // Filter positions in the time range - var filteredPositions = timeFilter == "Total" + var filteredPositions = timeFilter == "Total" ? positions.Where(p => p.IsFinished() && p.ProfitAndLoss != null) : positions.Where(p => p.IsFinished() && p.ProfitAndLoss != null && - (p.Date >= cutoffDate || - (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) || - (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) || - (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate))); - + (p.Date >= cutoffDate || + (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) || + (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) || + (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && + p.TakeProfit2.Date >= cutoffDate))); + // Calculate investment and profit decimal totalInvestment = filteredPositions.Sum(p => p.Open.Quantity * p.Open.Price); decimal totalProfit = filteredPositions.Sum(p => p.ProfitAndLoss.Realized); - + // Calculate ROI if (totalInvestment == 0) { return 0; } - + return (totalProfit / totalInvestment) * 100; } - + /// /// Gets the win/loss counts from positions in a specific time range /// @@ -443,7 +445,7 @@ public static class TradingBox { // Convert time filter to a DateTime DateTime cutoffDate = DateTime.UtcNow; - + if (timeFilter != "Total") { switch (timeFilter) @@ -465,19 +467,20 @@ public static class TradingBox break; } } - + // Filter positions in the time range - var filteredPositions = timeFilter == "Total" + var filteredPositions = timeFilter == "Total" ? positions.Where(p => p.IsFinished()) : positions.Where(p => p.IsFinished() && - (p.Date >= cutoffDate || - (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) || - (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) || - (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && p.TakeProfit2.Date >= cutoffDate))); - + (p.Date >= cutoffDate || + (p.StopLoss.Status == TradeStatus.Filled && p.StopLoss.Date >= cutoffDate) || + (p.TakeProfit1.Status == TradeStatus.Filled && p.TakeProfit1.Date >= cutoffDate) || + (p.TakeProfit2 != null && p.TakeProfit2.Status == TradeStatus.Filled && + p.TakeProfit2.Date >= cutoffDate))); + int wins = 0; int losses = 0; - + foreach (var position in filteredPositions) { if (position.ProfitAndLoss != null && position.ProfitAndLoss.Realized > 0) @@ -489,7 +492,7 @@ public static class TradingBox losses++; } } - + return (wins, losses); } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs index e2c8d11..1083227 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -1,5 +1,4 @@ -using Managing.Core; -using Managing.Domain.Accounts; +using Managing.Domain.Accounts; using Managing.Domain.Backtests; using Managing.Domain.Bots; using Managing.Domain.Candles; @@ -12,9 +11,8 @@ using Managing.Domain.Users; using Managing.Domain.Workers; using Managing.Domain.Workflows.Synthetics; using Managing.Infrastructure.Databases.MongoDb.Collections; -using static Managing.Common.Enums; using MongoDB.Bson; -using Managing.Domain.Shared.Helpers; +using static Managing.Common.Enums; namespace Managing.Infrastructure.Databases.MongoDb; @@ -486,7 +484,6 @@ public static class MongoMappers return new MoneyManagementDto { Timeframe = request.Timeframe, - BalanceAtRisk = request.BalanceAtRisk, StopLoss = request.StopLoss, TakeProfit = request.TakeProfit, Leverage = request.Leverage, @@ -503,7 +500,6 @@ public static class MongoMappers return new MoneyManagement { Timeframe = request.Timeframe, - BalanceAtRisk = request.BalanceAtRisk, StopLoss = request.StopLoss, TakeProfit = request.TakeProfit, Leverage = request.Leverage, diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs index 2a4352b..f7130e7 100644 --- a/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordCommands.cs @@ -69,12 +69,11 @@ namespace Managing.Infrastructure.Messengers.Discord var moneymanagement = new MoneyManagement { - BalanceAtRisk = 5, StopLoss = stopLoss.GetValueOrDefault(), TakeProfit = takeProfit.GetValueOrDefault(), }; var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker, - PositionInitiator.User, DateTime.UtcNow, new User()); + PositionInitiator.User, DateTime.UtcNow, new User(), 100m); var result = await _openTradeCommandHandler.Handle(tradeCommand); var builder = new ComponentBuilder().WithButton("Close Position", $"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}"); diff --git a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs index 90205c3..d2ffd16 100644 --- a/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs +++ b/src/Managing.Infrastructure.Messengers/Discord/DiscordService.cs @@ -12,6 +12,7 @@ using Managing.Core; using Managing.Domain.MoneyManagements; using Managing.Domain.Statistics; using Managing.Domain.Trades; +using Managing.Domain.Users; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -264,7 +265,7 @@ namespace Managing.Infrastructure.Messengers.Discord var tradingService = (ITradingService)_services.GetService(typeof(ITradingService)); // Create default user for Discord bot operations - var defaultUser = new Domain.Users.User { Name = "DiscordBot" }; + var defaultUser = new User { Name = "DiscordBot" }; var tradeCommand = new OpenPositionRequest( accountName, @@ -274,7 +275,7 @@ namespace Managing.Infrastructure.Messengers.Discord initiator, DateTime.UtcNow, defaultUser, - ignoreSLTP: ignoreSLTP); + 100m); var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService) .Handle(tradeCommand); @@ -416,7 +417,6 @@ namespace Managing.Infrastructure.Messengers.Discord new MoneyManagement { Name = "MediumRisk", - BalanceAtRisk = 0.05m, Leverage = 1, StopLoss = 0.021m, TakeProfit = 0.042m, diff --git a/src/Managing.WebApp/src/components/mollecules/Modal/BotNameModal.tsx b/src/Managing.WebApp/src/components/mollecules/Modal/BotNameModal.tsx index 82db356..c48b10d 100644 --- a/src/Managing.WebApp/src/components/mollecules/Modal/BotNameModal.tsx +++ b/src/Managing.WebApp/src/components/mollecules/Modal/BotNameModal.tsx @@ -1,14 +1,14 @@ -import React, { useState, useEffect } from 'react' +import React, {useEffect, useState} from 'react' -import type { Backtest, MoneyManagement } from '../../../generated/ManagingApi' -import type { IModalProps } from '../../../global/type' +import type {Backtest, MoneyManagement} from '../../../generated/ManagingApi' +import type {IModalProps} from '../../../global/type' import Modal from './Modal' interface IBotNameModalProps extends IModalProps { backtest: Backtest isForWatchOnly: boolean - onSubmitBotName: (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) => void + onSubmitBotName: (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => void moneyManagements: MoneyManagement[] selectedMoneyManagement: string setSelectedMoneyManagement: (name: string) => void @@ -25,6 +25,7 @@ const BotNameModal: React.FC = ({ setSelectedMoneyManagement, }) => { const [botName, setBotName] = useState('') + const [initialTradingBalance, setInitialTradingBalance] = useState(1000) // Initialize botName when backtest changes useEffect(() => { @@ -35,8 +36,8 @@ const BotNameModal: React.FC = ({ const handleSubmit = (e: React.FormEvent) => { e.preventDefault() - if (botName.trim()) { - onSubmitBotName(botName, backtest, isForWatchOnly, selectedMoneyManagement) + if (botName.trim() && initialTradingBalance > 0) { + onSubmitBotName(botName, backtest, isForWatchOnly, selectedMoneyManagement, initialTradingBalance) } } @@ -85,6 +86,22 @@ const BotNameModal: React.FC = ({ +
+ + setInitialTradingBalance(Number(e.target.value))} + min="10" + step="0.01" + required + /> +
+

{isForWatchOnly ? 'The bot will run in watch-only mode and will not execute trades.' diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx index e994dfa..19be777 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestCards.tsx @@ -1,22 +1,13 @@ -import { DotsVerticalIcon, TrashIcon } from '@heroicons/react/solid' +import {DotsVerticalIcon, TrashIcon} from '@heroicons/react/solid' import moment from 'moment' import React from 'react' import useApiUrlStore from '../../../app/store/apiStore' -import type { - Backtest, - MoneyManagement, - StartBotRequest, - Ticker, -} from '../../../generated/ManagingApi' -import { - BacktestClient, - BotClient, - BotType, -} from '../../../generated/ManagingApi' -import type { IBacktestCards } from '../../../global/type' +import type {Backtest, MoneyManagement, StartBotRequest, Ticker,} from '../../../generated/ManagingApi' +import {BacktestClient, BotClient, BotType,} from '../../../generated/ManagingApi' +import type {IBacktestCards} from '../../../global/type' import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal' -import { CardPosition, CardText, Toast } from '../../mollecules' +import {CardPosition, CardText, Toast} from '../../mollecules' import CardPositionItem from '../Trading/CardPositionItem' import TradeChart from '../Trading/TradeChart/TradeChart' @@ -73,6 +64,7 @@ const BacktestCards: React.FC = ({ list, setBacktests }) => { scenario: backtest.scenario, ticker: backtest.ticker as Ticker, timeframe: backtest.timeframe, + initialTradingBalance: 1000, } await client @@ -89,6 +81,10 @@ const BacktestCards: React.FC = ({ list, setBacktests }) => { const t = new Toast('Optimized backtest is running') const client = new BacktestClient({}, apiUrl) + // Calculate dates for the API call + const startDate = backtest.candles[0].date + const endDate = backtest.candles[backtest.candles.length - 1].date + await client .backtest_Run( backtest.accountName, @@ -96,12 +92,13 @@ const BacktestCards: React.FC = ({ list, setBacktests }) => { backtest.ticker as Ticker, backtest.scenario, backtest.timeframe, - false, - daysBetween(backtest.candles[0].date), - backtest.walletBalances[0].value, - '', - false, - backtest.optimizedMoneyManagement + false, // watchOnly + backtest.walletBalances[0].value, // balance + '', // moneyManagementName (empty since we're passing the optimized moneyManagement object) + startDate, // startDate + endDate, // endDate + false, // save + backtest.optimizedMoneyManagement // moneyManagement object ) .then((backtest: Backtest) => { t.update('success', `${backtest.ticker} Backtest Succeeded`) diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx index 64ef1ed..919be98 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx @@ -1,28 +1,23 @@ -import { useQuery } from '@tanstack/react-query' -import React, { useEffect, useState } from 'react' -import { useForm, type SubmitHandler } from 'react-hook-form' +import {useQuery} from '@tanstack/react-query' +import React, {useEffect, useState} from 'react' +import {type SubmitHandler, useForm} from 'react-hook-form' import useApiUrlStore from '../../../app/store/apiStore' import { - Backtest, - MoneyManagement, - Ticker, + AccountClient, + Backtest, + BacktestClient, + BotType, + DataClient, + MoneyManagement, + MoneyManagementClient, + ScenarioClient, + Ticker, + Timeframe, } from '../../../generated/ManagingApi' -import { - AccountClient, - BacktestClient, - BotType, - DataClient, - MoneyManagementClient, - ScenarioClient, - Timeframe, -} from '../../../generated/ManagingApi' -import type { - BacktestModalProps, - IBacktestsFormInput, -} from '../../../global/type' -import { Loader, Slider } from '../../atoms' -import { Modal, Toast } from '../../mollecules' +import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type' +import {Loader, Slider} from '../../atoms' +import {Modal, Toast} from '../../mollecules' import FormInput from '../../mollecules/FormInput/FormInput' import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement' @@ -100,6 +95,8 @@ const BacktestModal: React.FC = ({ // Use the name of the money management strategy if custom is not provided const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement + console.log(customMoneyManagement) + backtestClient .backtest_Run( form.accountName, @@ -122,7 +119,6 @@ const BacktestModal: React.FC = ({ if (showLoopSlider && selectedLoopQuantity > loopCount) { const nextCount = loopCount + 1 const mm: MoneyManagement = { - balanceAtRisk: backtest.optimizedMoneyManagement.balanceAtRisk, leverage: backtest.optimizedMoneyManagement.leverage, name: backtest.optimizedMoneyManagement.name + nextCount, stopLoss: backtest.optimizedMoneyManagement.stopLoss, @@ -148,6 +144,7 @@ const BacktestModal: React.FC = ({ function onMoneyManagementChange(e: any) { if (e.target.value === 'custom') { setShowCustomMoneyManagement(true) + setCustomMoneyManagement(e.target.value) } else { setShowCustomMoneyManagement(false) setCustomMoneyManagement(undefined) @@ -280,6 +277,32 @@ const BacktestModal: React.FC = ({ + {/* Losing streak info */} + {(() => { + // Find the selected money management object + let mm: MoneyManagement | undefined = undefined; + if (showCustomMoneyManagement && customMoneyManagement) { + mm = { ...customMoneyManagement, stopLoss: customMoneyManagement.stopLoss / 100 }; + } else if (selectedMoneyManagement) { + mm = moneyManagements.find((m) => m.name === selectedMoneyManagement); + } + // Use actual initial balance and a minimum threshold + const initialBalance = balance; + const minBalance = 10; // You can make this configurable if needed + if (mm && mm.leverage && mm.stopLoss && initialBalance > minBalance) { + const perLoss = mm.leverage * mm.stopLoss; + if (perLoss > 0 && perLoss < 1) { + const streak = Math.floor(Math.log(minBalance / initialBalance) / Math.log(1 - perLoss)); + return ( +

+ Max losing streak before balance drops below {minBalance}: {streak} trades (with leverage x{mm.leverage} and stop loss {Math.round(mm.stopLoss * 10000) / 100}% per trade, starting from {initialBalance}) +
+ ); + } + } + return null; + })()} + {showCustomMoneyManagement && (
= ({ list, isFetching, setBacktest } }, [moneyManagements]) - async function runBot(botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) { + async function runBot(botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) { const t = new Toast('Bot is starting') const client = new BotClient({}, apiUrl) @@ -70,6 +59,7 @@ const BacktestTable: React.FC = ({ list, isFetching, setBacktest scenario: backtest.scenario, ticker: backtest.ticker as Ticker, timeframe: backtest.timeframe, + initialTradingBalance: initialTradingBalance, } await client @@ -92,8 +82,8 @@ const BacktestTable: React.FC = ({ list, isFetching, setBacktest setShowBotNameModal(false) } - const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string) => { - runBot(botName, backtest, isForWatchOnly, moneyManagementName) + const handleSubmitBotName = (botName: string, backtest: Backtest, isForWatchOnly: boolean, moneyManagementName: string, initialTradingBalance: number) => { + runBot(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) setShowBotNameModal(false) } @@ -364,8 +354,8 @@ const BacktestTable: React.FC = ({ list, isFetching, setBacktest onClose={handleCloseBotNameModal} backtest={currentBacktest} isForWatchOnly={isForWatchOnly} - onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName) => - handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName) + onSubmitBotName={(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) => + handleSubmitBotName(botName, backtest, isForWatchOnly, moneyManagementName, initialTradingBalance) } moneyManagements={moneyManagements} selectedMoneyManagement={selectedMoneyManagement} diff --git a/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx b/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx index fcebe90..d3bce28 100644 --- a/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx +++ b/src/Managing.WebApp/src/components/organism/CustomMoneyManagement/CustomMoneyManagement.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from 'react' +import React, {useEffect, useState} from 'react' -import type { MoneyManagement, Timeframe } from '../../../generated/ManagingApi' -import { Slider } from '../../atoms' +import type {MoneyManagement, Timeframe} from '../../../generated/ManagingApi' import FormInput from '../../mollecules/FormInput/FormInput' -import { useCustomMoneyManagement } from '../../../app/store/customMoneyManagement' +import {useCustomMoneyManagement} from '../../../app/store/customMoneyManagement' type ICustomMoneyManagement = { onCreateMoneyManagement: (moneyManagement: MoneyManagement) => void @@ -18,14 +17,12 @@ const CustomMoneyManagement: React.FC = ({ }) => { const { moneyManagement, setCustomMoneyManagement } = useCustomMoneyManagement() - const [balanceAtRisk, setBalanceAtRisk] = useState(moneyManagement?.balanceAtRisk || 5) const [leverage, setLeverage] = useState(moneyManagement?.leverage || 1) const [takeProfit, setTakeProfit] = useState(moneyManagement?.takeProfit || 2) const [stopLoss, setStopLoss] = useState(moneyManagement?.stopLoss || 1) const handleCreateMoneyManagement = () => { const moneyManagement: MoneyManagement = { - balanceAtRisk, leverage, name: 'custom', stopLoss, @@ -38,7 +35,7 @@ const CustomMoneyManagement: React.FC = ({ useEffect(() => { handleCreateMoneyManagement() - }, [balanceAtRisk, leverage, takeProfit, stopLoss]) + }, [leverage, takeProfit, stopLoss]) return ( <> @@ -49,23 +46,6 @@ const CustomMoneyManagement: React.FC = ({ Custom MoneyManagement
- - setBalanceAtRisk(parseInt(e.target.value))} - min="1" - max="100" - step="1" - type='number' - className='input input-bordered' - > - - = ({ - showModal, - onClose, - moneyManagement, - disableInputs = false, -}) => { - const [balanceAtRisk, setBalanceAtRisk] = useState(5) - const [takeProfit, setTakeProfit] = useState(20) - const [name, setName] = useState('') - const [stopLoss, setStopLoss] = useState(10) - const [leverage, setLeverage] = useState(1) - const [timeframe, setTimeframe] = useState( - Timeframe.FifteenMinutes - ) - const { reset, register, handleSubmit } = useForm() - const { apiUrl } = useApiUrlStore() + showModal, + onClose, + moneyManagement, + disableInputs = false, + }) => { + const [takeProfit, setTakeProfit] = useState(20) + const [name, setName] = useState('') + const [stopLoss, setStopLoss] = useState(10) + const [leverage, setLeverage] = useState(1) + const [timeframe, setTimeframe] = useState( + Timeframe.FifteenMinutes + ) + const {reset, register, handleSubmit} = useForm() + const {apiUrl} = useApiUrlStore() - async function createMoneyManagement(form: IMoneyManagementFormInput) { - const t = new Toast('Creating settings') - const client = new MoneyManagementClient({}, apiUrl) - const mm: MoneyManagement = { - balanceAtRisk: balanceAtRisk / 100, - leverage: leverage, - name: name, - stopLoss: stopLoss / 100, - takeProfit: takeProfit / 100, - timeframe: form.timeframe, + async function createMoneyManagement(form: IMoneyManagementFormInput) { + const t = new Toast('Creating settings') + const client = new MoneyManagementClient({}, apiUrl) + const mm: MoneyManagement = { + leverage: leverage, + name: name, + stopLoss: stopLoss / 100, + takeProfit: takeProfit / 100, + timeframe: form.timeframe, + } + + await client + .moneyManagement_PostMoneyManagement(mm) + .then(() => { + t.update('success', 'Settings created') + }) + .catch((err) => { + t.update('error', 'Error :' + err) + }) } - await client - .moneyManagement_PostMoneyManagement(mm) - .then(() => { - t.update('success', 'Settings created') - }) - .catch((err) => { - t.update('error', 'Error :' + err) - }) - } - const onSubmit: SubmitHandler = async (form) => { - // @ts-ignore - await createMoneyManagement(form) - onClose() - } - - function onChangeName(e: any) { - setName(e.target.value) - } - - function onTakeProfitChange(e: any) { - setTakeProfit(e.target.value) - } - - function onStopLossChange(e: any) { - setStopLoss(e.target.value) - } - - function onLeverageChange(e: any) { - setLeverage(e.target.value) - } - - function onBalanceAtRiskChange(e: any) { - setBalanceAtRisk(e.target.value) - } - - useEffect(() => { - if (moneyManagement) { - console.log(moneyManagement) - setBalanceAtRisk(moneyManagement.balanceAtRisk * 100) - setTakeProfit(moneyManagement.takeProfit * 100) - setStopLoss(moneyManagement.stopLoss * 100) - setLeverage(moneyManagement.leverage) - setTimeframe(moneyManagement.timeframe) - setName(moneyManagement.name) - - const defaultValues: MoneyManagement = { - balanceAtRisk: moneyManagement.balanceAtRisk, - leverage: moneyManagement.leverage, - name: moneyManagement.name || '', - stopLoss: moneyManagement.stopLoss, - takeProfit: moneyManagement.takeProfit, - timeframe: moneyManagement.timeframe, - } - reset({ ...defaultValues }) + const onSubmit: SubmitHandler = async (form) => { + // @ts-ignore + await createMoneyManagement(form) + onClose() } - }, [showModal, moneyManagement]) - return ( - - - - + function onChangeName(e: any) { + setName(e.target.value) + } - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + -
-
- -
{(takeProfit / stopLoss).toFixed(2)}
-
-
- {disableInputs ? null : ( -
- -
- )} -
- ) +
+
+ +
{(takeProfit / stopLoss).toFixed(2)}
+
+
+ {disableInputs ? null : ( +
+ +
+ )} + + ) } export default MoneyManagementModal diff --git a/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx index 3e1bb4d..7a9d8e8 100644 --- a/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx +++ b/src/Managing.WebApp/src/pages/settingsPage/moneymanagement/moneymanagementTable.tsx @@ -1,15 +1,11 @@ -import { PencilAltIcon, TrashIcon } from '@heroicons/react/solid' -import React, { useEffect, useState } from 'react' +import {PencilAltIcon, TrashIcon} from '@heroicons/react/solid' +import React, {useEffect, useState} from 'react' import useApiUrlStore from '../../../app/store/apiStore' -import { - Toast, - SelectColumnFilter, - Table, -} from '../../../components/mollecules' -import type { MoneyManagement } from '../../../generated/ManagingApi' -import { MoneyManagementClient } from '../../../generated/ManagingApi' -import type { IMoneyManagementList } from '../../../global/type' +import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules' +import type {MoneyManagement} from '../../../generated/ManagingApi' +import {MoneyManagementClient} from '../../../generated/ManagingApi' +import type {IMoneyManagementList} from '../../../global/type' import MoneyManagementModal from './moneyManagementModal' @@ -63,13 +59,7 @@ const MoneyManagementTable: React.FC = ({ list }) => { disableSortBy: true, }, { - Cell: ({ cell }: any) => <>{cell.row.values.balanceAtRisk * 100} %, - Header: 'Balance used', - accessor: 'balanceAtRisk', - disableFilters: true, - }, - { - Cell: ({ cell }) => <>{cell.row.values.stopLoss * 100} %, + Cell: ({ cell }: any) => <>{cell.row.values.stopLoss * 100} %, Header: 'SL', accessor: 'stopLoss', disableFilters: true,