From 7c38c27b4ab682b6215a33e2162aeb66de183006 Mon Sep 17 00:00:00 2001 From: Oda <102867384+CryptoOda@users.noreply.github.com> Date: Fri, 9 May 2025 17:40:31 +0200 Subject: [PATCH] Fixes for bots running (#22) * Fixes for bots running * Up botmanager * Add cooldown * Refact can open position * Add cooldown Period and MaxLossStreak * Add agentName * Add env variable for botManager * Always enable Botmanager * Fix bot handle * Fix get positions * Add Ticker url * Dont start stopped bot * fix --- .DS_Store | Bin 10244 -> 10244 bytes .../Controllers/BacktestController.cs | 14 +- .../Controllers/BaseController.cs | 6 +- src/Managing.Api/Controllers/BotController.cs | 184 +- .../Controllers/DataController.cs | 201 +- .../Controllers/UserController.cs | 33 +- .../Models/Requests/StartBotRequest.cs | 7 + .../Models/Responses/AgentSummaryViewModel.cs | 4 +- .../Models/Responses/TickerInfos.cs | 9 + .../Models/Responses/TradingBot.cs | 6 +- src/Managing.Api/Program.cs | 6 +- .../appsettings.SandboxLocal.json | 3 +- .../Repositories/IUserRepository.cs | 1 + .../Services/IBacktester.cs | 32 +- .../Services/IUserService.cs | 2 + .../BaseWorker.cs | 2 +- .../Abstractions/IBotFactory.cs | 13 +- .../Abstractions/IBotService.cs | 30 +- .../Abstractions/ITradingBot.cs | 30 +- .../Backtesting/Backtester.cs | 135 +- .../Bots/Base/BotFactory.cs | 61 +- src/Managing.Application/Bots/FlippingBot.cs | 34 +- src/Managing.Application/Bots/ScalpingBot.cs | 32 +- src/Managing.Application/Bots/SimpleBot.cs | 2 +- src/Managing.Application/Bots/TradingBot.cs | 362 +- .../Bots/TradingBotConfig.cs | 20 + .../ManageBot/BotService.cs | 204 +- .../ManageBot/Commands/StartBotCommand.cs | 36 +- .../GetUserStrategiesCommandHandler.cs | 2 +- .../GetUserStrategyCommandHandler.cs | 11 +- .../ManageBot/LoadBackupBotCommandHandler.cs | 7 + .../ManageBot/StartBotCommandHandler.cs | 26 +- .../ToggleIsForWatchingCommandHandler.cs | 2 +- src/Managing.Application/Users/UserService.cs | 29 +- src/Managing.Bootstrap/ApiBootstrap.cs | 3 +- src/Managing.Bootstrap/WorkersBootstrap.cs | 3 + src/Managing.Domain/Bots/BotBackup.cs | 1 + src/Managing.Domain/Bots/IBot.cs | 5 +- src/Managing.Domain/Users/User.cs | 1 + .../MongoDb/Collections/BotDto.cs | 1 + .../MongoDb/Collections/UserDto.cs | 1 + .../MongoDb/MongoMappers.cs | 8 +- .../UserRepository.cs | 8 +- src/Managing.Nswag/.DS_Store | Bin 6148 -> 6148 bytes src/Managing.Nswag/ManagingApi.ts | 3135 +++++++++++++++++ src/Managing.Nswag/ManagingApiTypes.ts | 703 ++++ .../mollecules/TradesModal/TradesModal.tsx | 20 +- .../organism/Backtest/backtestModal.tsx | 28 +- .../src/generated/ManagingApi.ts | 148 +- src/Managing.WebApp/src/global/type.tsx | 2 + .../src/pages/backtestPage/backtest.tsx | 14 +- .../src/pages/botsPage/botList.tsx | 57 +- .../pages/settingsPage/UserInfoSettings.tsx | 101 + .../src/pages/settingsPage/settings.tsx | 20 +- 54 files changed, 5164 insertions(+), 641 deletions(-) create mode 100644 src/Managing.Api/Models/Responses/TickerInfos.cs create mode 100644 src/Managing.Application/Bots/TradingBotConfig.cs create mode 100644 src/Managing.Nswag/ManagingApi.ts create mode 100644 src/Managing.Nswag/ManagingApiTypes.ts create mode 100644 src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx diff --git a/.DS_Store b/.DS_Store index 2a2cb614b57cca6c471e373b338d483296b14bff..61f05361312239c003e502c1e1cb65729ac465c4 100644 GIT binary patch delta 48 zcmZn(XbIRLEX=rbvWRdYyJU5>p^1^%hKM`hT E0D9vOmjD0& delta 45 zcmZn(XbIRLEX=rVvWRdYi&%BF!Q>uc1;&oaYlY<*J2#&dw&K~$qVShxGm9uQGXPUZ B4f_B9 diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index d92f66e..3ad41e6 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -108,6 +108,8 @@ public class BacktestController : BaseController /// The name of the money management strategy to use. /// The money management strategy details, if not using a named strategy. /// Whether to save the backtest results. + /// The cooldown period for the backtest. + /// The maximum loss streak for the backtest. /// The result of the backtest. [HttpPost] [Route("Run")] @@ -122,7 +124,9 @@ public class BacktestController : BaseController DateTime startDate, DateTime endDate, MoneyManagement? moneyManagement = null, - bool save = false) + bool save = false, + decimal cooldownPeriod = 1, + int maxLossStreak = 0) { if (string.IsNullOrEmpty(accountName)) { @@ -174,7 +178,9 @@ public class BacktestController : BaseController endDate, user, watchOnly, - save); + save, + cooldownPeriod: cooldownPeriod, + maxLossStreak: maxLossStreak); break; case BotType.FlippingBot: backtestResult = await _backtester.RunFlippingBotBacktest( @@ -188,7 +194,9 @@ public class BacktestController : BaseController endDate, user, watchOnly, - save); + save, + cooldownPeriod: cooldownPeriod, + maxLossStreak: maxLossStreak); break; } diff --git a/src/Managing.Api/Controllers/BaseController.cs b/src/Managing.Api/Controllers/BaseController.cs index 70c6fdc..2421609 100644 --- a/src/Managing.Api/Controllers/BaseController.cs +++ b/src/Managing.Api/Controllers/BaseController.cs @@ -1,7 +1,7 @@ -using Managing.Application.Abstractions.Services; +using System.Security.Claims; +using Managing.Application.Abstractions.Services; using Managing.Domain.Users; using Microsoft.AspNetCore.Mvc; -using System.Security.Claims; namespace Managing.Api.Controllers; @@ -10,7 +10,7 @@ namespace Managing.Api.Controllers; [Produces("application/json")] public abstract class BaseController : ControllerBase { - private readonly IUserService _userService; + public readonly IUserService _userService; public BaseController(IUserService userService) { diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index cb7a18b..e91d082 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -1,6 +1,6 @@ -using Managing.Api.Models.Requests; -using Managing.Application.Abstractions; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; +using Managing.Application.Bots; using Managing.Application.Hubs; using Managing.Application.ManageBot.Commands; using Managing.Common; @@ -56,10 +56,10 @@ public class BotController : BaseController /// /// Checks if the current authenticated user owns the account associated with the specified bot or account name /// - /// The name of the bot to check + /// The identifier of the bot to check /// Optional account name to check when creating a new bot /// True if the user owns the account, False otherwise - private async Task UserOwnsBotAccount(string botName, string accountName = null) + private async Task UserOwnsBotAccount(string identifier, string accountName = null) { try { @@ -78,12 +78,12 @@ public class BotController : BaseController // For existing bots, check if the user owns the bot's account var activeBots = _botService.GetActiveBots(); - var bot = activeBots.FirstOrDefault(b => b.Name == botName); + var bot = activeBots.FirstOrDefault(b => b.Identifier == identifier); if (bot == null) return true; // Bot doesn't exist yet, so no ownership conflict var botAccountService = HttpContext.RequestServices.GetRequiredService(); - var botAccount = await botAccountService.GetAccount(bot.AccountName, true, false); + var botAccount = await botAccountService.GetAccount(bot.Config.AccountName, true, false); // Compare the user names return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name; } @@ -106,7 +106,7 @@ public class BotController : BaseController try { // Check if user owns the account - if (!await UserOwnsBotAccount(request.BotName, request.AccountName)) + if (!await UserOwnsBotAccount(request.Identifier, request.AccountName)) { return Forbid("You don't have permission to start this bot"); } @@ -131,9 +131,19 @@ public class BotController : BaseController $"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.InitialTradingBalance)); + var config = new TradingBotConfig + { + AccountName = request.AccountName, + MoneyManagement = moneyManagement, + Ticker = request.Ticker, + ScenarioName = request.Scenario, + Timeframe = request.Timeframe, + IsForWatchingOnly = request.IsForWatchOnly, + BotTradingBalance = request.InitialTradingBalance, + BotType = request.BotType + }; + + var result = await _mediator.Send(new StartBotCommand(config, request.Identifier, user)); await NotifyBotSubscriberAsync(); return Ok(result); @@ -149,22 +159,22 @@ public class BotController : BaseController /// Stops a bot specified by type and name. /// /// The type of the bot to stop. - /// The name of the bot to stop. + /// The identifier of the bot to stop. /// A string indicating the result of the stop operation. [HttpGet] [Route("Stop")] - public async Task> Stop(BotType botType, string botName) + public async Task> Stop(BotType botType, string identifier) { try { // Check if user owns the account - if (!await UserOwnsBotAccount(botName)) + if (!await UserOwnsBotAccount(identifier)) { return Forbid("You don't have permission to stop this bot"); } - var result = await _mediator.Send(new StopBotCommand(botType, botName)); - _logger.LogInformation($"{botType} type called {botName} is now {result}"); + var result = await _mediator.Send(new StopBotCommand(botType, identifier)); + _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}"); await NotifyBotSubscriberAsync(); @@ -180,21 +190,21 @@ public class BotController : BaseController /// /// Deletes a bot specified by name. /// - /// The name of the bot to delete. + /// The identifier of the bot to delete. /// A boolean indicating the result of the delete operation. [HttpDelete] [Route("Delete")] - public async Task> Delete(string botName) + public async Task> Delete(string identifier) { try { // Check if user owns the account - if (!await UserOwnsBotAccount(botName)) + if (!await UserOwnsBotAccount(identifier)) { return Forbid("You don't have permission to delete this bot"); } - var result = await _botService.DeleteBot(botName); + var result = await _botService.DeleteBot(identifier); await NotifyBotSubscriberAsync(); return Ok(result); } @@ -235,9 +245,9 @@ public class BotController : BaseController foreach (var bot in userBots) { - await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name)); + await _mediator.Send(new StopBotCommand(bot.BotType, bot.Identifier)); await _hubContext.Clients.All.SendAsync("SendNotification", - $"Bot {bot.Name} paused by {user.Name}.", "Info"); + $"Bot {bot.Identifier} paused by {user.Name}.", "Info"); } await NotifyBotSubscriberAsync(); @@ -254,22 +264,22 @@ public class BotController : BaseController /// Restarts a bot specified by type and name. /// /// The type of the bot to restart. - /// The name of the bot to restart. + /// The identifier of the bot to restart. /// A string indicating the result of the restart operation. [HttpGet] [Route("Restart")] - public async Task> Restart(BotType botType, string botName) + public async Task> Restart(BotType botType, string identifier) { try { // Check if user owns the account - if (!await UserOwnsBotAccount(botName)) + if (!await UserOwnsBotAccount(identifier)) { return Forbid("You don't have permission to restart this bot"); } - var result = await _mediator.Send(new RestartBotCommand(botType, botName)); - _logger.LogInformation($"{botType} type called {botName} is now {result}"); + var result = await _mediator.Send(new RestartBotCommand(botType, identifier)); + _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}"); await NotifyBotSubscriberAsync(); @@ -314,15 +324,15 @@ public class BotController : BaseController { // We can't directly restart a bot with just BotType and Name // Instead, stop the bot and then retrieve the backup to start it again - await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name)); + await _mediator.Send(new StopBotCommand(bot.BotType, bot.Identifier)); // Get the saved bot backup - var backup = _botService.GetBotBackup(bot.Name); + var backup = _botService.GetBotBackup(bot.Identifier); if (backup != null) { _botService.StartBotFromBackup(backup); await _hubContext.Clients.All.SendAsync("SendNotification", - $"Bot {bot.Name} restarted by {user.Name}.", "Info"); + $"Bot {bot.Identifier} restarted by {user.Name}.", "Info"); } } @@ -339,22 +349,22 @@ public class BotController : BaseController /// /// Toggles the watching status of a bot specified by name. /// - /// The name of the bot to toggle watching status. + /// The identifier of the bot to toggle watching status. /// A string indicating the new watching status of the bot. [HttpGet] [Route("ToggleIsForWatching")] - public async Task> ToggleIsForWatching(string botName) + public async Task> ToggleIsForWatching(string identifier) { try { // Check if user owns the account - if (!await UserOwnsBotAccount(botName)) + if (!await UserOwnsBotAccount(identifier)) { return Forbid("You don't have permission to modify this bot"); } - var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName)); - _logger.LogInformation($"{botName} bot is now {result}"); + var result = await _mediator.Send(new ToggleIsForWatchingCommand(identifier)); + _logger.LogInformation($"Bot with identifier {identifier} is now {result}"); await NotifyBotSubscriberAsync(); @@ -397,13 +407,15 @@ public class BotController : BaseController Candles = item.Candles.DistinctBy(c => c.Date).ToList(), WinRate = item.GetWinRate(), ProfitAndLoss = item.GetProfitAndLoss(), - Timeframe = item.Timeframe, - Ticker = item.Ticker, - Scenario = item.ScenarioName, - IsForWatchingOnly = item.IsForWatchingOnly, - BotType = item.BotType, - AccountName = item.AccountName, - MoneyManagement = item.MoneyManagement + Timeframe = item.Config.Timeframe, + Ticker = item.Config.Ticker, + Scenario = item.Config.ScenarioName, + IsForWatchingOnly = item.Config.IsForWatchingOnly, + BotType = item.Config.BotType, + AccountName = item.Config.AccountName, + MoneyManagement = item.Config.MoneyManagement, + Identifier = item.Identifier, + AgentName = item.User.AgentName }); } @@ -431,22 +443,22 @@ public class BotController : BaseController try { // Check if user owns the account - if (!await UserOwnsBotAccount(request.BotName)) + if (!await UserOwnsBotAccount(request.Identifier)) { return Forbid("You don't have permission to open positions for this bot"); } var activeBots = _botService.GetActiveBots(); - var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot; + var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier) as ApplicationTradingBot; if (bot == null) { - return NotFound($"Bot {request.BotName} not found or is not a trading bot"); + return NotFound($"Bot with identifier {request.Identifier} not found or is not a trading bot"); } if (bot.GetStatus() != BotStatus.Up.ToString()) { - return BadRequest($"Bot {request.BotName} is not running"); + return BadRequest($"Bot with identifier {request.Identifier} is not running"); } var position = await bot.OpenPositionManually( @@ -475,29 +487,30 @@ public class BotController : BaseController try { // Check if user owns the account - if (!await UserOwnsBotAccount(request.BotName)) + if (!await UserOwnsBotAccount(request.Identifier)) { return Forbid("You don't have permission to close positions for this bot"); } var activeBots = _botService.GetActiveBots(); - var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot; + var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier) as ApplicationTradingBot; if (bot == null) { - return NotFound($"Bot {request.BotName} not found or is not a trading bot"); + return NotFound($"Bot with identifier {request.Identifier} not found or is not a trading bot"); } if (bot.GetStatus() != BotStatus.Up.ToString()) { - return BadRequest($"Bot {request.BotName} is not running"); + return BadRequest($"Bot with identifier {request.Identifier} is not running"); } // Find the position to close var position = bot.Positions.FirstOrDefault(p => p.Identifier == request.PositionId); if (position == null) { - return NotFound($"Position with ID {request.PositionId} not found for bot {request.BotName}"); + return NotFound( + $"Position with ID {request.PositionId} not found for bot with identifier {request.Identifier}"); } // Find the signal associated with this position @@ -528,18 +541,85 @@ public class BotController : BaseController } } +/// +/// Request model for opening a position manually +/// +public class OpenPositionManuallyRequest +{ + /// + /// The identifier of the bot + /// + public string Identifier { get; set; } + + /// + /// The direction of the position + /// + public TradeDirection Direction { get; set; } +} + /// /// Request model for closing a position /// public class ClosePositionRequest { /// - /// The name of the bot + /// The identifier of the bot /// - public string BotName { get; set; } + public string Identifier { get; set; } /// /// The ID of the position to close /// public string PositionId { get; set; } +} + +/// +/// Request model for starting a bot +/// +public class StartBotRequest +{ + /// + /// The type of bot to start + /// + public BotType BotType { get; set; } + + /// + /// The identifier of the bot + /// + public string Identifier { get; set; } + + /// + /// The ticker to trade + /// + public Ticker Ticker { get; set; } + + /// + /// The scenario to use + /// + public string Scenario { get; set; } + + /// + /// The timeframe to use + /// + public Timeframe Timeframe { get; set; } + + /// + /// The account name to use + /// + public string AccountName { get; set; } + + /// + /// The money management name to use + /// + public string MoneyManagementName { get; set; } + + /// + /// Whether the bot is for watching only + /// + public bool IsForWatchOnly { get; set; } + + /// + /// The initial trading balance + /// + public decimal InitialTradingBalance { get; set; } } \ No newline at end of file diff --git a/src/Managing.Api/Controllers/DataController.cs b/src/Managing.Api/Controllers/DataController.cs index d3d3ee9..1f685b5 100644 --- a/src/Managing.Api/Controllers/DataController.cs +++ b/src/Managing.Api/Controllers/DataController.cs @@ -1,20 +1,18 @@ -using Managing.Application.Abstractions; +using Managing.Api.Models.Responses; +using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Hubs; using Managing.Application.ManageBot.Commands; using Managing.Application.Workers.Abstractions; -using Managing.Api.Models.Responses; using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.Shared.Helpers; using Managing.Domain.Statistics; using Managing.Domain.Trades; -using Managing.Domain.Users; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; -using System.Linq; using static Managing.Common.Enums; namespace Managing.Api.Controllers; @@ -65,20 +63,114 @@ public class DataController : ControllerBase /// The timeframe for which to retrieve tickers. /// An array of tickers. [HttpPost("GetTickers")] - public async Task> GetTickers(Timeframe timeframe) + public async Task>> GetTickers(Timeframe timeframe) { var cacheKey = string.Concat(timeframe.ToString()); - var tickers = _cacheService.GetValue>(cacheKey); + var tickers = _cacheService.GetValue>(cacheKey); if (tickers == null || tickers.Count == 0) { - tickers = await _exchangeService.GetTickers(timeframe); + var availableTicker = await _exchangeService.GetTickers(timeframe); + + tickers = MapTickerToTickerInfos(availableTicker); + _cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2)); } return Ok(tickers); } + private List MapTickerToTickerInfos(List availableTicker) + { + var tickerInfos = new List(); + var tokens = new Dictionary + { + { "AAVE", "https://assets.coingecko.com/coins/images/12645/standard/AAVE.png?1696512452" }, + { "ADA", "https://assets.coingecko.com/coins/images/975/standard/cardano.png?1696502090" }, + { "APE", "https://assets.coingecko.com/coins/images/24383/standard/apecoin.jpg?1696523566" }, + { "ARB", "https://assets.coingecko.com/coins/images/16547/small/photo_2023-03-29_21.47.00.jpeg?1680097630" }, + { "ATOM", "https://assets.coingecko.com/coins/images/1481/standard/cosmos_hub.png?1696502525" }, + { "AVAX", "https://assets.coingecko.com/coins/images/12559/small/coin-round-red.png?1604021818" }, + { "BNB", "https://assets.coingecko.com/coins/images/825/standard/bnb-icon2_2x.png?1696501970" }, + { "BTC", "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579" }, + { "DOGE", "https://assets.coingecko.com/coins/images/5/small/dogecoin.png?1547792256" }, + { "DOT", "https://static.coingecko.com/s/polkadot-73b0c058cae10a2f076a82dcade5cbe38601fad05d5e6211188f09eb96fa4617.gif" }, + { "ETH", "https://assets.coingecko.com/coins/images/279/small/ethereum.png?1595348880" }, + { "FIL", "https://assets.coingecko.com/coins/images/12817/standard/filecoin.png?1696512609" }, + { "GMX", "https://assets.coingecko.com/coins/images/18323/small/arbit.png?1631532468" }, + { "LINK", "https://assets.coingecko.com/coins/images/877/thumb/chainlink-new-logo.png?1547034700" }, + { "LTC", "https://assets.coingecko.com/coins/images/2/small/litecoin.png?1547033580" }, + { "MATIC", "https://assets.coingecko.com/coins/images/32440/standard/polygon.png?1698233684" }, + { "NEAR", "https://assets.coingecko.com/coins/images/10365/standard/near.jpg?1696510367" }, + { "OP", "https://assets.coingecko.com/coins/images/25244/standard/Optimism.png?1696524385" }, + { "PEPE", "https://assets.coingecko.com/coins/images/29850/standard/pepe-token.jpeg?1696528776" }, + { "SOL", "https://assets.coingecko.com/coins/images/4128/small/solana.png?1640133422" }, + { "UNI", "https://assets.coingecko.com/coins/images/12504/thumb/uniswap-uni.png?1600306604" }, + { "USDC", "https://assets.coingecko.com/coins/images/6319/thumb/USD_Coin_icon.png?1547042389" }, + { "USDT", "https://assets.coingecko.com/coins/images/325/thumb/Tether-logo.png?1598003707" }, + { "WIF", "https://assets.coingecko.com/coins/images/33566/standard/dogwifhat.jpg?1702499428" }, + { "XRP", "https://assets.coingecko.com/coins/images/44/small/xrp-symbol-white-128.png?1605778731" }, + { "SHIB", "https://assets.coingecko.com/coins/images/11939/standard/shiba.png?1696511800" }, + { "STX", "https://assets.coingecko.com/coins/images/2069/standard/Stacks_Logo_png.png?1709979332" }, + { "ORDI", "https://assets.coingecko.com/coins/images/30162/standard/ordi.png?1696529082" }, + { "APT", "https://assets.coingecko.com/coins/images/26455/standard/aptos_round.png?1696525528" }, + { "BOME", "https://assets.coingecko.com/coins/images/36071/standard/bome.png?1710407255" }, + { "MEME", "https://assets.coingecko.com/coins/images/32528/standard/memecoin_%282%29.png?1698912168" }, + { "FLOKI", "https://assets.coingecko.com/coins/images/16746/standard/PNG_image.png?1696516318" }, + { "MEW", "https://assets.coingecko.com/coins/images/36440/standard/MEW.png?1711442286" }, + { "TAO", "https://assets.coingecko.com/coins/images/28452/standard/ARUsPeNQ_400x400.jpeg?1696527447" }, + { "BONK", "https://assets.coingecko.com/coins/images/28600/standard/bonk.jpg?1696527587" }, + { "WLD", "https://assets.coingecko.com/coins/images/31069/standard/worldcoin.jpeg?1696529903" }, + { "tBTC", "https://assets.coingecko.com/coins/images/11224/standard/0x18084fba666a33d37592fa2633fd49a74dd93a88.png?1696511155" }, + { "EIGEN", "https://assets.coingecko.com/coins/images/37441/standard/eigen.jpg?1728023974" }, + { "SUI", "https://assets.coingecko.com/coins/images/26375/standard/sui-ocean-square.png?1727791290" }, + { "SEI", "https://assets.coingecko.com/coins/images/28205/standard/Sei_Logo_-_Transparent.png?1696527207" }, + { "DAI", "https://assets.coingecko.com/coins/images/9956/thumb/4943.png?1636636734" }, + { "TIA", "https://assets.coingecko.com/coins/images/31967/standard/tia.jpg?1696530772" }, + { "TRX", "https://assets.coingecko.com/coins/images/1094/standard/tron-logo.png?1696502193" }, + { "TON", "https://assets.coingecko.com/coins/images/17980/standard/photo_2024-09-10_17.09.00.jpeg?1725963446" }, + { "PENDLE", "https://assets.coingecko.com/coins/images/15069/standard/Pendle_Logo_Normal-03.png?1696514728" }, + { "wstETH", "https://assets.coingecko.com/coins/images/18834/standard/wstETH.png?1696518295" }, + { "USDe", "https://assets.coingecko.com/coins/images/33613/standard/USDE.png?1716355685" }, + { "SATS", "https://assets.coingecko.com/coins/images/30666/standard/_dD8qr3M_400x400.png?1702913020" }, + { "POL", "https://assets.coingecko.com/coins/images/32440/standard/polygon.png?1698233684" }, + { "XLM", "https://assets.coingecko.com/coins/images/100/standard/Stellar_symbol_black_RGB.png?1696501482" }, + { "BCH", "https://assets.coingecko.com/coins/images/780/standard/bitcoin-cash-circle.png?1696501932" }, + { "ICP", "https://assets.coingecko.com/coins/images/14495/standard/Internet_Computer_logo.png?1696514180" }, + { "RENDER", "https://assets.coingecko.com/coins/images/11636/standard/rndr.png?1696511529" }, + { "INJ", "https://assets.coingecko.com/coins/images/12882/standard/Secondary_Symbol.png?1696512670" }, + { "TRUMP", "https://assets.coingecko.com/coins/images/53746/standard/trump.png?1737171561" }, + { "MELANIA", "https://assets.coingecko.com/coins/images/53775/standard/melania-meme.png?1737329885" }, + { "ENA", "https://assets.coingecko.com/coins/images/36530/standard/ethena.png?1711701436" }, + { "FARTCOIN", "https://assets.coingecko.com/coins/images/50891/standard/fart.jpg?1729503972" }, + { "AI16Z", "https://assets.coingecko.com/coins/images/51090/standard/AI16Z.jpg?1730027175" }, + { "ANIME", "https://assets.coingecko.com/coins/images/53575/standard/anime.jpg?1736748703" }, + { "BERA", "https://assets.coingecko.com/coins/images/25235/standard/BERA.png?1738822008" }, + { "VIRTUAL", "https://assets.coingecko.com/coins/images/34057/standard/LOGOMARK.png?1708356054" }, + { "PENGU", "https://assets.coingecko.com/coins/images/52622/standard/PUDGY_PENGUINS_PENGU_PFP.png?1733809110" }, + { "FET", "https://assets.coingecko.com/coins/images/5681/standard/ASI.png?1719827289" }, + { "ONDO", "https://assets.coingecko.com/coins/images/26580/standard/ONDO.png?1696525656" }, + { "AIXBT", "https://assets.coingecko.com/coins/images/51784/standard/3.png?1731981138" }, + { "CAKE", "https://assets.coingecko.com/coins/images/12632/standard/pancakeswap-cake-logo_%281%29.png?1696512440" }, + { "S", "https://assets.coingecko.com/coins/images/38108/standard/200x200_Sonic_Logo.png?1734679256" }, + { "JUP", "https://assets.coingecko.com/coins/images/34188/standard/jup.png?1704266489" }, + { "HYPE", "https://assets.coingecko.com/coins/images/50882/standard/hyperliquid.jpg?1729431300" }, + { "OM", "https://assets.coingecko.com/coins/images/12151/standard/OM_Token.png?1696511991" } + }; + + foreach (var ticker in availableTicker) + { + var tickerInfo = new TickerInfos + { + Ticker = ticker, + ImageUrl = tokens.GetValueOrDefault(ticker.ToString(), "https://assets.coingecko.com/coins/images/1/small/bitcoin.png?1547033579") // Default to BTC image if not found + }; + tickerInfos.Add(tickerInfo); + } + + return tickerInfos; + } + /// /// Retrieves the latest spotlight overview, using caching to enhance response times. /// @@ -222,24 +314,24 @@ public class DataController : ControllerBase public async Task>> GetUserStrategies(string agentName) { string cacheKey = $"UserStrategies_{agentName}"; - + // Check if the user strategy details are already cached var cachedDetails = _cacheService.GetValue>(cacheKey); - + if (cachedDetails != null && cachedDetails.Count > 0) { return Ok(cachedDetails); } - + // Get all strategies for the specified user var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName)); - + // Convert to detailed view model with additional information var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy)).ToList(); - + // Cache the results for 5 minutes _cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5)); - + return Ok(result); } @@ -253,32 +345,32 @@ public class DataController : ControllerBase public async Task> GetUserStrategy(string agentName, string strategyName) { string cacheKey = $"UserStrategy_{agentName}_{strategyName}"; - + // Check if the user strategy details are already cached var cachedDetails = _cacheService.GetValue(cacheKey); - + if (cachedDetails != null) { return Ok(cachedDetails); } - + // Get the specific strategy for the user var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName)); - + if (strategy == null) { return NotFound($"Strategy '{strategyName}' not found for user '{agentName}'"); } - + // Map the strategy to a view model using the shared method var result = MapStrategyToViewModel(strategy); - + // Cache the results for 5 minutes _cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5)); - + return Ok(result); } - + /// /// Maps a trading bot to a strategy view model with detailed statistics /// @@ -288,7 +380,7 @@ public class DataController : ControllerBase { // Get the runtime directly from the bot TimeSpan runtimeSpan = strategy.GetRuntime(); - + // Get the startup time from the bot's internal property // If bot is not running, we use MinValue as a placeholder DateTime startupTime = DateTime.MinValue; @@ -296,30 +388,30 @@ public class DataController : ControllerBase { startupTime = bot.StartupTime; } - + // Calculate ROI percentage based on PnL relative to account value decimal pnl = strategy.GetProfitAndLoss(); - + // If we had initial investment amount, we could calculate ROI like: decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0; - + // Calculate volume statistics decimal totalVolume = TradingBox.GetTotalVolumeTraded(strategy.Positions); decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(strategy.Positions); - + // Calculate win/loss statistics (int wins, int losses) = TradingBox.GetWinLossCount(strategy.Positions); - + // Calculate ROI for last 24h decimal roiLast24h = TradingBox.GetLast24HROI(strategy.Positions); - + return new UserStrategyDetailsViewModel { Name = strategy.Name, - StrategyName = strategy.ScenarioName, - State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" : - strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED", + StrategyName = strategy.Config.ScenarioName, + State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" : + strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED", PnL = pnl, ROIPercentage = roi, ROILast24H = roiLast24h, @@ -329,7 +421,8 @@ public class DataController : ControllerBase VolumeLast24H = volumeLast24h, Wins = wins, Losses = losses, - Positions = strategy.Positions.OrderByDescending(p => p.Date).ToList() // Include sorted positions with most recent first + Positions = strategy.Positions.OrderByDescending(p => p.Date) + .ToList() // Include sorted positions with most recent first }; } @@ -347,20 +440,20 @@ public class DataController : ControllerBase { timeFilter = "Total"; // Default to Total if invalid } - + string cacheKey = $"PlatformSummary_{timeFilter}"; - + // Check if the platform summary is already cached var cachedSummary = _cacheService.GetValue(cacheKey); - + if (cachedSummary != null) { return Ok(cachedSummary); } - + // Get all agents and their strategies var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter)); - + // Create the platform summary var summary = new PlatformSummaryViewModel { @@ -368,50 +461,50 @@ public class DataController : ControllerBase TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count), TimeFilter = timeFilter }; - + // Calculate total platform metrics decimal totalPlatformPnL = 0; decimal totalPlatformVolume = 0; decimal totalPlatformVolumeLast24h = 0; - + // Create summaries for each agent foreach (var agent in agentsWithStrategies) { var user = agent.Key; var strategies = agent.Value; - + if (strategies.Count == 0) { continue; // Skip agents with no strategies } - + // Combine all positions from all strategies var allPositions = strategies.SelectMany(s => s.Positions).ToList(); - + // Calculate agent metrics decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter); decimal pnlLast24h = TradingBox.GetPnLInTimeRange(allPositions, "24H"); - + decimal totalROI = TradingBox.GetROIInTimeRange(allPositions, timeFilter); decimal roiLast24h = TradingBox.GetROIInTimeRange(allPositions, "24H"); - + (int wins, int losses) = TradingBox.GetWinLossCountInTimeRange(allPositions, timeFilter); - + // Calculate trading volumes decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions); decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions); - + // Calculate win rate int averageWinRate = 0; if (wins + losses > 0) { averageWinRate = (wins * 100) / (wins + losses); } - + // Add to agent summaries var agentSummary = new AgentSummaryViewModel { - Username = user.Name, + AgentName = user.AgentName, TotalPnL = totalPnL, PnLLast24h = pnlLast24h, TotalROI = totalROI, @@ -423,26 +516,26 @@ public class DataController : ControllerBase TotalVolume = totalVolume, VolumeLast24h = volumeLast24h }; - + summary.AgentSummaries.Add(agentSummary); - + // Add to platform totals totalPlatformPnL += totalPnL; totalPlatformVolume += totalVolume; totalPlatformVolumeLast24h += volumeLast24h; } - + // Set the platform totals summary.TotalPlatformPnL = totalPlatformPnL; summary.TotalPlatformVolume = totalPlatformVolume; summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h; - + // Sort agent summaries by total PnL (highest first) summary.AgentSummaries = summary.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList(); - + // Cache the results for 5 minutes _cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5)); - + return Ok(summary); } } \ No newline at end of file diff --git a/src/Managing.Api/Controllers/UserController.cs b/src/Managing.Api/Controllers/UserController.cs index 632269f..acbfcd0 100644 --- a/src/Managing.Api/Controllers/UserController.cs +++ b/src/Managing.Api/Controllers/UserController.cs @@ -1,6 +1,7 @@ using Managing.Api.Authorization; using Managing.Api.Models.Requests; using Managing.Application.Abstractions.Services; +using Managing.Domain.Users; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -12,10 +13,9 @@ namespace Managing.Api.Controllers; [ApiController] [Route("[controller]")] [Produces("application/json")] -public class UserController : ControllerBase +public class UserController : BaseController { private IConfiguration _config; - private readonly IUserService _userService; private readonly IJwtUtils _jwtUtils; /// @@ -25,9 +25,9 @@ public class UserController : ControllerBase /// Service for user-related operations. /// Utility for JWT token operations. public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils) + : base(userService) { _config = config; - _userService = userService; _jwtUtils = jwtUtils; } @@ -49,5 +49,30 @@ public class UserController : ControllerBase } return Unauthorized(); + } + + /// + /// Gets the current user's information. + /// + /// The current user's information. + [HttpGet] + public async Task> GetCurrentUser() + { + var user = await base.GetUser(); + return Ok(user); } -} \ No newline at end of file + + /// + /// Updates the agent name for the current user. + /// + /// The new agent name to set. + /// The updated user with the new agent name. + [HttpPut("agent-name")] + public async Task> UpdateAgentName([FromBody] string agentName) + { + var user = await GetUser(); + var updatedUser = await _userService.UpdateAgentName(user, agentName); + return Ok(updatedUser); + } +} + \ No newline at end of file diff --git a/src/Managing.Api/Models/Requests/StartBotRequest.cs b/src/Managing.Api/Models/Requests/StartBotRequest.cs index e60de8a..ed714f1 100644 --- a/src/Managing.Api/Models/Requests/StartBotRequest.cs +++ b/src/Managing.Api/Models/Requests/StartBotRequest.cs @@ -20,5 +20,12 @@ namespace Managing.Api.Models.Requests [Required] [Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")] public decimal InitialTradingBalance { get; set; } + + /// + /// Cooldown period in minutes between trades + /// + [Required] + [Range(1, 1440, ErrorMessage = "Cooldown period must be between 1 and 1440 minutes (24 hours)")] + public decimal CooldownPeriod { get; set; } = 1; // Default to 1 minute if not specified } } \ No newline at end of file diff --git a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs index 590839d..f40c09c 100644 --- a/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs +++ b/src/Managing.Api/Models/Responses/AgentSummaryViewModel.cs @@ -6,9 +6,9 @@ namespace Managing.Api.Models.Responses public class AgentSummaryViewModel { /// - /// Username of the agent + /// AgentName of the agent /// - public string Username { get; set; } + public string AgentName { get; set; } /// /// Total profit and loss in USD diff --git a/src/Managing.Api/Models/Responses/TickerInfos.cs b/src/Managing.Api/Models/Responses/TickerInfos.cs new file mode 100644 index 0000000..d0571a4 --- /dev/null +++ b/src/Managing.Api/Models/Responses/TickerInfos.cs @@ -0,0 +1,9 @@ +using Managing.Common; + +namespace Managing.Api.Models.Responses; + +public class TickerInfos +{ + public Enums.Ticker Ticker { get; set; } + public string ImageUrl { get; set; } +} \ No newline at end of file diff --git a/src/Managing.Api/Models/Responses/TradingBot.cs b/src/Managing.Api/Models/Responses/TradingBot.cs index a53d6f5..5ff58cb 100644 --- a/src/Managing.Api/Models/Responses/TradingBot.cs +++ b/src/Managing.Api/Models/Responses/TradingBot.cs @@ -1,8 +1,8 @@ -using Managing.Domain.Candles; +using System.ComponentModel.DataAnnotations; +using Managing.Domain.Candles; using Managing.Domain.MoneyManagements; using Managing.Domain.Strategies; using Managing.Domain.Trades; -using System.ComponentModel.DataAnnotations; using static Managing.Common.Enums; namespace Managing.Api.Models.Responses @@ -23,5 +23,7 @@ namespace Managing.Api.Models.Responses [Required] public BotType BotType { get; internal set; } [Required] public string AccountName { get; internal set; } [Required] public MoneyManagement MoneyManagement { get; internal set; } + [Required] public string Identifier { get; set; } + [Required] public string AgentName { get; set; } } } \ No newline at end of file diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs index a662a02..5113b21 100644 --- a/src/Managing.Api/Program.cs +++ b/src/Managing.Api/Program.cs @@ -4,6 +4,7 @@ using HealthChecks.UI.Client; using Managing.Api.Authorization; using Managing.Api.Filters; using Managing.Api.HealthChecks; +using Managing.Api.Workers; using Managing.Application.Hubs; using Managing.Bootstrap; using Managing.Common; @@ -195,7 +196,10 @@ builder.Services.AddSwaggerGen(options => }); builder.WebHost.SetupDiscordBot(); -// builder.Services.AddHostedService(); +if (builder.Configuration.GetValue("EnableBotManager", false)) +{ + builder.Services.AddHostedService(); +} // App var app = builder.Build(); diff --git a/src/Managing.Api/appsettings.SandboxLocal.json b/src/Managing.Api/appsettings.SandboxLocal.json index 8de4c89..3a889e5 100644 --- a/src/Managing.Api/appsettings.SandboxLocal.json +++ b/src/Managing.Api/appsettings.SandboxLocal.json @@ -24,5 +24,6 @@ "ElasticConfiguration": { "Uri": "http://elasticsearch:9200" }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "EnableBotManager": true } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs index 5cd6356..6876d6f 100644 --- a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs @@ -4,6 +4,7 @@ namespace Managing.Application.Abstractions.Repositories; public interface IUserRepository { + Task GetUserByAgentNameAsync(string agentName); Task GetUserByNameAsync(string name); Task InsertUserAsync(User user); Task UpdateUser(User user); diff --git a/src/Managing.Application.Abstractions/Services/IBacktester.cs b/src/Managing.Application.Abstractions/Services/IBacktester.cs index 6666efa..6e0e64a 100644 --- a/src/Managing.Application.Abstractions/Services/IBacktester.cs +++ b/src/Managing.Application.Abstractions/Services/IBacktester.cs @@ -22,7 +22,9 @@ namespace Managing.Application.Abstractions.Services User user = null, bool isForWatchingOnly = false, bool save = false, - List? initialCandles = null); + List? initialCandles = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0); Task RunFlippingBotBacktest( Account account, @@ -36,16 +38,34 @@ namespace Managing.Application.Abstractions.Services User user = null, bool isForWatchingOnly = false, bool save = false, - List? initialCandles = null); + List? initialCandles = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0); bool DeleteBacktest(string id); bool DeleteBacktests(); - Task RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, - Timeframe timeframe, List candles, decimal balance, User user = null); + Task RunScalpingBotBacktest( + Account account, + MoneyManagement moneyManagement, + Scenario scenario, + Timeframe timeframe, + List candles, + decimal balance, + User user = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0); - Task RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, - Timeframe timeframe, List candles, decimal balance, User user = null); + Task RunFlippingBotBacktest( + Account account, + MoneyManagement moneyManagement, + Scenario scenario, + Timeframe timeframe, + List candles, + decimal balance, + User user = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0); // User-specific operations Task> GetBacktestsByUser(User user); diff --git a/src/Managing.Application.Abstractions/Services/IUserService.cs b/src/Managing.Application.Abstractions/Services/IUserService.cs index 36349a5..be8e8ad 100644 --- a/src/Managing.Application.Abstractions/Services/IUserService.cs +++ b/src/Managing.Application.Abstractions/Services/IUserService.cs @@ -6,4 +6,6 @@ public interface IUserService { Task Authenticate(string name, string address, string message, string signature); Task GetUserByAddressAsync(string address); + Task UpdateAgentName(User user, string agentName); + User GetUser(string name); } diff --git a/src/Managing.Application.Workers/BaseWorker.cs b/src/Managing.Application.Workers/BaseWorker.cs index ae83927..6f9e28f 100644 --- a/src/Managing.Application.Workers/BaseWorker.cs +++ b/src/Managing.Application.Workers/BaseWorker.cs @@ -50,7 +50,7 @@ public abstract class BaseWorker : BackgroundService where T : class { worker = await _workerService.GetWorker(_workerType); - if (worker.IsActive) + if (worker.IsActive || worker.WorkerType.Equals(WorkerType.BotManager)) { await Run(cancellationToken); _executionCount++; diff --git a/src/Managing.Application/Abstractions/IBotFactory.cs b/src/Managing.Application/Abstractions/IBotFactory.cs index 9c91569..25a7f97 100644 --- a/src/Managing.Application/Abstractions/IBotFactory.cs +++ b/src/Managing.Application/Abstractions/IBotFactory.cs @@ -1,16 +1,15 @@ -using Managing.Domain.Bots; -using Managing.Domain.MoneyManagements; +using Managing.Application.Bots; +using Managing.Domain.Bots; using Managing.Domain.Workflows; -using static Managing.Common.Enums; namespace Managing.Application.Abstractions { public interface IBotFactory { - 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); + ITradingBot CreateScalpingBot(TradingBotConfig config); + ITradingBot CreateBacktestScalpingBot(TradingBotConfig config); + ITradingBot CreateFlippingBot(TradingBotConfig config); + ITradingBot CreateBacktestFlippingBot(TradingBotConfig config); } } diff --git a/src/Managing.Application/Abstractions/IBotService.cs b/src/Managing.Application/Abstractions/IBotService.cs index 8ad7358..93ede4b 100644 --- a/src/Managing.Application/Abstractions/IBotService.cs +++ b/src/Managing.Application/Abstractions/IBotService.cs @@ -1,38 +1,30 @@ -using Managing.Common; +using Managing.Application.Bots; using Managing.Domain.Bots; -using Managing.Domain.MoneyManagements; using Managing.Domain.Users; using Managing.Domain.Workflows; +using static Managing.Common.Enums; namespace Managing.Application.Abstractions; public interface IBotService { - void SaveOrUpdateBotBackup(BotBackup botBackup); - void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data); + void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data); void AddSimpleBotToCache(IBot bot); void AddTradingBotToCache(ITradingBot bot); List GetActiveBots(); IEnumerable GetSavedBots(); void StartBotFromBackup(BotBackup backupBot); - BotBackup GetBotBackup(string name); + BotBackup GetBotBackup(string identifier); - ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, - 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, decimal initialTradingAmount); - - ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, - 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, decimal initialTradingAmount); + ITradingBot CreateScalpingBot(TradingBotConfig config); + ITradingBot CreateBacktestScalpingBot(TradingBotConfig config); + ITradingBot CreateFlippingBot(TradingBotConfig config); + ITradingBot CreateBacktestFlippingBot(TradingBotConfig config); IBot CreateSimpleBot(string botName, Workflow workflow); - Task StopBot(string requestName); - Task DeleteBot(string requestName); - Task RestartBot(string requestName); + Task StopBot(string botName); + Task DeleteBot(string botName); + Task RestartBot(string botName); void DeleteBotBackup(string backupBotName); void ToggleIsForWatchingOnly(string botName); } \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/ITradingBot.cs b/src/Managing.Application/Abstractions/ITradingBot.cs index 3bc5ee6..c954780 100644 --- a/src/Managing.Application/Abstractions/ITradingBot.cs +++ b/src/Managing.Application/Abstractions/ITradingBot.cs @@ -1,33 +1,32 @@ -using Managing.Core.FixedSizedQueue; +using Managing.Application.Bots; +using Managing.Core.FixedSizedQueue; +using Managing.Domain.Accounts; using Managing.Domain.Bots; using Managing.Domain.Candles; -using Managing.Domain.MoneyManagements; +using Managing.Domain.Scenarios; using Managing.Domain.Strategies; using Managing.Domain.Strategies.Base; using Managing.Domain.Trades; -using Managing.Domain.Users; using static Managing.Common.Enums; namespace Managing.Application.Abstractions { public interface ITradingBot : IBot { - HashSet Signals { get; set; } - List Positions { get; set; } + TradingBotConfig Config { get; set; } + Account Account { get; set; } + HashSet Strategies { get; set; } FixedSizeQueue OptimizedCandles { get; set; } HashSet Candles { get; set; } - Timeframe Timeframe { get; set; } - HashSet Strategies { get; set; } - Ticker Ticker { get; } - string ScenarioName { get; } - string AccountName { get; } - bool IsForWatchingOnly { get; set; } - MoneyManagement MoneyManagement { get; set; } - BotType BotType { get; set; } + HashSet Signals { get; set; } + List Positions { get; set; } Dictionary WalletBalances { get; set; } Dictionary StrategiesValues { get; set; } - User User { get; set; } - string Identifier { get; set; } + DateTime StartupTime { get; set; } + DateTime PreloadSince { get; set; } + int PreloadedCandlesCount { get; set; } + decimal Fee { get; set; } + Scenario Scenario { get; set; } Task Run(); Task ToggleIsForWatchOnly(); @@ -38,5 +37,6 @@ namespace Managing.Application.Abstractions void LoadScenario(string scenarioName); void UpdateStrategiesValues(); Task LoadAccount(); + Task OpenPositionManually(TradeDirection direction); } } \ No newline at end of file diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs index 9a52523..72fb302 100644 --- a/src/Managing.Application/Backtesting/Backtester.cs +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -1,6 +1,7 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; +using Managing.Application.Bots; using Managing.Core; using Managing.Core.FixedSizedQueue; using Managing.Domain.Accounts; @@ -64,10 +65,26 @@ namespace Managing.Application.Backtesting User user = null, bool isForWatchingOnly = false, bool save = false, - List initialCandles = null) + List? initialCandles = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0) { - var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, isForWatchingOnly, balance); + var config = new TradingBotConfig + { + AccountName = account.Name, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario.Name, + Timeframe = timeframe, + IsForWatchingOnly = isForWatchingOnly, + BotTradingBalance = balance, + BotType = BotType.ScalpingBot, + IsForBacktest = true, + CooldownPeriod = cooldownPeriod, + MaxLossStreak = maxLossStreak + }; + + var scalpingBot = _botFactory.CreateBacktestScalpingBot(config); scalpingBot.LoadScenario(scenario.Name); await scalpingBot.LoadAccount(); var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate); @@ -91,21 +108,6 @@ namespace Managing.Application.Backtesting return result; } - private List GetCandles(Account account, Ticker ticker, Timeframe timeframe, - DateTime startDate, DateTime endDate) - { - List candles; - - // Use specific date range - candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker, - startDate, timeframe, endDate).Result; - - if (candles == null || candles.Count == 0) - throw new Exception($"No candles for {ticker} on {account.Exchange}"); - - return candles; - } - public async Task RunFlippingBotBacktest( Account account, MoneyManagement moneyManagement, @@ -118,10 +120,26 @@ namespace Managing.Application.Backtesting User user = null, bool isForWatchingOnly = false, bool save = false, - List initialCandles = null) + List? initialCandles = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0) { - var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, false, balance); + var config = new TradingBotConfig + { + AccountName = account.Name, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario.Name, + Timeframe = timeframe, + IsForWatchingOnly = isForWatchingOnly, + BotTradingBalance = balance, + BotType = BotType.FlippingBot, + IsForBacktest = true, + CooldownPeriod = cooldownPeriod, + MaxLossStreak = maxLossStreak + }; + + var flippingBot = _botFactory.CreateBacktestFlippingBot(config); flippingBot.LoadScenario(scenario.Name); await flippingBot.LoadAccount(); @@ -146,13 +164,34 @@ namespace Managing.Application.Backtesting return result; } - public async Task RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, + public async Task RunScalpingBotBacktest( + Account account, + MoneyManagement moneyManagement, Scenario scenario, - Timeframe timeframe, List candles, decimal balance, User user = null) + Timeframe timeframe, + List candles, + decimal balance, + User user = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0) { var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); - var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, false, balance); + var config = new TradingBotConfig + { + AccountName = account.Name, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario.Name, + Timeframe = timeframe, + IsForWatchingOnly = false, + BotTradingBalance = balance, + BotType = BotType.ScalpingBot, + IsForBacktest = true, + CooldownPeriod = cooldownPeriod, + MaxLossStreak = maxLossStreak + }; + + var bot = _botFactory.CreateBacktestScalpingBot(config); bot.LoadScenario(scenario.Name); await bot.LoadAccount(); @@ -167,13 +206,34 @@ namespace Managing.Application.Backtesting return result; } - public async Task RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, + public async Task RunFlippingBotBacktest( + Account account, + MoneyManagement moneyManagement, Scenario scenario, - Timeframe timeframe, List candles, decimal balance, User user = null) + Timeframe timeframe, + List candles, + decimal balance, + User user = null, + decimal cooldownPeriod = 1, + int maxLossStreak = 0) { var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); - var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", - timeframe, false, balance); + var config = new TradingBotConfig + { + AccountName = account.Name, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario.Name, + Timeframe = timeframe, + IsForWatchingOnly = false, + BotTradingBalance = balance, + BotType = BotType.FlippingBot, + IsForBacktest = true, + CooldownPeriod = cooldownPeriod, + MaxLossStreak = maxLossStreak + }; + + var bot = _botFactory.CreateBacktestFlippingBot(config); bot.LoadScenario(scenario.Name); await bot.LoadAccount(); @@ -188,6 +248,21 @@ namespace Managing.Application.Backtesting return result; } + private List GetCandles(Account account, Ticker ticker, Timeframe timeframe, + DateTime startDate, DateTime endDate) + { + List candles; + + // Use specific date range + candles = _exchangeService.GetCandlesInflux(account.Exchange, ticker, + startDate, timeframe, endDate).Result; + + if (candles == null || candles.Count == 0) + throw new Exception($"No candles for {ticker} on {account.Exchange}"); + + return candles; + } + private Backtest GetBacktestingResult( Ticker ticker, Scenario scenario, @@ -239,7 +314,7 @@ namespace Managing.Application.Backtesting var score = BacktestScorer.CalculateTotalScore(scoringParams); var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles, - bot.BotType, account.Name) + bot.Config.BotType, account.Name) { FinalPnl = finalPnl, WinRate = winRate, diff --git a/src/Managing.Application/Bots/Base/BotFactory.cs b/src/Managing.Application/Bots/Base/BotFactory.cs index 2cc03c2..fffa32d 100644 --- a/src/Managing.Application/Bots/Base/BotFactory.cs +++ b/src/Managing.Application/Bots/Base/BotFactory.cs @@ -1,7 +1,6 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Domain.Bots; -using Managing.Domain.MoneyManagements; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -38,82 +37,58 @@ 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, decimal initialTradingBalance) + ITradingBot IBotFactory.CreateScalpingBot(TradingBotConfig config) { + config.BotType = BotType.ScalpingBot; return new ScalpingBot( - accountName, - moneyManagement, - name, - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, _botService, - initialTradingBalance, - isForWatchingOnly: isForWatchingOnly); + config); } - ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) + ITradingBot IBotFactory.CreateBacktestScalpingBot(TradingBotConfig config) { + config.BotType = BotType.ScalpingBot; + config.IsForBacktest = true; return new ScalpingBot( - accountName, - moneyManagement, - "BacktestBot", - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, _botService, - initialTradingBalance, - isForBacktest: true, - isForWatchingOnly: isForWatchingOnly); + config); } - public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) + public ITradingBot CreateFlippingBot(TradingBotConfig config) { + config.BotType = BotType.FlippingBot; return new FlippingBot( - accountName, - moneyManagement, - name, - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, _botService, - initialTradingBalance, - isForWatchingOnly: isForWatchingOnly); + config); } - public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) + public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config) { + config.BotType = BotType.FlippingBot; + config.IsForBacktest = true; return new FlippingBot( - accountName, - moneyManagement, - "BacktestBot", - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, _botService, - initialTradingBalance, - isForBacktest: true, - isForWatchingOnly: isForWatchingOnly); + config); } } } diff --git a/src/Managing.Application/Bots/FlippingBot.cs b/src/Managing.Application/Bots/FlippingBot.cs index d740d96..7c2c2b8 100644 --- a/src/Managing.Application/Bots/FlippingBot.cs +++ b/src/Managing.Application/Bots/FlippingBot.cs @@ -1,6 +1,5 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; -using Managing.Domain.MoneyManagements; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -8,39 +7,18 @@ namespace Managing.Application.Bots { public class FlippingBot : TradingBot { - public FlippingBot(string accountName, - MoneyManagement moneyManagement, - string name, - string scenarioName, + public FlippingBot( IExchangeService exchangeService, - Ticker ticker, - ITradingService tradingService, ILogger logger, - Timeframe timeframe, + ITradingService tradingService, IAccountService accountService, IMessengerService messengerService, IBotService botService, - decimal initialTradingBalance, - bool isForBacktest = false, - bool isForWatchingOnly = false) - : base(accountName, - moneyManagement, - name, - ticker, - scenarioName, - exchangeService, - logger, - tradingService, - timeframe, - accountService, - messengerService, - botService, - initialTradingBalance, - isForBacktest, - isForWatchingOnly, - flipPosition: true) + TradingBotConfig config) + : base(exchangeService, logger, tradingService, accountService, messengerService, botService, config) { - BotType = BotType.FlippingBot; + Config.BotType = BotType.FlippingBot; + Config.FlipPosition = true; } public sealed override void Start() diff --git a/src/Managing.Application/Bots/ScalpingBot.cs b/src/Managing.Application/Bots/ScalpingBot.cs index a0611fd..f72e756 100644 --- a/src/Managing.Application/Bots/ScalpingBot.cs +++ b/src/Managing.Application/Bots/ScalpingBot.cs @@ -1,6 +1,5 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; -using Managing.Domain.MoneyManagements; using Microsoft.Extensions.Logging; using static Managing.Common.Enums; @@ -8,38 +7,17 @@ namespace Managing.Application.Bots { public class ScalpingBot : TradingBot { - public ScalpingBot(string accountName, - MoneyManagement moneyManagement, - string name, - string scenarioName, + public ScalpingBot( IExchangeService exchangeService, - Ticker ticker, - ITradingService tradingService, ILogger logger, - Timeframe timeframe, + ITradingService tradingService, IAccountService accountService, IMessengerService messengerService, IBotService botService, - decimal initialTradingBalance, - bool isForBacktest = false, - bool isForWatchingOnly = false) - : base(accountName, - moneyManagement, - name, - ticker, - scenarioName, - exchangeService, - logger, - tradingService, - timeframe, - accountService, - messengerService, - botService, - initialTradingBalance, - isForBacktest, - isForWatchingOnly) + TradingBotConfig config) + : base(exchangeService, logger, tradingService, accountService, messengerService, botService, config) { - BotType = BotType.ScalpingBot; + Config.BotType = BotType.ScalpingBot; } public sealed override void Start() diff --git a/src/Managing.Application/Bots/SimpleBot.cs b/src/Managing.Application/Bots/SimpleBot.cs index a392d25..e2e03a6 100644 --- a/src/Managing.Application/Bots/SimpleBot.cs +++ b/src/Managing.Application/Bots/SimpleBot.cs @@ -45,7 +45,7 @@ namespace Managing.Application.Bots public override void SaveBackup() { var data = JsonConvert.SerializeObject(_workflow); - _botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, data); + _botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, Status, data); } public override void LoadBackup(BotBackup backup) diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index f265309..8af7289 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -28,76 +28,47 @@ public class TradingBot : Bot, ITradingBot private readonly ITradingService TradingService; private readonly IBotService BotService; + public TradingBotConfig Config { get; set; } public Account Account { get; set; } public HashSet Strategies { get; set; } public FixedSizeQueue OptimizedCandles { get; set; } public HashSet Candles { get; set; } public HashSet Signals { get; set; } public List Positions { get; set; } - public Ticker Ticker { get; set; } - public string ScenarioName { get; set; } - public string AccountName { get; set; } - public MoneyManagement MoneyManagement { get; set; } - public Timeframe Timeframe { get; set; } - public bool IsForBacktest { get; set; } - public DateTime PreloadSince { get; set; } - public bool IsForWatchingOnly { get; set; } - public bool FlipPosition { get; set; } - public int PreloadedCandlesCount { get; set; } - public BotType BotType { get; set; } - public decimal Fee { get; set; } - public Scenario Scenario { get; set; } public Dictionary WalletBalances { get; set; } 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 DateTime PreloadSince { get; set; } + public int PreloadedCandlesCount { get; set; } + public decimal Fee { get; set; } + public Scenario Scenario { get; set; } public TradingBot( - string accountName, - MoneyManagement moneyManagement, - string name, - Ticker ticker, - string scenarioName, IExchangeService exchangeService, ILogger logger, ITradingService tradingService, - Timeframe timeframe, IAccountService accountService, IMessengerService messengerService, IBotService botService, - decimal initialTradingBalance, - bool isForBacktest = false, - bool isForWatchingOnly = false, - bool flipPosition = false) - : base(name) + TradingBotConfig config + ) + : base(config.AccountName) { ExchangeService = exchangeService; AccountService = accountService; MessengerService = messengerService; TradingService = tradingService; BotService = botService; + Logger = logger; - if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) + if (config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) { throw new ArgumentException( $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}", - nameof(initialTradingBalance)); + nameof(config.BotTradingBalance)); } - IsForWatchingOnly = isForWatchingOnly; - FlipPosition = flipPosition; - AccountName = accountName; - MoneyManagement = moneyManagement; - Ticker = ticker; - ScenarioName = scenarioName; - Timeframe = timeframe; - IsForBacktest = isForBacktest; - Logger = logger; - BotTradingBalance = initialTradingBalance; + Config = config; Strategies = new HashSet(); Signals = new HashSet(); @@ -107,10 +78,10 @@ public class TradingBot : Bot, ITradingBot WalletBalances = new Dictionary(); StrategiesValues = new Dictionary(); - if (!isForBacktest) + if (!Config.IsForBacktest) { - Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe); - PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe); + Interval = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe); + PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(Config.Timeframe); } } @@ -120,9 +91,9 @@ public class TradingBot : Bot, ITradingBot // Load account synchronously await LoadAccount(); - if (!IsForBacktest) + if (!Config.IsForBacktest) { - LoadScenario(ScenarioName); + LoadScenario(Config.ScenarioName); await PreloadCandles(); await CancelAllOrders(); @@ -146,10 +117,10 @@ public class TradingBot : Bot, ITradingBot public async Task LoadAccount() { - var account = await AccountService.GetAccount(AccountName, false, false); + var account = await AccountService.GetAccount(Config.AccountName, false, false); if (account == null) { - Logger.LogWarning($"No account found for this {AccountName}"); + Logger.LogWarning($"No account found for this {Config.AccountName}"); Stop(); } else @@ -185,7 +156,7 @@ public class TradingBot : Bot, ITradingBot public async Task Run() { - if (!IsForBacktest) + if (!Config.IsForBacktest) { // Check broker balance before running var balance = await ExchangeService.GetBalance(Account, false); @@ -200,25 +171,25 @@ public class TradingBot : Bot, ITradingBot Logger.LogInformation($"____________________{Name}____________________"); Logger.LogInformation( - $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}"); + $"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {Config.BotType} - Ticker : {Config.Ticker}"); } var previousLastCandle = OptimizedCandles.LastOrDefault(); - if (!IsForBacktest) + if (!Config.IsForBacktest) await UpdateCandles(); var currentLastCandle = OptimizedCandles.LastOrDefault(); - if (currentLastCandle != previousLastCandle || IsForBacktest) + if (currentLastCandle != previousLastCandle || Config.IsForBacktest) await UpdateSignals(OptimizedCandles); else - Logger.LogInformation($"No need to update signals for {Ticker}"); + Logger.LogInformation($"No need to update signals for {Config.Ticker}"); - if (!IsForWatchingOnly) + if (!Config.IsForWatchingOnly) await ManagePositions(); - if (!IsForBacktest) + if (!Config.IsForBacktest) { SaveBackup(); UpdateStrategiesValues(); @@ -248,7 +219,8 @@ public class TradingBot : Bot, ITradingBot if (OptimizedCandles.Any()) return; - var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe); + var candles = + await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, PreloadSince, Config.Timeframe); foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime())) { @@ -272,25 +244,22 @@ public class TradingBot : Bot, ITradingBot await AddSignal(signal); } - private async Task AddSignal(Signal signal) { - // if (!IsForBacktest) - // TradingService.InsertSignal(signal); - - if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest)) + if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest)) signal.Status = SignalStatus.Expired; Signals.Add(signal); - var signalText = $"{ScenarioName} trigger a signal. Signal told you " + - $"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}"; + var signalText = $"{Config.ScenarioName} trigger a signal. Signal told you " + + $"to {signal.Direction} {Config.Ticker} on {Config.Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}"; Logger.LogInformation(signalText); - if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0) + if (Config.IsForWatchingOnly && !Config.IsForBacktest && ExecutionCount > 0) { - await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe); + await MessengerService.SendSignal(signalText, Account.Exchange, Config.Ticker, signal.Direction, + Config.Timeframe); } } @@ -300,7 +269,8 @@ public class TradingBot : Bot, ITradingBot return; var lastCandle = OptimizedCandles.Last(); - var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe); + var newCandle = + await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, lastCandle.Date, Config.Timeframe); foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime())) { @@ -342,7 +312,7 @@ public class TradingBot : Bot, ITradingBot if (WalletBalances.Count == 0) { // WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest); - WalletBalances[date] = BotTradingBalance; + WalletBalances[date] = Config.BotTradingBalance; return; } @@ -353,24 +323,23 @@ public class TradingBot : Bot, ITradingBot } } - private async Task UpdatePosition(Signal signal, Position positionForSignal) { try { Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}"); - var position = IsForBacktest + var position = Config.IsForBacktest ? positionForSignal : TradingService.GetPositionByIdentifier(positionForSignal.Identifier); - var positionsExchange = IsForBacktest + var positionsExchange = Config.IsForBacktest ? new List { position } : await TradingService.GetBrokerPositions(Account); - if (!IsForBacktest) + if (!Config.IsForBacktest) { - var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Ticker); + var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Config.Ticker); if (brokerPosition != null) { UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized); @@ -395,7 +364,7 @@ public class TradingBot : Bot, ITradingBot if (position.Status == PositionStatus.New) { - var orders = await ExchangeService.GetOpenOrders(Account, Ticker); + var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker); if (orders.Any()) { await LogInformation( @@ -420,9 +389,9 @@ public class TradingBot : Bot, ITradingBot // Position might be partially filled, meaning that TPSL havent been sended yet // But the position might already been closed by the exchange so we have to check should be closed - var lastCandle = IsForBacktest + var lastCandle = Config.IsForBacktest ? OptimizedCandles.Last() - : ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow); + : ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow); if (positionForSignal.OriginDirection == TradeDirection.Long) { @@ -512,7 +481,6 @@ public class TradingBot : Bot, ITradingBot } } - private async Task OpenPosition(Signal signal) { // Check if a position is already open @@ -521,9 +489,9 @@ public class TradingBot : Bot, ITradingBot var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled && p.SignalIdentifier != signal.Identifier); - var lastPrice = IsForBacktest + var lastPrice = Config.IsForBacktest ? OptimizedCandles.Last().Close - : ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow); + : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow); // If position open if (openedPosition != null) @@ -542,7 +510,7 @@ public class TradingBot : Bot, ITradingBot { // An operation is already open for the opposite direction // ==> Flip the position - if (FlipPosition) + if (Config.FlipPosition) { await LogInformation("Try to flip the position because of an opposite direction signal"); await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); @@ -561,10 +529,8 @@ public class TradingBot : Bot, ITradingBot } else { - if (!CanOpenPosition(signal)) + if (!(await CanOpenPosition(signal))) { - await LogInformation( - "Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position"); SetSignalStatus(signal.Identifier, SignalStatus.Expired); return; } @@ -575,15 +541,15 @@ public class TradingBot : Bot, ITradingBot try { var command = new OpenPositionRequest( - AccountName, - MoneyManagement, + Config.AccountName, + Config.MoneyManagement, signal.Direction, - Ticker, + Config.Ticker, PositionInitiator.Bot, signal.Date, User, - BotTradingBalance, - IsForBacktest, + Config.BotTradingBalance, + Config.IsForBacktest, lastPrice, signalIdentifier: signal.Identifier); @@ -598,7 +564,7 @@ public class TradingBot : Bot, ITradingBot { SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen); - if (!IsForBacktest) + if (!Config.IsForBacktest) { await MessengerService.SendPosition(position); } @@ -622,25 +588,142 @@ public class TradingBot : Bot, ITradingBot } } - private bool CanOpenPosition(Signal signal) + private async Task CanOpenPosition(Signal signal) { - if (!IsForBacktest && ExecutionCount < 1) + // Early return if we're in backtest mode and haven't executed yet + if (!Config.IsForBacktest && ExecutionCount < 1) + { + await LogInformation("Cannot open position: Bot hasn't executed yet"); return false; + } - if (Positions.Count == 0) + // Check if we're in backtest mode + if (Config.IsForBacktest) + { + return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal); + } + + // Check broker positions for live trading + var canOpenPosition = await CheckBrokerPositions(); + if (!canOpenPosition) + { + return false; + } + + // Check cooldown period and loss streak + return await CheckCooldownPeriod(signal) && await CheckLossStreak(signal); + } + + private async Task CheckLossStreak(Signal signal) + { + // If MaxLossStreak is 0, there's no limit + if (Config.MaxLossStreak <= 0) + { return true; + } + // Get the last N finished positions regardless of direction + var recentPositions = Positions + .Where(p => p.IsFinished()) + .OrderByDescending(p => p.Open.Date) + .Take(Config.MaxLossStreak) + .ToList(); + + // If we don't have enough positions to form a streak, we can open + if (recentPositions.Count < Config.MaxLossStreak) + { + return true; + } + + // Check if all recent positions were losses + var allLosses = recentPositions.All(p => p.ProfitAndLoss?.Realized < 0); + if (!allLosses) + { + return true; + } + + // If we have a loss streak, check if the last position was in the same direction as the signal + var lastPosition = recentPositions.First(); + if (lastPosition.OriginDirection == signal.Direction) + { + await LogWarning($"Cannot open position: Max loss streak ({Config.MaxLossStreak}) reached. " + + $"Last {recentPositions.Count} trades were losses. " + + $"Last position was {lastPosition.OriginDirection}, waiting for a signal in the opposite direction."); + return false; + } + + return true; + } + + private async Task CheckBrokerPositions() + { + try + { + var positions = await ExchangeService.GetBrokerPositions(Account); + if (!positions.Any(p => p.Ticker == Config.Ticker)) + { + return true; + } + + // Handle existing position on broker + var previousPosition = Positions.LastOrDefault(); + var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker); + + var reason = $"Cannot open position. There is already a position open for {Config.Ticker} on the broker."; + + if (previousPosition != null && orders.Count >= 2) + { + await SetPositionStatus(previousPosition.SignalIdentifier, PositionStatus.Filled); + } + else + { + reason += + " Position open on broker but not enough orders or no previous position internally saved by the bot"; + } + + await LogWarning(reason); + return false; + } + catch (Exception ex) + { + await LogWarning($"Error checking broker positions: {ex.Message}"); + return false; + } + } + + private async Task CheckCooldownPeriod(Signal signal) + { var lastPosition = Positions.LastOrDefault(p => p.IsFinished() && p.SignalIdentifier != signal.Identifier && p.OriginDirection == signal.Direction); if (lastPosition == null) + { return true; + } + + var cooldownCandle = OptimizedCandles.TakeLast((int)Config.CooldownPeriod).FirstOrDefault(); + if (cooldownCandle == null) + { + await LogWarning("Cannot check cooldown period: Not enough candles available"); + return false; + } - var tenCandleAgo = OptimizedCandles.TakeLast(10).First(); var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier); + if (positionSignal == null) + { + await LogWarning($"Cannot find signal for last position {lastPosition.Identifier}"); + return false; + } - return positionSignal.Date < tenCandleAgo.Date; + var canOpenPosition = positionSignal.Date < cooldownCandle.Date; + if (!canOpenPosition) + { + await LogInformation( + $"Position cannot be opened: Cooldown period not elapsed. Last position date: {positionSignal.Date}, Cooldown candle date: {cooldownCandle.Date}"); + } + + return canOpenPosition; } public async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice, @@ -654,18 +737,18 @@ public class TradingBot : Bot, ITradingBot } await LogInformation( - $"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " + + $"Trying to close trade {Config.Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " + $"- Closing Position : {tradeClosingPosition}"); // Get status of position before closing it. The position might be already close by the exchange - if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0) + if (!Config.IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Config.Ticker) == 0) { Logger.LogInformation($"Trade already close on exchange"); await HandleClosedPosition(position); } else { - var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest); + var command = new ClosePositionCommand(position, lastPrice, isForBacktest: Config.IsForBacktest); try { var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService) @@ -710,12 +793,12 @@ public class TradingBot : Bot, ITradingBot if (position.ProfitAndLoss != null) { // Add PnL (could be positive or negative) - BotTradingBalance += position.ProfitAndLoss.Realized; + Config.BotTradingBalance += position.ProfitAndLoss.Realized; // Subtract fees - BotTradingBalance -= GetPositionFees(position); + Config.BotTradingBalance -= GetPositionFees(position); - Logger.LogInformation($"Updated bot trading balance to: {BotTradingBalance}"); + Logger.LogInformation($"Updated bot trading balance to: {Config.BotTradingBalance}"); } } else @@ -723,7 +806,7 @@ public class TradingBot : Bot, ITradingBot await LogWarning("Weird things happen - Trying to update position status, but no position found"); } - if (!IsForBacktest) + if (!Config.IsForBacktest) { await MessengerService.SendClosingPosition(position); } @@ -733,15 +816,15 @@ public class TradingBot : Bot, ITradingBot private async Task CancelAllOrders() { - if (!IsForBacktest && !IsForWatchingOnly) + if (!Config.IsForBacktest && !Config.IsForWatchingOnly) { try { - var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker); + var openOrders = await ExchangeService.GetOpenOrders(Account, Config.Ticker); if (openOrders.Any()) { var openPositions = (await ExchangeService.GetBrokerPositions(Account)) - .Where(p => p.Ticker == Ticker); + .Where(p => p.Ticker == Config.Ticker); var cancelClose = openPositions.Any(); if (cancelClose) @@ -750,15 +833,15 @@ public class TradingBot : Bot, ITradingBot } else { - Logger.LogInformation($"Canceling all orders for {Ticker}"); - await ExchangeService.CancelOrder(Account, Ticker); - var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker); - Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}"); + Logger.LogInformation($"Canceling all orders for {Config.Ticker}"); + await ExchangeService.CancelOrder(Account, Config.Ticker); + var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Config.Ticker); + Logger.LogInformation($"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}"); } } else { - Logger.LogInformation($"No need to cancel orders for {Ticker}"); + Logger.LogInformation($"No need to cancel orders for {Config.Ticker}"); } } catch (Exception ex) @@ -771,10 +854,11 @@ public class TradingBot : Bot, ITradingBot private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus) { - if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus)) + var position = Positions.First(p => p.SignalIdentifier == signalIdentifier); + if (!position.Status.Equals(positionStatus)) { - await LogInformation($"Position {signalIdentifier} is now {positionStatus}"); Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus; + await LogInformation($"Position {signalIdentifier} new status {position.Status} => {positionStatus}"); } SetSignalStatus(signalIdentifier, @@ -864,8 +948,8 @@ public class TradingBot : Bot, ITradingBot public async Task ToggleIsForWatchOnly() { - IsForWatchingOnly = (!IsForWatchingOnly); - await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}"); + Config.IsForWatchingOnly = !Config.IsForWatchingOnly; + await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {Config.IsForWatchingOnly}"); } private async Task LogInformation(string message) @@ -883,7 +967,7 @@ public class TradingBot : Bot, ITradingBot private async Task SendTradeMessage(string message, bool isBadBehavior = false) { - if (!IsForBacktest) + if (!Config.IsForBacktest) { await MessengerService.SendTradeMessage(message, isBadBehavior); } @@ -894,43 +978,47 @@ public class TradingBot : Bot, ITradingBot var data = new TradingBotBackup { Name = Name, - BotType = BotType, + BotType = Config.BotType, Signals = Signals, Positions = Positions, - Timeframe = Timeframe, - Ticker = Ticker, - ScenarioName = ScenarioName, - AccountName = AccountName, - IsForWatchingOnly = IsForWatchingOnly, + Timeframe = Config.Timeframe, + Ticker = Config.Ticker, + ScenarioName = Config.ScenarioName, + AccountName = Config.AccountName, + IsForWatchingOnly = Config.IsForWatchingOnly, WalletBalances = WalletBalances, - MoneyManagement = MoneyManagement, - BotTradingBalance = BotTradingBalance, + MoneyManagement = Config.MoneyManagement, + BotTradingBalance = Config.BotTradingBalance, StartupTime = StartupTime, + CooldownPeriod = Config.CooldownPeriod, }; - BotService.SaveOrUpdateBotBackup(User, Identifier, BotType, JsonConvert.SerializeObject(data)); + BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data)); } public override void LoadBackup(BotBackup backup) { var data = JsonConvert.DeserializeObject(backup.Data); + Config = new TradingBotConfig + { + AccountName = data.AccountName, + MoneyManagement = data.MoneyManagement, + Ticker = data.Ticker, + ScenarioName = data.ScenarioName, + Timeframe = data.Timeframe, + IsForBacktest = false, // Always false when loading from backup + IsForWatchingOnly = data.IsForWatchingOnly, + BotTradingBalance = data.BotTradingBalance, + BotType = data.BotType, + CooldownPeriod = data.CooldownPeriod, + }; + Signals = data.Signals; Positions = data.Positions; WalletBalances = data.WalletBalances; - // MoneyManagement = data.MoneyManagement; => loaded from database - Timeframe = data.Timeframe; - Ticker = data.Ticker; - ScenarioName = data.ScenarioName; - AccountName = data.AccountName; - IsForWatchingOnly = data.IsForWatchingOnly; - BotTradingBalance = data.BotTradingBalance; + PreloadSince = data.StartupTime; Identifier = backup.Identifier; User = backup.User; - - // Restore the startup time if it was previously saved - if (data.StartupTime != DateTime.MinValue) - { - StartupTime = data.StartupTime; - } + Status = backup.LastStatus; } /// @@ -949,7 +1037,8 @@ public class TradingBot : Bot, ITradingBot } // Create a fake signal for manual position opening - var signal = new Signal(Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, TradingExchanges.GmxV2, + var signal = new Signal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, + TradingExchanges.GmxV2, StrategyType.Stc, SignalType.Signal); signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct signal.User = Account.User; // Assign user @@ -989,4 +1078,5 @@ public class TradingBotBackup public MoneyManagement MoneyManagement { get; set; } public DateTime StartupTime { get; set; } public decimal BotTradingBalance { get; set; } + public decimal CooldownPeriod { get; set; } } \ No newline at end of file diff --git a/src/Managing.Application/Bots/TradingBotConfig.cs b/src/Managing.Application/Bots/TradingBotConfig.cs new file mode 100644 index 0000000..8c6bb95 --- /dev/null +++ b/src/Managing.Application/Bots/TradingBotConfig.cs @@ -0,0 +1,20 @@ +using Managing.Domain.MoneyManagements; +using static Managing.Common.Enums; + +namespace Managing.Application.Bots; + +public class TradingBotConfig +{ + public string AccountName { get; set; } + public MoneyManagement MoneyManagement { get; set; } + public Ticker Ticker { get; set; } + public string ScenarioName { get; set; } + public Timeframe Timeframe { get; set; } + public bool IsForBacktest { get; set; } + public bool IsForWatchingOnly { get; set; } + public bool FlipPosition { get; set; } + public BotType BotType { get; set; } + public decimal BotTradingBalance { get; set; } + public decimal CooldownPeriod { get; set; } = 1; + public int MaxLossStreak { get; set; } = 0; // 0 means no limit +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/BotService.cs b/src/Managing.Application/ManageBot/BotService.cs index 6590e18..e28c6bb 100644 --- a/src/Managing.Application/ManageBot/BotService.cs +++ b/src/Managing.Application/ManageBot/BotService.cs @@ -3,13 +3,13 @@ using Managing.Application.Abstractions; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Application.Bots; -using Managing.Common; using Managing.Domain.Bots; using Managing.Domain.MoneyManagements; using Managing.Domain.Users; using Managing.Domain.Workflows; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using static Managing.Common.Enums; namespace Managing.Application.ManageBot { @@ -22,13 +22,14 @@ namespace Managing.Application.ManageBot private readonly ILogger _tradingBotLogger; private readonly ITradingService _tradingService; private readonly IMoneyManagementService _moneyManagementService; + private readonly IUserService _userService; private ConcurrentDictionary _botTasks = new ConcurrentDictionary(); public BotService(IBotRepository botRepository, IExchangeService exchangeService, IMessengerService messengerService, IAccountService accountService, ILogger tradingBotLogger, - ITradingService tradingService, IMoneyManagementService moneyManagementService) + ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService) { _botRepository = botRepository; _exchangeService = exchangeService; @@ -37,11 +38,7 @@ namespace Managing.Application.ManageBot _tradingBotLogger = tradingBotLogger; _tradingService = tradingService; _moneyManagementService = moneyManagementService; - } - - public async void SaveOrUpdateBotBackup(BotBackup botBackup) - { - await _botRepository.InsertBotAsync(botBackup); + _userService = userService; } public BotBackup GetBotBackup(string identifier) @@ -49,12 +46,13 @@ namespace Managing.Application.ManageBot return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier); } - public void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data) + public void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data) { var backup = GetBotBackup(identifier); if (backup != null) { + backup.LastStatus = status; backup.Data = data; _botRepository.UpdateBackupBot(backup); } @@ -62,6 +60,7 @@ namespace Managing.Application.ManageBot { var botBackup = new BotBackup { + LastStatus = status, User = user, Identifier = identifier, BotType = botType, @@ -127,7 +126,7 @@ namespace Managing.Application.ManageBot // null); // Assuming null is an acceptable parameter for workflow // botTask = Task.Run(() => ((IBot)bot).Start()); // break; - case Enums.BotType.ScalpingBot: + case BotType.ScalpingBot: var scalpingBotData = JsonConvert.DeserializeObject(backupBot.Data); var scalpingMoneyManagement = _moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result; @@ -142,7 +141,7 @@ namespace Managing.Application.ManageBot scalpingBotData.BotTradingBalance); botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot)); break; - case Enums.BotType.FlippingBot: + case BotType.FlippingBot: var flippingBotData = JsonConvert.DeserializeObject(backupBot.Data); var flippingMoneyManagement = _moneyManagementService.GetMoneyMangement(flippingBotData.MoneyManagement.Name).Result; @@ -166,9 +165,12 @@ namespace Managing.Application.ManageBot } } - private static Action InitBot(ITradingBot bot, BotBackup backupBot) + private Action InitBot(ITradingBot bot, BotBackup backupBot) { bot.Start(); + + var user = _userService.GetUser(backupBot.User.Name); + backupBot.User = user; bot.LoadBackup(backupBot); return () => { }; } @@ -178,9 +180,9 @@ namespace Managing.Application.ManageBot return new SimpleBot(botName, _tradingBotLogger, workflow, this); } - public async Task StopBot(string botName) + public async Task StopBot(string identifier) { - if (_botTasks.TryGetValue(botName, out var botWrapper)) + if (_botTasks.TryGetValue(identifier, out var botWrapper)) { if (botWrapper.BotInstance is IBot bot) { @@ -190,12 +192,12 @@ namespace Managing.Application.ManageBot } } - return Enums.BotStatus.Down.ToString(); + return BotStatus.Down.ToString(); } - public async Task DeleteBot(string botName) + public async Task DeleteBot(string identifier) { - if (_botTasks.TryRemove(botName, out var botWrapper)) + if (_botTasks.TryRemove(identifier, out var botWrapper)) { try { @@ -205,7 +207,7 @@ namespace Managing.Application.ManageBot bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods } - await _botRepository.DeleteBotBackup(botName); + await _botRepository.DeleteBotBackup(identifier); return true; } catch (Exception e) @@ -218,9 +220,9 @@ namespace Managing.Application.ManageBot return false; } - public Task RestartBot(string botName) + public Task RestartBot(string identifier) { - if (_botTasks.TryGetValue(botName, out var botWrapper)) + if (_botTasks.TryGetValue(identifier, out var botWrapper)) { if (botWrapper.BotInstance is IBot bot) { @@ -229,17 +231,17 @@ namespace Managing.Application.ManageBot } } - return Task.FromResult(Enums.BotStatus.Down.ToString()); + return Task.FromResult(BotStatus.Down.ToString()); } - public void DeleteBotBackup(string backupBotName) + public void DeleteBotBackup(string identifier) { - _botRepository.DeleteBotBackup(backupBotName); + _botRepository.DeleteBotBackup(identifier); } - public void ToggleIsForWatchingOnly(string botName) + public void ToggleIsForWatchingOnly(string identifier) { - if (_botTasks.TryGetValue(botName, out var botTaskWrapper) && + if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) && botTaskWrapper.BotInstance is ITradingBot tradingBot) { tradingBot.ToggleIsForWatchOnly().Wait(); @@ -247,89 +249,159 @@ namespace Managing.Application.ManageBot } public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { + var config = new TradingBotConfig + { + AccountName = accountName, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario, + Timeframe = interval, + IsForWatchingOnly = isForWatchingOnly, + BotTradingBalance = initialTradingBalance, + BotType = BotType.ScalpingBot + }; + return new ScalpingBot( - accountName, - moneyManagement, - name, - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, this, - initialTradingBalance, - isForWatchingOnly: isForWatchingOnly); + config); } public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { + var config = new TradingBotConfig + { + AccountName = accountName, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario, + Timeframe = interval, + IsForWatchingOnly = isForWatchingOnly, + BotTradingBalance = initialTradingBalance, + BotType = BotType.ScalpingBot, + IsForBacktest = true + }; + return new ScalpingBot( - accountName, - moneyManagement, - "BacktestBot", - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, this, - initialTradingBalance, - isForBacktest: true, - isForWatchingOnly: isForWatchingOnly); + config); } public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { + var config = new TradingBotConfig + { + AccountName = accountName, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario, + Timeframe = interval, + IsForWatchingOnly = isForWatchingOnly, + BotTradingBalance = initialTradingBalance, + BotType = BotType.FlippingBot + }; + return new FlippingBot( - accountName, - moneyManagement, - name, - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, this, - initialTradingBalance, - isForWatchingOnly: isForWatchingOnly); + config); } public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, - Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly, + Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance) { + var config = new TradingBotConfig + { + AccountName = accountName, + MoneyManagement = moneyManagement, + Ticker = ticker, + ScenarioName = scenario, + Timeframe = interval, + IsForWatchingOnly = isForWatchingOnly, + BotTradingBalance = initialTradingBalance, + BotType = BotType.FlippingBot, + IsForBacktest = true + }; + return new FlippingBot( - accountName, - moneyManagement, - "BacktestBot", - scenario, _exchangeService, - ticker, - _tradingService, _tradingBotLogger, - interval, + _tradingService, _accountService, _messengerService, this, - initialTradingBalance, - isForBacktest: true, - isForWatchingOnly: isForWatchingOnly); + config); + } + + public ITradingBot CreateScalpingBot(TradingBotConfig config) + { + return new ScalpingBot( + _exchangeService, + _tradingBotLogger, + _tradingService, + _accountService, + _messengerService, + this, + config); + } + + public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config) + { + config.IsForBacktest = true; + return new ScalpingBot( + _exchangeService, + _tradingBotLogger, + _tradingService, + _accountService, + _messengerService, + this, + config); + } + + public ITradingBot CreateFlippingBot(TradingBotConfig config) + { + return new FlippingBot( + _exchangeService, + _tradingBotLogger, + _tradingService, + _accountService, + _messengerService, + this, + config); + } + + public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config) + { + config.IsForBacktest = true; + return new FlippingBot( + _exchangeService, + _tradingBotLogger, + _tradingService, + _accountService, + _messengerService, + this, + config); } } } \ 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 a536997..3e1d856 100644 --- a/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs +++ b/src/Managing.Application/ManageBot/Commands/StartBotCommand.cs @@ -1,44 +1,20 @@ -using Managing.Domain.Users; +using Managing.Application.Bots; +using Managing.Domain.Users; using MediatR; -using static Managing.Common.Enums; namespace Managing.Application.ManageBot.Commands { public class StartBotCommand : IRequest { public string Name { get; } - public BotType BotType { get; } - public Ticker Ticker { get; internal set; } - public Timeframe Timeframe { get; internal set; } - public bool IsForWatchingOnly { get; internal set; } - public string Scenario { get; internal set; } - 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 TradingBotConfig Config { get; } + public User User { get; } - public StartBotCommand(BotType botType, - string name, - Ticker ticker, - string scenario, - Timeframe timeframe, - string accountName, - string moneyManagementName, - User user, - bool isForWatchingOnly = false, - decimal initialTradingBalance = 0) + public StartBotCommand(TradingBotConfig config, string name, User user) { - BotType = botType; + Config = config; Name = name; - Scenario = scenario; - Ticker = ticker; - Timeframe = timeframe; - IsForWatchingOnly = isForWatchingOnly; - 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/GetUserStrategiesCommandHandler.cs b/src/Managing.Application/ManageBot/GetUserStrategiesCommandHandler.cs index a4f2796..0fbbc01 100644 --- a/src/Managing.Application/ManageBot/GetUserStrategiesCommandHandler.cs +++ b/src/Managing.Application/ManageBot/GetUserStrategiesCommandHandler.cs @@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot { var allActiveBots = _botService.GetActiveBots(); var userBots = allActiveBots - .Where(bot => bot.User != null && bot.User.Name == request.UserName) + .Where(bot => bot.User != null && bot.User.AgentName == request.UserName) .ToList(); return Task.FromResult(userBots); diff --git a/src/Managing.Application/ManageBot/GetUserStrategyCommandHandler.cs b/src/Managing.Application/ManageBot/GetUserStrategyCommandHandler.cs index 640ad86..1afefba 100644 --- a/src/Managing.Application/ManageBot/GetUserStrategyCommandHandler.cs +++ b/src/Managing.Application/ManageBot/GetUserStrategyCommandHandler.cs @@ -19,15 +19,14 @@ namespace Managing.Application.ManageBot public Task Handle(GetUserStrategyCommand request, CancellationToken cancellationToken) { var allActiveBots = _botService.GetActiveBots(); - + // Find the specific strategy that matches both user and strategy name var strategy = allActiveBots - .FirstOrDefault(bot => - bot.User != null && - bot.User.Name == request.AgentName && - bot.Name == request.StrategyName); + .FirstOrDefault(bot => + bot.User.AgentName == request.AgentName && + bot.Identifier == request.StrategyName); return Task.FromResult(strategy); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs index ba88a1a..fd88313 100644 --- a/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/LoadBackupBotCommandHandler.cs @@ -31,6 +31,13 @@ public class LoadBackupBotCommandHandler : IRequestHandler b.Identifier == backupBot.Identifier); if (activeBot == null) diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs index 3daa702..7b4349d 100644 --- a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -29,38 +29,38 @@ namespace Managing.Application.ManageBot { BotStatus botStatus = BotStatus.Down; - var account = await _accountService.GetAccount(request.AccountName, true, true); + var account = await _accountService.GetAccount(request.Config.AccountName, true, true); if (account == null) { - throw new Exception($"Account {request.AccountName} not found"); + throw new Exception($"Account {request.Config.AccountName} not found"); } var usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString()); - if (usdcBalance == null || usdcBalance.Value < request.InitialTradingBalance) + if (usdcBalance == null || usdcBalance.Value < request.Config.BotTradingBalance) { - throw new Exception($"Account {request.AccountName} has no USDC balance or not enough balance"); + throw new Exception($"Account {request.Config.AccountName} has no USDC balance or not enough balance"); } - var moneyManagement = - await _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName); - switch (request.BotType) + // Ensure cooldown period is set + if (request.Config.CooldownPeriod <= 0) + { + request.Config.CooldownPeriod = 1; // Default to 1 minute if not set + } + + switch (request.Config.BotType) { case BotType.SimpleBot: var bot = _botFactory.CreateSimpleBot(request.Name, null); _botService.AddSimpleBotToCache(bot); return bot.GetStatus(); case BotType.ScalpingBot: - var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, - request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly, - request.InitialTradingBalance); + var sBot = _botFactory.CreateScalpingBot(request.Config); _botService.AddTradingBotToCache(sBot); return sBot.GetStatus(); case BotType.FlippingBot: - var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, - request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly, - request.InitialTradingBalance); + var fBot = _botFactory.CreateFlippingBot(request.Config); _botService.AddTradingBotToCache(fBot); return fBot.GetStatus(); } diff --git a/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs b/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs index a213c18..4b25eb9 100644 --- a/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs +++ b/src/Managing.Application/ManageBot/ToggleIsForWatchingCommandHandler.cs @@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot { _botService.ToggleIsForWatchingOnly(request.Name); var bot = _botService.GetActiveBots().FirstOrDefault(b => b.Name == request.Name); - return Task.FromResult(bot?.IsForWatchingOnly.ToString()); + return Task.FromResult(bot?.Config.IsForWatchingOnly.ToString()); } } } \ No newline at end of file diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 2b9a0ce..6314e42 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -24,7 +24,7 @@ public class UserService : IUserService "0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3", // Local optiflex "0x932167388dD9aad41149b3cA23eBD489E2E2DD78", // Embedded wallet "0x66CB57Fe3f53cE57376421106dFDa2D39186cBd0", // Embedded wallet optiflex - "0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain + "0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain "0xc8bC497534d0A43bAb2BBA9BA94d46D9Ddfaea6B" // DevCowchain2 ]; @@ -47,7 +47,8 @@ public class UserService : IUserService if (!message.Equals("KaigenTeamXCowchain")) { _logger.LogWarning($"Message {message} not starting with KaigenTeamXCowchain"); - throw new Exception($"Message not good : {message} - Address : {address} - User : {name} - Signature : {signature}"); + throw new Exception( + $"Message not good : {message} - Address : {address} - User : {name} - Signature : {signature}"); } // if (!authorizedAddresses.Contains(recoveredAddress)) @@ -106,11 +107,19 @@ public class UserService : IUserService { account }; + + // Update user with the new account + await _userRepository.UpdateUser(user); } return user; } + public User GetUser(string name) + { + return _userRepository.GetUserByNameAsync(name).Result; + } + public async Task GetUserByAddressAsync(string address) { var account = await _accountService.GetAccountByKey(address, true, false); @@ -118,4 +127,18 @@ public class UserService : IUserService user.Accounts = _accountService.GetAccountsByUser(user).ToList(); return user; } -} + + public async Task UpdateAgentName(User user, string agentName) + { + // Check if agent name is already used + var existingUser = await _userRepository.GetUserByAgentNameAsync(agentName); + if (existingUser != null) + { + throw new Exception("Agent name already used"); + } + + user.AgentName = agentName; + await _userRepository.UpdateUser(user); + return user; + } +} \ No newline at end of file diff --git a/src/Managing.Bootstrap/ApiBootstrap.cs b/src/Managing.Bootstrap/ApiBootstrap.cs index 69bd8fb..5f82c2c 100644 --- a/src/Managing.Bootstrap/ApiBootstrap.cs +++ b/src/Managing.Bootstrap/ApiBootstrap.cs @@ -115,6 +115,7 @@ public static class ApiBootstrap services.AddTransient(); services.AddTransient(); + // Cache services.AddDistributedMemoryCache(); services.AddTransient(); @@ -135,7 +136,7 @@ public static class ApiBootstrap services.AddSingleton(); services.AddSingleton(); services.AddTransient(); - + // Web3Proxy Configuration services.Configure(configuration.GetSection("Web3Proxy")); services.AddTransient(); diff --git a/src/Managing.Bootstrap/WorkersBootstrap.cs b/src/Managing.Bootstrap/WorkersBootstrap.cs index 63c2b30..f226b64 100644 --- a/src/Managing.Bootstrap/WorkersBootstrap.cs +++ b/src/Managing.Bootstrap/WorkersBootstrap.cs @@ -14,6 +14,7 @@ using Managing.Application.Scenarios; using Managing.Application.Shared; using Managing.Application.Trading; using Managing.Application.Trading.Commands; +using Managing.Application.Users; using Managing.Application.Workers; using Managing.Application.Workers.Abstractions; using Managing.Domain.Trades; @@ -52,6 +53,7 @@ public static class WorkersBootstrap private static IServiceCollection AddApplication(this IServiceCollection services) { + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -102,6 +104,7 @@ public static class WorkersBootstrap services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Cache services.AddDistributedMemoryCache(); diff --git a/src/Managing.Domain/Bots/BotBackup.cs b/src/Managing.Domain/Bots/BotBackup.cs index 63313b3..8e84300 100644 --- a/src/Managing.Domain/Bots/BotBackup.cs +++ b/src/Managing.Domain/Bots/BotBackup.cs @@ -9,4 +9,5 @@ public class BotBackup public string Identifier { get; set; } public User User { get; set; } public string Data { get; set; } + public BotStatus LastStatus { get; set; } } \ No newline at end of file diff --git a/src/Managing.Domain/Bots/IBot.cs b/src/Managing.Domain/Bots/IBot.cs index 679efb0..d821d0d 100644 --- a/src/Managing.Domain/Bots/IBot.cs +++ b/src/Managing.Domain/Bots/IBot.cs @@ -1,7 +1,10 @@ -namespace Managing.Domain.Bots +using Managing.Domain.Users; + +namespace Managing.Domain.Bots { public interface IBot { + User User { get; set; } string Name { get; set; } void Start(); void Stop(); diff --git a/src/Managing.Domain/Users/User.cs b/src/Managing.Domain/Users/User.cs index e27bb5c..9d01d24 100644 --- a/src/Managing.Domain/Users/User.cs +++ b/src/Managing.Domain/Users/User.cs @@ -6,4 +6,5 @@ public class User { public string Name { get; set; } public List Accounts { get; set; } + public string AgentName { get; set; } } diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs index 51ad164..f92328c 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/BotDto.cs @@ -12,4 +12,5 @@ public class BotDto : Document public BotType BotType { get; set; } public string Identifier { get; set; } public UserDto User { get; set; } + public BotStatus LastStatus { get; set; } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs index 4f94b3b..c0a81c3 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/Collections/UserDto.cs @@ -7,4 +7,5 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections; public class UserDto : Document { public string Name { get; set; } + public string AgentName { get; set; } } \ 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 9c941bf..1616209 100644 --- a/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs +++ b/src/Managing.Infrastructure.Database/MongoDb/MongoMappers.cs @@ -516,6 +516,7 @@ public static class MongoMappers return new User { Name = user.Name, + AgentName = user.AgentName }; } @@ -523,7 +524,8 @@ public static class MongoMappers { return new UserDto { - Name = user.Name + Name = user.Name, + AgentName = user.AgentName }; } @@ -724,6 +726,7 @@ public static class MongoMappers Identifier = bot.Identifier, BotType = bot.BotType, Data = bot.Data, + LastStatus = bot.LastStatus }; } @@ -736,7 +739,8 @@ public static class MongoMappers User = Map(b.User), Identifier = b.Identifier, BotType = b.BotType, - Data = b.Data + Data = b.Data, + LastStatus = b.LastStatus }; } diff --git a/src/Managing.Infrastructure.Database/UserRepository.cs b/src/Managing.Infrastructure.Database/UserRepository.cs index 35f4633..6e17c47 100644 --- a/src/Managing.Infrastructure.Database/UserRepository.cs +++ b/src/Managing.Infrastructure.Database/UserRepository.cs @@ -15,6 +15,12 @@ public class UserRepository : IUserRepository _userRepository = userRepository; } + public async Task GetUserByAgentNameAsync(string agentName) + { + var user = await _userRepository.FindOneAsync(u => u.AgentName == agentName); + return MongoMappers.Map(user); + } + public async Task GetUserByNameAsync(string name) { var user = await _userRepository.FindOneAsync(u => u.Name == name); @@ -31,7 +37,7 @@ public class UserRepository : IUserRepository try { var dto = await _userRepository.FindOneAsync(u => u.Name == user.Name); - dto.Name = user.Name; + dto.AgentName = user.AgentName; _userRepository.Update(dto); } catch (Exception e) diff --git a/src/Managing.Nswag/.DS_Store b/src/Managing.Nswag/.DS_Store index 21260084edbce376256620a6875612b96c6555db..cebf5ccac92432f74013c71cbb623f379ce0c216 100644 GIT binary patch delta 37 tcmZoMXfc@J&&a+pU^gQp`(z%bf1D}B$vH{+`8kZ6n7^=WX6N|J4*=%-42u8& delta 31 ncmZoMXfc@J&&akhU^gQp+hiW5f172QU$IPV5Z=tr@s}R}qZSHf diff --git a/src/Managing.Nswag/ManagingApi.ts b/src/Managing.Nswag/ManagingApi.ts new file mode 100644 index 0000000..d634f7b --- /dev/null +++ b/src/Managing.Nswag/ManagingApi.ts @@ -0,0 +1,3135 @@ +//---------------------- +// +// Generated using the NSwag toolchain v14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +//---------------------- + +/* tslint:disable */ +/* eslint-disable */ +// ReSharper disable InconsistentNaming + +export class AccountClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + account_PostAccount(account: Account): Promise { + let url_ = this.baseUrl + "/Account"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(account); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processAccount_PostAccount(_response); + }); + } + + protected processAccount_PostAccount(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Account; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + account_GetAccount(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Account?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processAccount_GetAccount(_response); + }); + } + + protected processAccount_GetAccount(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Account; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + account_DeleteAccount(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Account?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processAccount_DeleteAccount(_response); + }); + } + + protected processAccount_DeleteAccount(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + account_GetAccounts(): Promise { + let url_ = this.baseUrl + "/Account/accounts"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processAccount_GetAccounts(_response); + }); + } + + protected processAccount_GetAccounts(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Account[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + account_GetAccountsBalances(): Promise { + let url_ = this.baseUrl + "/Account/balances"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processAccount_GetAccountsBalances(_response); + }); + } + + protected processAccount_GetAccountsBalances(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Account[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class BacktestClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + backtest_Backtests(): Promise { + let url_ = this.baseUrl + "/Backtest"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBacktest_Backtests(_response); + }); + } + + protected processBacktest_Backtests(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Backtest[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + backtest_DeleteBacktest(id: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Backtest?"; + if (id !== undefined && id !== null) + url_ += "id=" + encodeURIComponent("" + id) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBacktest_DeleteBacktest(_response); + }); + } + + protected processBacktest_DeleteBacktest(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + backtest_Backtest(id: string): Promise { + let url_ = this.baseUrl + "/Backtest/{id}"; + if (id === undefined || id === null) + throw new Error("The parameter 'id' must be defined."); + url_ = url_.replace("{id}", encodeURIComponent("" + id)); + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBacktest_Backtest(_response); + }); + } + + protected processBacktest_Backtest(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Backtest; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + backtest_Run(accountName: string | null | undefined, botType: BotType | undefined, ticker: Ticker | undefined, scenarioName: string | null | undefined, timeframe: Timeframe | undefined, watchOnly: boolean | undefined, balance: number | undefined, moneyManagementName: string | null | undefined, startDate: Date | undefined, endDate: Date | undefined, save: boolean | undefined, cooldownPeriod: number | undefined, maxLossStreak: number | undefined, moneyManagement: MoneyManagement | undefined): Promise { + let url_ = this.baseUrl + "/Backtest/Run?"; + if (accountName !== undefined && accountName !== null) + url_ += "accountName=" + encodeURIComponent("" + accountName) + "&"; + if (botType === null) + throw new Error("The parameter 'botType' cannot be null."); + else if (botType !== undefined) + url_ += "botType=" + encodeURIComponent("" + botType) + "&"; + if (ticker === null) + throw new Error("The parameter 'ticker' cannot be null."); + else if (ticker !== undefined) + url_ += "ticker=" + encodeURIComponent("" + ticker) + "&"; + if (scenarioName !== undefined && scenarioName !== null) + url_ += "scenarioName=" + encodeURIComponent("" + scenarioName) + "&"; + if (timeframe === null) + throw new Error("The parameter 'timeframe' cannot be null."); + else if (timeframe !== undefined) + url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&"; + if (watchOnly === null) + throw new Error("The parameter 'watchOnly' cannot be null."); + else if (watchOnly !== undefined) + url_ += "watchOnly=" + encodeURIComponent("" + watchOnly) + "&"; + if (balance === null) + throw new Error("The parameter 'balance' cannot be null."); + else if (balance !== undefined) + url_ += "balance=" + encodeURIComponent("" + balance) + "&"; + if (moneyManagementName !== undefined && moneyManagementName !== null) + url_ += "moneyManagementName=" + encodeURIComponent("" + moneyManagementName) + "&"; + if (startDate === null) + throw new Error("The parameter 'startDate' cannot be null."); + else if (startDate !== undefined) + url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&"; + if (endDate === null) + throw new Error("The parameter 'endDate' cannot be null."); + else if (endDate !== undefined) + url_ += "endDate=" + encodeURIComponent(endDate ? "" + endDate.toISOString() : "") + "&"; + if (save === null) + throw new Error("The parameter 'save' cannot be null."); + else if (save !== undefined) + url_ += "save=" + encodeURIComponent("" + save) + "&"; + if (cooldownPeriod === null) + throw new Error("The parameter 'cooldownPeriod' cannot be null."); + else if (cooldownPeriod !== undefined) + url_ += "cooldownPeriod=" + encodeURIComponent("" + cooldownPeriod) + "&"; + if (maxLossStreak === null) + throw new Error("The parameter 'maxLossStreak' cannot be null."); + else if (maxLossStreak !== undefined) + url_ += "maxLossStreak=" + encodeURIComponent("" + maxLossStreak) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(moneyManagement); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBacktest_Run(_response); + }); + } + + protected processBacktest_Run(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Backtest; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class BotClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + bot_Start(request: StartBotRequest): Promise { + let url_ = this.baseUrl + "/Bot/Start"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(request); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_Start(_response); + }); + } + + protected processBot_Start(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_Stop(botType: BotType | undefined, botName: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Bot/Stop?"; + if (botType === null) + throw new Error("The parameter 'botType' cannot be null."); + else if (botType !== undefined) + url_ += "botType=" + encodeURIComponent("" + botType) + "&"; + if (botName !== undefined && botName !== null) + url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_Stop(_response); + }); + } + + protected processBot_Stop(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_Delete(botName: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Bot/Delete?"; + if (botName !== undefined && botName !== null) + url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_Delete(_response); + }); + } + + protected processBot_Delete(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as boolean; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_StopAll(): Promise { + let url_ = this.baseUrl + "/Bot/stop-all"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_StopAll(_response); + }); + } + + protected processBot_StopAll(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_Restart(botType: BotType | undefined, botName: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Bot/Restart?"; + if (botType === null) + throw new Error("The parameter 'botType' cannot be null."); + else if (botType !== undefined) + url_ += "botType=" + encodeURIComponent("" + botType) + "&"; + if (botName !== undefined && botName !== null) + url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_Restart(_response); + }); + } + + protected processBot_Restart(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_RestartAll(): Promise { + let url_ = this.baseUrl + "/Bot/restart-all"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_RestartAll(_response); + }); + } + + protected processBot_RestartAll(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_ToggleIsForWatching(botName: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Bot/ToggleIsForWatching?"; + if (botName !== undefined && botName !== null) + url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_ToggleIsForWatching(_response); + }); + } + + protected processBot_ToggleIsForWatching(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_GetActiveBots(): Promise { + let url_ = this.baseUrl + "/Bot"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_GetActiveBots(_response); + }); + } + + protected processBot_GetActiveBots(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as TradingBot[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_OpenPositionManually(request: OpenPositionManuallyRequest): Promise { + let url_ = this.baseUrl + "/Bot/OpenPosition"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(request); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_OpenPositionManually(_response); + }); + } + + protected processBot_OpenPositionManually(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Position; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + bot_ClosePosition(request: ClosePositionRequest): Promise { + let url_ = this.baseUrl + "/Bot/ClosePosition"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(request); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processBot_ClosePosition(_response); + }); + } + + protected processBot_ClosePosition(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Position; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class DataClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + data_GetTickers(timeframe: Timeframe | undefined): Promise { + let url_ = this.baseUrl + "/Data/GetTickers?"; + if (timeframe === null) + throw new Error("The parameter 'timeframe' cannot be null."); + else if (timeframe !== undefined) + url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetTickers(_response); + }); + } + + protected processData_GetTickers(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Ticker[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetSpotlight(): Promise { + let url_ = this.baseUrl + "/Data/Spotlight"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetSpotlight(_response); + }); + } + + protected processData_GetSpotlight(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as SpotlightOverview; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetCandles(exchange: TradingExchanges | undefined, ticker: Ticker | undefined, startDate: Date | undefined, timeframe: Timeframe | undefined): Promise { + let url_ = this.baseUrl + "/Data/GetCandles?"; + if (exchange === null) + throw new Error("The parameter 'exchange' cannot be null."); + else if (exchange !== undefined) + url_ += "exchange=" + encodeURIComponent("" + exchange) + "&"; + if (ticker === null) + throw new Error("The parameter 'ticker' cannot be null."); + else if (ticker !== undefined) + url_ += "ticker=" + encodeURIComponent("" + ticker) + "&"; + if (startDate === null) + throw new Error("The parameter 'startDate' cannot be null."); + else if (startDate !== undefined) + url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&"; + if (timeframe === null) + throw new Error("The parameter 'timeframe' cannot be null."); + else if (timeframe !== undefined) + url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetCandles(_response); + }); + } + + protected processData_GetCandles(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Candle[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetStrategiesStatistics(): Promise { + let url_ = this.baseUrl + "/Data/GetStrategiesStatistics"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetStrategiesStatistics(_response); + }); + } + + protected processData_GetStrategiesStatistics(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as StrategiesStatisticsViewModel; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetTopStrategies(): Promise { + let url_ = this.baseUrl + "/Data/GetTopStrategies"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetTopStrategies(_response); + }); + } + + protected processData_GetTopStrategies(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as TopStrategiesViewModel; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetUserStrategies(agentName: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Data/GetUserStrategies?"; + if (agentName !== undefined && agentName !== null) + url_ += "agentName=" + encodeURIComponent("" + agentName) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetUserStrategies(_response); + }); + } + + protected processData_GetUserStrategies(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as UserStrategyDetailsViewModel[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetUserStrategy(agentName: string | null | undefined, strategyName: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Data/GetUserStrategy?"; + if (agentName !== undefined && agentName !== null) + url_ += "agentName=" + encodeURIComponent("" + agentName) + "&"; + if (strategyName !== undefined && strategyName !== null) + url_ += "strategyName=" + encodeURIComponent("" + strategyName) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetUserStrategy(_response); + }); + } + + protected processData_GetUserStrategy(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as UserStrategyDetailsViewModel; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + data_GetPlatformSummary(timeFilter: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Data/GetPlatformSummary?"; + if (timeFilter !== undefined && timeFilter !== null) + url_ += "timeFilter=" + encodeURIComponent("" + timeFilter) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processData_GetPlatformSummary(_response); + }); + } + + protected processData_GetPlatformSummary(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as PlatformSummaryViewModel; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class MoneyManagementClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + moneyManagement_PostMoneyManagement(moneyManagement: MoneyManagement): Promise { + let url_ = this.baseUrl + "/MoneyManagement"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(moneyManagement); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processMoneyManagement_PostMoneyManagement(_response); + }); + } + + protected processMoneyManagement_PostMoneyManagement(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as MoneyManagement; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + moneyManagement_GetMoneyManagement(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/MoneyManagement?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processMoneyManagement_GetMoneyManagement(_response); + }); + } + + protected processMoneyManagement_GetMoneyManagement(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as MoneyManagement; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + moneyManagement_DeleteMoneyManagement(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/MoneyManagement?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processMoneyManagement_DeleteMoneyManagement(_response); + }); + } + + protected processMoneyManagement_DeleteMoneyManagement(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + moneyManagement_GetMoneyManagements(): Promise { + let url_ = this.baseUrl + "/MoneyManagement/moneymanagements"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processMoneyManagement_GetMoneyManagements(_response); + }); + } + + protected processMoneyManagement_GetMoneyManagements(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as MoneyManagement[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class ScenarioClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + scenario_GetScenarios(): Promise { + let url_ = this.baseUrl + "/Scenario"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_GetScenarios(_response); + }); + } + + protected processScenario_GetScenarios(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Scenario[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_CreateScenario(name: string | null | undefined, loopbackPeriod: number | null | undefined, strategies: string[]): Promise { + let url_ = this.baseUrl + "/Scenario?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + if (loopbackPeriod !== undefined && loopbackPeriod !== null) + url_ += "loopbackPeriod=" + encodeURIComponent("" + loopbackPeriod) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(strategies); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_CreateScenario(_response); + }); + } + + protected processScenario_CreateScenario(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Scenario; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_DeleteScenario(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Scenario?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_DeleteScenario(_response); + }); + } + + protected processScenario_DeleteScenario(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_UpdateScenario(name: string | null | undefined, loopbackPeriod: number | null | undefined, strategies: string[]): Promise { + let url_ = this.baseUrl + "/Scenario?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + if (loopbackPeriod !== undefined && loopbackPeriod !== null) + url_ += "loopbackPeriod=" + encodeURIComponent("" + loopbackPeriod) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(strategies); + + let options_: RequestInit = { + body: content_, + method: "PUT", + headers: { + "Content-Type": "application/json", + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_UpdateScenario(_response); + }); + } + + protected processScenario_UpdateScenario(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_GetStrategies(): Promise { + let url_ = this.baseUrl + "/Scenario/strategy"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_GetStrategies(_response); + }); + } + + protected processScenario_GetStrategies(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Strategy[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_CreateStrategy(strategyType: StrategyType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise { + let url_ = this.baseUrl + "/Scenario/strategy?"; + if (strategyType === null) + throw new Error("The parameter 'strategyType' cannot be null."); + else if (strategyType !== undefined) + url_ += "strategyType=" + encodeURIComponent("" + strategyType) + "&"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + if (period !== undefined && period !== null) + url_ += "period=" + encodeURIComponent("" + period) + "&"; + if (fastPeriods !== undefined && fastPeriods !== null) + url_ += "fastPeriods=" + encodeURIComponent("" + fastPeriods) + "&"; + if (slowPeriods !== undefined && slowPeriods !== null) + url_ += "slowPeriods=" + encodeURIComponent("" + slowPeriods) + "&"; + if (signalPeriods !== undefined && signalPeriods !== null) + url_ += "signalPeriods=" + encodeURIComponent("" + signalPeriods) + "&"; + if (multiplier !== undefined && multiplier !== null) + url_ += "multiplier=" + encodeURIComponent("" + multiplier) + "&"; + if (stochPeriods !== undefined && stochPeriods !== null) + url_ += "stochPeriods=" + encodeURIComponent("" + stochPeriods) + "&"; + if (smoothPeriods !== undefined && smoothPeriods !== null) + url_ += "smoothPeriods=" + encodeURIComponent("" + smoothPeriods) + "&"; + if (cyclePeriods !== undefined && cyclePeriods !== null) + url_ += "cyclePeriods=" + encodeURIComponent("" + cyclePeriods) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_CreateStrategy(_response); + }); + } + + protected processScenario_CreateStrategy(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Strategy; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_DeleteStrategy(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Scenario/strategy?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_DeleteStrategy(_response); + }); + } + + protected processScenario_DeleteStrategy(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + scenario_UpdateStrategy(strategyType: StrategyType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise { + let url_ = this.baseUrl + "/Scenario/strategy?"; + if (strategyType === null) + throw new Error("The parameter 'strategyType' cannot be null."); + else if (strategyType !== undefined) + url_ += "strategyType=" + encodeURIComponent("" + strategyType) + "&"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + if (period !== undefined && period !== null) + url_ += "period=" + encodeURIComponent("" + period) + "&"; + if (fastPeriods !== undefined && fastPeriods !== null) + url_ += "fastPeriods=" + encodeURIComponent("" + fastPeriods) + "&"; + if (slowPeriods !== undefined && slowPeriods !== null) + url_ += "slowPeriods=" + encodeURIComponent("" + slowPeriods) + "&"; + if (signalPeriods !== undefined && signalPeriods !== null) + url_ += "signalPeriods=" + encodeURIComponent("" + signalPeriods) + "&"; + if (multiplier !== undefined && multiplier !== null) + url_ += "multiplier=" + encodeURIComponent("" + multiplier) + "&"; + if (stochPeriods !== undefined && stochPeriods !== null) + url_ += "stochPeriods=" + encodeURIComponent("" + stochPeriods) + "&"; + if (smoothPeriods !== undefined && smoothPeriods !== null) + url_ += "smoothPeriods=" + encodeURIComponent("" + smoothPeriods) + "&"; + if (cyclePeriods !== undefined && cyclePeriods !== null) + url_ += "cyclePeriods=" + encodeURIComponent("" + cyclePeriods) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "PUT", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processScenario_UpdateStrategy(_response); + }); + } + + protected processScenario_UpdateStrategy(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class SentryTestClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + sentryTest_TestException(): Promise { + let url_ = this.baseUrl + "/api/SentryTest/test-exception"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processSentryTest_TestException(_response); + }); + } + + protected processSentryTest_TestException(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + sentryTest_ThrowException(): Promise { + let url_ = this.baseUrl + "/api/SentryTest/throw-exception"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processSentryTest_ThrowException(_response); + }); + } + + protected processSentryTest_ThrowException(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + sentryTest_TestMessage(): Promise { + let url_ = this.baseUrl + "/api/SentryTest/test-message"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processSentryTest_TestMessage(_response); + }); + } + + protected processSentryTest_TestMessage(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class SettingsClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + settings_SetupSettings(): Promise { + let url_ = this.baseUrl + "/Settings"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processSettings_SetupSettings(_response); + }); + } + + protected processSettings_SetupSettings(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + settings_ResetSettings(): Promise { + let url_ = this.baseUrl + "/Settings"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processSettings_ResetSettings(_response); + }); + } + + protected processSettings_ResetSettings(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as boolean; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + settings_CreateDefaultConfiguration(): Promise { + let url_ = this.baseUrl + "/Settings/create-default-config"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processSettings_CreateDefaultConfiguration(_response); + }); + } + + protected processSettings_CreateDefaultConfiguration(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as boolean; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class TradingClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + trading_GetPositions(positionInitiator: PositionInitiator | undefined): Promise { + let url_ = this.baseUrl + "/Trading/GetPositions?"; + if (positionInitiator === null) + throw new Error("The parameter 'positionInitiator' cannot be null."); + else if (positionInitiator !== undefined) + url_ += "positionInitiator=" + encodeURIComponent("" + positionInitiator) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processTrading_GetPositions(_response); + }); + } + + protected processTrading_GetPositions(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Position[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + trading_GetTrade(accountName: string | null | undefined, ticker: Ticker | undefined, exchangeOrderId: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Trading/GetTrade?"; + if (accountName !== undefined && accountName !== null) + url_ += "accountName=" + encodeURIComponent("" + accountName) + "&"; + if (ticker === null) + throw new Error("The parameter 'ticker' cannot be null."); + else if (ticker !== undefined) + url_ += "ticker=" + encodeURIComponent("" + ticker) + "&"; + if (exchangeOrderId !== undefined && exchangeOrderId !== null) + url_ += "exchangeOrderId=" + encodeURIComponent("" + exchangeOrderId) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processTrading_GetTrade(_response); + }); + } + + protected processTrading_GetTrade(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Trade; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + trading_GetTrades(accountName: string | null | undefined, ticker: Ticker | undefined): Promise { + let url_ = this.baseUrl + "/Trading/GetTrades?"; + if (accountName !== undefined && accountName !== null) + url_ += "accountName=" + encodeURIComponent("" + accountName) + "&"; + if (ticker === null) + throw new Error("The parameter 'ticker' cannot be null."); + else if (ticker !== undefined) + url_ += "ticker=" + encodeURIComponent("" + ticker) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processTrading_GetTrades(_response); + }); + } + + protected processTrading_GetTrades(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Trade; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + trading_ClosePosition(identifier: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Trading/ClosePosition?"; + if (identifier !== undefined && identifier !== null) + url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processTrading_ClosePosition(_response); + }); + } + + protected processTrading_ClosePosition(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Position; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + trading_Trade(accountName: string | null | undefined, moneyManagementName: string | null | undefined, direction: TradeDirection | undefined, ticker: Ticker | undefined, riskLevel: RiskLevel | undefined, isForPaperTrading: boolean | undefined, openPrice: number | null | undefined, moneyManagement: MoneyManagement | undefined): Promise { + let url_ = this.baseUrl + "/Trading/OpenPosition?"; + if (accountName !== undefined && accountName !== null) + url_ += "accountName=" + encodeURIComponent("" + accountName) + "&"; + if (moneyManagementName !== undefined && moneyManagementName !== null) + url_ += "moneyManagementName=" + encodeURIComponent("" + moneyManagementName) + "&"; + if (direction === null) + throw new Error("The parameter 'direction' cannot be null."); + else if (direction !== undefined) + url_ += "direction=" + encodeURIComponent("" + direction) + "&"; + if (ticker === null) + throw new Error("The parameter 'ticker' cannot be null."); + else if (ticker !== undefined) + url_ += "ticker=" + encodeURIComponent("" + ticker) + "&"; + if (riskLevel === null) + throw new Error("The parameter 'riskLevel' cannot be null."); + else if (riskLevel !== undefined) + url_ += "riskLevel=" + encodeURIComponent("" + riskLevel) + "&"; + if (isForPaperTrading === null) + throw new Error("The parameter 'isForPaperTrading' cannot be null."); + else if (isForPaperTrading !== undefined) + url_ += "isForPaperTrading=" + encodeURIComponent("" + isForPaperTrading) + "&"; + if (openPrice !== undefined && openPrice !== null) + url_ += "openPrice=" + encodeURIComponent("" + openPrice) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(moneyManagement); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processTrading_Trade(_response); + }); + } + + protected processTrading_Trade(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Position; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class UserClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + user_CreateToken(login: LoginRequest): Promise { + let url_ = this.baseUrl + "/User"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(login); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processUser_CreateToken(_response); + }); + } + + protected processUser_CreateToken(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as string; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + user_UpdateAgentName(agentName: string): Promise { + let url_ = this.baseUrl + "/User/agent-name"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(agentName); + + let options_: RequestInit = { + body: content_, + method: "PUT", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processUser_UpdateAgentName(_response); + }); + } + + protected processUser_UpdateAgentName(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class WorkflowClient extends AuthorizedApiBase { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(configuration: IConfig, baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + super(configuration); + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + workflow_PostWorkflow(workflowRequest: SyntheticWorkflow): Promise { + let url_ = this.baseUrl + "/Workflow"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(workflowRequest); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processWorkflow_PostWorkflow(_response); + }); + } + + protected processWorkflow_PostWorkflow(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Workflow; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + workflow_GetWorkflows(): Promise { + let url_ = this.baseUrl + "/Workflow"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processWorkflow_GetWorkflows(_response); + }); + } + + protected processWorkflow_GetWorkflows(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as SyntheticWorkflow[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + workflow_DeleteWorkflow(name: string | null | undefined): Promise { + let url_ = this.baseUrl + "/Workflow?"; + if (name !== undefined && name !== null) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processWorkflow_DeleteWorkflow(_response); + }); + } + + protected processWorkflow_DeleteWorkflow(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + workflow_GetAvailableFlows(): Promise { + let url_ = this.baseUrl + "/Workflow/flows"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processWorkflow_GetAvailableFlows(_response); + }); + } + + protected processWorkflow_GetAvailableFlows(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as IFlow[]; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export interface Account { + name: string; + exchange: TradingExchanges; + type: AccountType; + key?: string | null; + secret?: string | null; + user?: User | null; + balances?: Balance[] | null; + isPrivyWallet?: boolean; +} + +export enum TradingExchanges { + Binance = "Binance", + Kraken = "Kraken", + Ftx = "Ftx", + Evm = "Evm", + GmxV2 = "GmxV2", +} + +export enum AccountType { + Cex = "Cex", + Trader = "Trader", + Watch = "Watch", + Auth = "Auth", + Privy = "Privy", +} + +export interface User { + name?: string | null; + accounts?: Account[] | null; + agentName?: string | null; +} + +export interface Balance { + tokenImage?: string | null; + tokenName?: string | null; + amount?: number; + price?: number; + value?: number; + tokenAdress?: string | null; + chain?: Chain | null; +} + +export interface Chain { + id?: string | null; + rpcUrl?: string | null; + name?: string | null; + chainId?: number; +} + +export interface Backtest { + id: string; + finalPnl: number; + winRate: number; + growthPercentage: number; + hodlPercentage: number; + ticker: Ticker; + scenario: string; + positions: Position[]; + signals: Signal[]; + timeframe: Timeframe; + botType: BotType; + accountName: string; + candles: Candle[]; + startDate: Date; + endDate: Date; + statistics: PerformanceMetrics; + fees: number; + walletBalances: KeyValuePairOfDateTimeAndDecimal[]; + optimizedMoneyManagement: MoneyManagement; + moneyManagement: MoneyManagement; + user: User; + strategiesValues: { [key in keyof typeof StrategyType]?: StrategiesResultBase; }; + score: number; +} + +export enum Ticker { + AAVE = "AAVE", + ADA = "ADA", + APE = "APE", + ALGO = "ALGO", + ARB = "ARB", + ATOM = "ATOM", + AVAX = "AVAX", + BNB = "BNB", + BTC = "BTC", + BAL = "BAL", + CHZ = "CHZ", + COMP = "COMP", + CRO = "CRO", + CRV = "CRV", + DOGE = "DOGE", + DOT = "DOT", + DYDX = "DYDX", + ENS = "ENS", + ETC = "ETC", + ETH = "ETH", + FIL = "FIL", + FLM = "FLM", + FTM = "FTM", + GALA = "GALA", + GMX = "GMX", + GRT = "GRT", + IMX = "IMX", + JASMY = "JASMY", + KSM = "KSM", + LDO = "LDO", + LINK = "LINK", + LRC = "LRC", + LTC = "LTC", + MANA = "MANA", + MATIC = "MATIC", + MKR = "MKR", + NEAR = "NEAR", + OP = "OP", + PEPE = "PEPE", + QTUM = "QTUM", + REN = "REN", + ROSE = "ROSE", + RSR = "RSR", + RUNE = "RUNE", + SAND = "SAND", + SOL = "SOL", + SRM = "SRM", + SUSHI = "SUSHI", + THETA = "THETA", + UNI = "UNI", + USDC = "USDC", + USDT = "USDT", + WIF = "WIF", + XMR = "XMR", + XRP = "XRP", + XTZ = "XTZ", + SHIB = "SHIB", + STX = "STX", + ORDI = "ORDI", + APT = "APT", + BOME = "BOME", + MEME = "MEME", + FLOKI = "FLOKI", + MEW = "MEW", + TAO = "TAO", + BONK = "BONK", + WLD = "WLD", + TBTC = "tBTC", + WBTC_b = "WBTC_b", + EIGEN = "EIGEN", + SUI = "SUI", + SEI = "SEI", + USDC_e = "USDC_e", + DAI = "DAI", + TIA = "TIA", + TRX = "TRX", + TON = "TON", + PENDLE = "PENDLE", + WstETH = "wstETH", + USDe = "USDe", + SATS = "SATS", + POL = "POL", + XLM = "XLM", + BCH = "BCH", + ICP = "ICP", + RENDER = "RENDER", + INJ = "INJ", + TRUMP = "TRUMP", + MELANIA = "MELANIA", + ENA = "ENA", + FARTCOIN = "FARTCOIN", + AI16Z = "AI16Z", + ANIME = "ANIME", + BERA = "BERA", + VIRTUAL = "VIRTUAL", + PENGU = "PENGU", + ONDO = "ONDO", + FET = "FET", + AIXBT = "AIXBT", + CAKE = "CAKE", + S = "S", + JUP = "JUP", + HYPE = "HYPE", + OM = "OM", + DOLO = "DOLO", + Unknown = "Unknown", +} + +export interface Position { + accountName: string; + date: Date; + originDirection: TradeDirection; + ticker: Ticker; + moneyManagement: MoneyManagement; + open: Trade; + stopLoss: Trade; + takeProfit1: Trade; + takeProfit2?: Trade | null; + profitAndLoss?: ProfitAndLoss | null; + status: PositionStatus; + signalIdentifier?: string | null; + identifier: string; + initiator: PositionInitiator; + user: User; +} + +export enum TradeDirection { + None = "None", + Short = "Short", + Long = "Long", +} + +export interface MoneyManagement { + name: string; + timeframe: Timeframe; + stopLoss: number; + takeProfit: number; + leverage: number; + user?: User | null; +} + +export enum Timeframe { + FiveMinutes = "FiveMinutes", + FifteenMinutes = "FifteenMinutes", + ThirtyMinutes = "ThirtyMinutes", + OneHour = "OneHour", + FourHour = "FourHour", + OneDay = "OneDay", + OneMinute = "OneMinute", +} + +export interface Trade { + fee?: number; + date: Date; + direction: TradeDirection; + status: TradeStatus; + tradeType: TradeType; + ticker: Ticker; + quantity: number; + price: number; + leverage?: number; + exchangeOrderId: string; + message?: string | null; +} + +export enum TradeStatus { + PendingOpen = "PendingOpen", + Requested = "Requested", + Cancelled = "Cancelled", + Filled = "Filled", +} + +export enum TradeType { + Limit = "Limit", + Market = "Market", + StopMarket = "StopMarket", + StopLimit = "StopLimit", + StopLoss = "StopLoss", + TakeProfit = "TakeProfit", + StopLossProfit = "StopLossProfit", + StopLossProfitLimit = "StopLossProfitLimit", + StopLossLimit = "StopLossLimit", + TakeProfitLimit = "TakeProfitLimit", + TrailingStop = "TrailingStop", + TrailingStopLimit = "TrailingStopLimit", + StopLossAndLimit = "StopLossAndLimit", + SettlePosition = "SettlePosition", +} + +export interface ProfitAndLoss { + realized?: number; + net?: number; + averageOpenPrice?: number; +} + +export enum PositionStatus { + New = "New", + Canceled = "Canceled", + Rejected = "Rejected", + Updating = "Updating", + PartiallyFilled = "PartiallyFilled", + Filled = "Filled", + Flipped = "Flipped", + Finished = "Finished", +} + +export enum PositionInitiator { + PaperTrading = "PaperTrading", + Bot = "Bot", + User = "User", + CopyTrading = "CopyTrading", +} + +export interface ValueObject { +} + +export interface Signal extends ValueObject { + status: SignalStatus; + direction: TradeDirection; + confidence: Confidence; + timeframe: Timeframe; + date: Date; + candle: Candle; + identifier: string; + ticker: Ticker; + exchange: TradingExchanges; + strategyType: StrategyType; + signalType: SignalType; + user?: User | null; +} + +export enum SignalStatus { + WaitingForPosition = "WaitingForPosition", + PositionOpen = "PositionOpen", + Expired = "Expired", +} + +export enum Confidence { + Low = "Low", + Medium = "Medium", + High = "High", + None = "None", +} + +export interface Candle { + exchange: TradingExchanges; + ticker: string; + openTime: Date; + date: Date; + open: number; + close: number; + volume?: number; + high: number; + low: number; + baseVolume?: number; + quoteVolume?: number; + tradeCount?: number; + takerBuyBaseVolume?: number; + takerBuyQuoteVolume?: number; + timeframe: Timeframe; +} + +export enum StrategyType { + RsiDivergence = "RsiDivergence", + RsiDivergenceConfirm = "RsiDivergenceConfirm", + MacdCross = "MacdCross", + EmaCross = "EmaCross", + ThreeWhiteSoldiers = "ThreeWhiteSoldiers", + SuperTrend = "SuperTrend", + ChandelierExit = "ChandelierExit", + EmaTrend = "EmaTrend", + Composite = "Composite", + StochRsiTrend = "StochRsiTrend", + Stc = "Stc", + StDev = "StDev", + LaggingStc = "LaggingStc", + SuperTrendCrossEma = "SuperTrendCrossEma", +} + +export enum SignalType { + Signal = "Signal", + Trend = "Trend", + Context = "Context", +} + +export enum BotType { + SimpleBot = "SimpleBot", + ScalpingBot = "ScalpingBot", + FlippingBot = "FlippingBot", +} + +export interface PerformanceMetrics { + count?: number; + sharpeRatio?: number; + maxDrawdown?: number; + maxDrawdownPc?: number; + maxDrawdownRecoveryTime?: string; + winningTrades?: number; + loosingTrades?: number; + totalPnL?: number; +} + +export interface KeyValuePairOfDateTimeAndDecimal { + key?: Date; + value?: number; +} + +export interface StrategiesResultBase { + ema?: EmaResult[] | null; + macd?: MacdResult[] | null; + rsi?: RsiResult[] | null; + stoch?: StochResult[] | null; + stochRsi?: StochRsiResult[] | null; + bollingerBands?: BollingerBandsResult[] | null; + chandelierShort?: ChandelierResult[] | null; + stc?: StcResult[] | null; + stdDev?: StdDevResult[] | null; + superTrend?: SuperTrendResult[] | null; + chandelierLong?: ChandelierResult[] | null; +} + +export interface ResultBase { + date?: Date; +} + +export interface EmaResult extends ResultBase { + ema?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface MacdResult extends ResultBase { + macd?: number | null; + signal?: number | null; + histogram?: number | null; + fastEma?: number | null; + slowEma?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface RsiResult extends ResultBase { + rsi?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */ +export interface StochResult extends ResultBase { + oscillator?: number | null; + signal?: number | null; + percentJ?: number | null; + k?: number | null; + d?: number | null; + j?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface StochRsiResult extends ResultBase { + stochRsi?: number | null; + signal?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface BollingerBandsResult extends ResultBase { + sma?: number | null; + upperBand?: number | null; + lowerBand?: number | null; + percentB?: number | null; + zScore?: number | null; + width?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface ChandelierResult extends ResultBase { + chandelierExit?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface StcResult extends ResultBase { + stc?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface StdDevResult extends ResultBase { + stdDev?: number | null; + mean?: number | null; + zScore?: number | null; + stdDevSma?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface SuperTrendResult extends ResultBase { + superTrend?: number | null; + upperBand?: number | null; + lowerBand?: number | null; +} + +export interface StartBotRequest { + botType?: BotType; + botName?: string | null; + ticker?: Ticker; + scenario?: string | null; + timeframe?: Timeframe; + accountName?: string | null; + moneyManagementName?: string | null; + isForWatchOnly?: boolean; + initialTradingBalance?: number; +} + +export interface TradingBot { + name: string; + status: string; + signals: Signal[]; + positions: Position[]; + candles: Candle[]; + winRate: number; + profitAndLoss: number; + timeframe: Timeframe; + ticker: Ticker; + scenario: string; + isForWatchingOnly: boolean; + botType: BotType; + accountName: string; + moneyManagement: MoneyManagement; +} + +export interface OpenPositionManuallyRequest { + botName?: string | null; + direction?: TradeDirection; +} + +export interface ClosePositionRequest { + botName?: string | null; + positionId?: string | null; +} + +export interface SpotlightOverview { + spotlights: Spotlight[]; + dateTime: Date; + identifier?: string; + scenarioCount?: number; +} + +export interface Spotlight { + scenario: Scenario; + tickerSignals: TickerSignal[]; +} + +export interface Scenario { + name?: string | null; + strategies?: Strategy[] | null; + loopbackPeriod?: number | null; + user?: User | null; +} + +export interface Strategy { + name?: string | null; + type?: StrategyType; + signalType?: SignalType; + minimumHistory?: number; + period?: number | null; + fastPeriods?: number | null; + slowPeriods?: number | null; + signalPeriods?: number | null; + multiplier?: number | null; + smoothPeriods?: number | null; + stochPeriods?: number | null; + cyclePeriods?: number | null; + user?: User | null; +} + +export interface TickerSignal { + ticker: Ticker; + fiveMinutes: Signal[]; + fifteenMinutes: Signal[]; + oneHour: Signal[]; + fourHour: Signal[]; + oneDay: Signal[]; +} + +export interface StrategiesStatisticsViewModel { + totalStrategiesRunning?: number; + changeInLast24Hours?: number; +} + +export interface TopStrategiesViewModel { + topStrategies?: StrategyPerformance[] | null; +} + +export interface StrategyPerformance { + strategyName?: string | null; + pnL?: number; +} + +export interface UserStrategyDetailsViewModel { + name?: string | null; + strategyName?: string | null; + state?: string | null; + pnL?: number; + roiPercentage?: number; + roiLast24H?: number; + runtime?: Date; + winRate?: number; + totalVolumeTraded?: number; + volumeLast24H?: number; + wins?: number; + losses?: number; + positions?: Position[] | null; +} + +export interface PlatformSummaryViewModel { + totalAgents?: number; + totalActiveStrategies?: number; + totalPlatformPnL?: number; + totalPlatformVolume?: number; + totalPlatformVolumeLast24h?: number; + agentSummaries?: AgentSummaryViewModel[] | null; + timeFilter?: string | null; +} + +export interface AgentSummaryViewModel { + username?: string | null; + totalPnL?: number; + pnLLast24h?: number; + totalROI?: number; + roiLast24h?: number; + wins?: number; + losses?: number; + averageWinRate?: number; + activeStrategiesCount?: number; + totalVolume?: number; + volumeLast24h?: number; +} + +export enum RiskLevel { + Low = "Low", + Medium = "Medium", + High = "High", + Adaptive = "Adaptive", +} + +export interface LoginRequest { + name: string; + address: string; + signature: string; + message: string; +} + +export interface Workflow { + name: string; + usage: WorkflowUsage; + flows: IFlow[]; + description: string; +} + +export enum WorkflowUsage { + Trading = "Trading", + Task = "Task", +} + +export interface IFlow { + id: string; + name: string; + type: FlowType; + description: string; + acceptedInputs: FlowOutput[]; + children?: IFlow[] | null; + parameters: FlowParameter[]; + parentId?: string; + output?: string | null; + outputTypes: FlowOutput[]; +} + +export enum FlowType { + RsiDivergence = "RsiDivergence", + FeedTicker = "FeedTicker", + OpenPosition = "OpenPosition", +} + +export enum FlowOutput { + Signal = "Signal", + Candles = "Candles", + Position = "Position", + MoneyManagement = "MoneyManagement", +} + +export interface FlowParameter { + value?: any | null; + name?: string | null; +} + +export interface SyntheticWorkflow { + name: string; + usage: WorkflowUsage; + description: string; + flows: SyntheticFlow[]; +} + +export interface SyntheticFlow { + id: string; + parentId?: string | null; + type: FlowType; + parameters: SyntheticFlowParameter[]; +} + +export interface SyntheticFlowParameter { + value: string; + name: string; +} + +export interface FileResponse { + data: Blob; + status: number; + fileName?: string; + headers?: { [name: string]: any }; +} + +export class ApiException extends Error { + override message: string; + status: number; + response: string; + headers: { [key: string]: any; }; + result: any; + + constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) { + super(); + + this.message = message; + this.status = status; + this.response = response; + this.headers = headers; + this.result = result; + } + + protected isApiException = true; + + static isApiException(obj: any): obj is ApiException { + return obj.isApiException === true; + } +} + +function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any { + if (result !== null && result !== undefined) + throw result; + else + throw new ApiException(message, status, response, headers, null); +} \ No newline at end of file diff --git a/src/Managing.Nswag/ManagingApiTypes.ts b/src/Managing.Nswag/ManagingApiTypes.ts new file mode 100644 index 0000000..9c870be --- /dev/null +++ b/src/Managing.Nswag/ManagingApiTypes.ts @@ -0,0 +1,703 @@ +//---------------------- +// +// Generated using the NSwag toolchain v14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org) +// +//---------------------- + +/* tslint:disable */ +/* eslint-disable */ +// ReSharper disable InconsistentNaming + + + +export interface Account { + name: string; + exchange: TradingExchanges; + type: AccountType; + key?: string | null; + secret?: string | null; + user?: User | null; + balances?: Balance[] | null; + isPrivyWallet?: boolean; +} + +export enum TradingExchanges { + Binance = "Binance", + Kraken = "Kraken", + Ftx = "Ftx", + Evm = "Evm", + GmxV2 = "GmxV2", +} + +export enum AccountType { + Cex = "Cex", + Trader = "Trader", + Watch = "Watch", + Auth = "Auth", + Privy = "Privy", +} + +export interface User { + name?: string | null; + accounts?: Account[] | null; + agentName?: string | null; +} + +export interface Balance { + tokenImage?: string | null; + tokenName?: string | null; + amount?: number; + price?: number; + value?: number; + tokenAdress?: string | null; + chain?: Chain | null; +} + +export interface Chain { + id?: string | null; + rpcUrl?: string | null; + name?: string | null; + chainId?: number; +} + +export interface Backtest { + id: string; + finalPnl: number; + winRate: number; + growthPercentage: number; + hodlPercentage: number; + ticker: Ticker; + scenario: string; + positions: Position[]; + signals: Signal[]; + timeframe: Timeframe; + botType: BotType; + accountName: string; + candles: Candle[]; + startDate: Date; + endDate: Date; + statistics: PerformanceMetrics; + fees: number; + walletBalances: KeyValuePairOfDateTimeAndDecimal[]; + optimizedMoneyManagement: MoneyManagement; + moneyManagement: MoneyManagement; + user: User; + strategiesValues: { [key in keyof typeof StrategyType]?: StrategiesResultBase; }; + score: number; +} + +export enum Ticker { + AAVE = "AAVE", + ADA = "ADA", + APE = "APE", + ALGO = "ALGO", + ARB = "ARB", + ATOM = "ATOM", + AVAX = "AVAX", + BNB = "BNB", + BTC = "BTC", + BAL = "BAL", + CHZ = "CHZ", + COMP = "COMP", + CRO = "CRO", + CRV = "CRV", + DOGE = "DOGE", + DOT = "DOT", + DYDX = "DYDX", + ENS = "ENS", + ETC = "ETC", + ETH = "ETH", + FIL = "FIL", + FLM = "FLM", + FTM = "FTM", + GALA = "GALA", + GMX = "GMX", + GRT = "GRT", + IMX = "IMX", + JASMY = "JASMY", + KSM = "KSM", + LDO = "LDO", + LINK = "LINK", + LRC = "LRC", + LTC = "LTC", + MANA = "MANA", + MATIC = "MATIC", + MKR = "MKR", + NEAR = "NEAR", + OP = "OP", + PEPE = "PEPE", + QTUM = "QTUM", + REN = "REN", + ROSE = "ROSE", + RSR = "RSR", + RUNE = "RUNE", + SAND = "SAND", + SOL = "SOL", + SRM = "SRM", + SUSHI = "SUSHI", + THETA = "THETA", + UNI = "UNI", + USDC = "USDC", + USDT = "USDT", + WIF = "WIF", + XMR = "XMR", + XRP = "XRP", + XTZ = "XTZ", + SHIB = "SHIB", + STX = "STX", + ORDI = "ORDI", + APT = "APT", + BOME = "BOME", + MEME = "MEME", + FLOKI = "FLOKI", + MEW = "MEW", + TAO = "TAO", + BONK = "BONK", + WLD = "WLD", + TBTC = "tBTC", + WBTC_b = "WBTC_b", + EIGEN = "EIGEN", + SUI = "SUI", + SEI = "SEI", + USDC_e = "USDC_e", + DAI = "DAI", + TIA = "TIA", + TRX = "TRX", + TON = "TON", + PENDLE = "PENDLE", + WstETH = "wstETH", + USDe = "USDe", + SATS = "SATS", + POL = "POL", + XLM = "XLM", + BCH = "BCH", + ICP = "ICP", + RENDER = "RENDER", + INJ = "INJ", + TRUMP = "TRUMP", + MELANIA = "MELANIA", + ENA = "ENA", + FARTCOIN = "FARTCOIN", + AI16Z = "AI16Z", + ANIME = "ANIME", + BERA = "BERA", + VIRTUAL = "VIRTUAL", + PENGU = "PENGU", + ONDO = "ONDO", + FET = "FET", + AIXBT = "AIXBT", + CAKE = "CAKE", + S = "S", + JUP = "JUP", + HYPE = "HYPE", + OM = "OM", + DOLO = "DOLO", + Unknown = "Unknown", +} + +export interface Position { + accountName: string; + date: Date; + originDirection: TradeDirection; + ticker: Ticker; + moneyManagement: MoneyManagement; + open: Trade; + stopLoss: Trade; + takeProfit1: Trade; + takeProfit2?: Trade | null; + profitAndLoss?: ProfitAndLoss | null; + status: PositionStatus; + signalIdentifier?: string | null; + identifier: string; + initiator: PositionInitiator; + user: User; +} + +export enum TradeDirection { + None = "None", + Short = "Short", + Long = "Long", +} + +export interface MoneyManagement { + name: string; + timeframe: Timeframe; + stopLoss: number; + takeProfit: number; + leverage: number; + user?: User | null; +} + +export enum Timeframe { + FiveMinutes = "FiveMinutes", + FifteenMinutes = "FifteenMinutes", + ThirtyMinutes = "ThirtyMinutes", + OneHour = "OneHour", + FourHour = "FourHour", + OneDay = "OneDay", + OneMinute = "OneMinute", +} + +export interface Trade { + fee?: number; + date: Date; + direction: TradeDirection; + status: TradeStatus; + tradeType: TradeType; + ticker: Ticker; + quantity: number; + price: number; + leverage?: number; + exchangeOrderId: string; + message?: string | null; +} + +export enum TradeStatus { + PendingOpen = "PendingOpen", + Requested = "Requested", + Cancelled = "Cancelled", + Filled = "Filled", +} + +export enum TradeType { + Limit = "Limit", + Market = "Market", + StopMarket = "StopMarket", + StopLimit = "StopLimit", + StopLoss = "StopLoss", + TakeProfit = "TakeProfit", + StopLossProfit = "StopLossProfit", + StopLossProfitLimit = "StopLossProfitLimit", + StopLossLimit = "StopLossLimit", + TakeProfitLimit = "TakeProfitLimit", + TrailingStop = "TrailingStop", + TrailingStopLimit = "TrailingStopLimit", + StopLossAndLimit = "StopLossAndLimit", + SettlePosition = "SettlePosition", +} + +export interface ProfitAndLoss { + realized?: number; + net?: number; + averageOpenPrice?: number; +} + +export enum PositionStatus { + New = "New", + Canceled = "Canceled", + Rejected = "Rejected", + Updating = "Updating", + PartiallyFilled = "PartiallyFilled", + Filled = "Filled", + Flipped = "Flipped", + Finished = "Finished", +} + +export enum PositionInitiator { + PaperTrading = "PaperTrading", + Bot = "Bot", + User = "User", + CopyTrading = "CopyTrading", +} + +export interface ValueObject { +} + +export interface Signal extends ValueObject { + status: SignalStatus; + direction: TradeDirection; + confidence: Confidence; + timeframe: Timeframe; + date: Date; + candle: Candle; + identifier: string; + ticker: Ticker; + exchange: TradingExchanges; + strategyType: StrategyType; + signalType: SignalType; + user?: User | null; +} + +export enum SignalStatus { + WaitingForPosition = "WaitingForPosition", + PositionOpen = "PositionOpen", + Expired = "Expired", +} + +export enum Confidence { + Low = "Low", + Medium = "Medium", + High = "High", + None = "None", +} + +export interface Candle { + exchange: TradingExchanges; + ticker: string; + openTime: Date; + date: Date; + open: number; + close: number; + volume?: number; + high: number; + low: number; + baseVolume?: number; + quoteVolume?: number; + tradeCount?: number; + takerBuyBaseVolume?: number; + takerBuyQuoteVolume?: number; + timeframe: Timeframe; +} + +export enum StrategyType { + RsiDivergence = "RsiDivergence", + RsiDivergenceConfirm = "RsiDivergenceConfirm", + MacdCross = "MacdCross", + EmaCross = "EmaCross", + ThreeWhiteSoldiers = "ThreeWhiteSoldiers", + SuperTrend = "SuperTrend", + ChandelierExit = "ChandelierExit", + EmaTrend = "EmaTrend", + Composite = "Composite", + StochRsiTrend = "StochRsiTrend", + Stc = "Stc", + StDev = "StDev", + LaggingStc = "LaggingStc", + SuperTrendCrossEma = "SuperTrendCrossEma", +} + +export enum SignalType { + Signal = "Signal", + Trend = "Trend", + Context = "Context", +} + +export enum BotType { + SimpleBot = "SimpleBot", + ScalpingBot = "ScalpingBot", + FlippingBot = "FlippingBot", +} + +export interface PerformanceMetrics { + count?: number; + sharpeRatio?: number; + maxDrawdown?: number; + maxDrawdownPc?: number; + maxDrawdownRecoveryTime?: string; + winningTrades?: number; + loosingTrades?: number; + totalPnL?: number; +} + +export interface KeyValuePairOfDateTimeAndDecimal { + key?: Date; + value?: number; +} + +export interface StrategiesResultBase { + ema?: EmaResult[] | null; + macd?: MacdResult[] | null; + rsi?: RsiResult[] | null; + stoch?: StochResult[] | null; + stochRsi?: StochRsiResult[] | null; + bollingerBands?: BollingerBandsResult[] | null; + chandelierShort?: ChandelierResult[] | null; + stc?: StcResult[] | null; + stdDev?: StdDevResult[] | null; + superTrend?: SuperTrendResult[] | null; + chandelierLong?: ChandelierResult[] | null; +} + +export interface ResultBase { + date?: Date; +} + +export interface EmaResult extends ResultBase { + ema?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface MacdResult extends ResultBase { + macd?: number | null; + signal?: number | null; + histogram?: number | null; + fastEma?: number | null; + slowEma?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface RsiResult extends ResultBase { + rsi?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */ +export interface StochResult extends ResultBase { + oscillator?: number | null; + signal?: number | null; + percentJ?: number | null; + k?: number | null; + d?: number | null; + j?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface StochRsiResult extends ResultBase { + stochRsi?: number | null; + signal?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface BollingerBandsResult extends ResultBase { + sma?: number | null; + upperBand?: number | null; + lowerBand?: number | null; + percentB?: number | null; + zScore?: number | null; + width?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface ChandelierResult extends ResultBase { + chandelierExit?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface StcResult extends ResultBase { + stc?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface StdDevResult extends ResultBase { + stdDev?: number | null; + mean?: number | null; + zScore?: number | null; + stdDevSma?: number | null; + "skender.Stock.Indicators.IReusableResult.Value"?: number | null; +} + +export interface SuperTrendResult extends ResultBase { + superTrend?: number | null; + upperBand?: number | null; + lowerBand?: number | null; +} + +export interface StartBotRequest { + botType?: BotType; + botName?: string | null; + ticker?: Ticker; + scenario?: string | null; + timeframe?: Timeframe; + accountName?: string | null; + moneyManagementName?: string | null; + isForWatchOnly?: boolean; + initialTradingBalance?: number; +} + +export interface TradingBot { + name: string; + status: string; + signals: Signal[]; + positions: Position[]; + candles: Candle[]; + winRate: number; + profitAndLoss: number; + timeframe: Timeframe; + ticker: Ticker; + scenario: string; + isForWatchingOnly: boolean; + botType: BotType; + accountName: string; + moneyManagement: MoneyManagement; +} + +export interface OpenPositionManuallyRequest { + botName?: string | null; + direction?: TradeDirection; +} + +export interface ClosePositionRequest { + botName?: string | null; + positionId?: string | null; +} + +export interface SpotlightOverview { + spotlights: Spotlight[]; + dateTime: Date; + identifier?: string; + scenarioCount?: number; +} + +export interface Spotlight { + scenario: Scenario; + tickerSignals: TickerSignal[]; +} + +export interface Scenario { + name?: string | null; + strategies?: Strategy[] | null; + loopbackPeriod?: number | null; + user?: User | null; +} + +export interface Strategy { + name?: string | null; + type?: StrategyType; + signalType?: SignalType; + minimumHistory?: number; + period?: number | null; + fastPeriods?: number | null; + slowPeriods?: number | null; + signalPeriods?: number | null; + multiplier?: number | null; + smoothPeriods?: number | null; + stochPeriods?: number | null; + cyclePeriods?: number | null; + user?: User | null; +} + +export interface TickerSignal { + ticker: Ticker; + fiveMinutes: Signal[]; + fifteenMinutes: Signal[]; + oneHour: Signal[]; + fourHour: Signal[]; + oneDay: Signal[]; +} + +export interface StrategiesStatisticsViewModel { + totalStrategiesRunning?: number; + changeInLast24Hours?: number; +} + +export interface TopStrategiesViewModel { + topStrategies?: StrategyPerformance[] | null; +} + +export interface StrategyPerformance { + strategyName?: string | null; + pnL?: number; +} + +export interface UserStrategyDetailsViewModel { + name?: string | null; + strategyName?: string | null; + state?: string | null; + pnL?: number; + roiPercentage?: number; + roiLast24H?: number; + runtime?: Date; + winRate?: number; + totalVolumeTraded?: number; + volumeLast24H?: number; + wins?: number; + losses?: number; + positions?: Position[] | null; +} + +export interface PlatformSummaryViewModel { + totalAgents?: number; + totalActiveStrategies?: number; + totalPlatformPnL?: number; + totalPlatformVolume?: number; + totalPlatformVolumeLast24h?: number; + agentSummaries?: AgentSummaryViewModel[] | null; + timeFilter?: string | null; +} + +export interface AgentSummaryViewModel { + username?: string | null; + totalPnL?: number; + pnLLast24h?: number; + totalROI?: number; + roiLast24h?: number; + wins?: number; + losses?: number; + averageWinRate?: number; + activeStrategiesCount?: number; + totalVolume?: number; + volumeLast24h?: number; +} + +export enum RiskLevel { + Low = "Low", + Medium = "Medium", + High = "High", + Adaptive = "Adaptive", +} + +export interface LoginRequest { + name: string; + address: string; + signature: string; + message: string; +} + +export interface Workflow { + name: string; + usage: WorkflowUsage; + flows: IFlow[]; + description: string; +} + +export enum WorkflowUsage { + Trading = "Trading", + Task = "Task", +} + +export interface IFlow { + id: string; + name: string; + type: FlowType; + description: string; + acceptedInputs: FlowOutput[]; + children?: IFlow[] | null; + parameters: FlowParameter[]; + parentId?: string; + output?: string | null; + outputTypes: FlowOutput[]; +} + +export enum FlowType { + RsiDivergence = "RsiDivergence", + FeedTicker = "FeedTicker", + OpenPosition = "OpenPosition", +} + +export enum FlowOutput { + Signal = "Signal", + Candles = "Candles", + Position = "Position", + MoneyManagement = "MoneyManagement", +} + +export interface FlowParameter { + value?: any | null; + name?: string | null; +} + +export interface SyntheticWorkflow { + name: string; + usage: WorkflowUsage; + description: string; + flows: SyntheticFlow[]; +} + +export interface SyntheticFlow { + id: string; + parentId?: string | null; + type: FlowType; + parameters: SyntheticFlowParameter[]; +} + +export interface SyntheticFlowParameter { + value: string; + name: string; +} + +export interface FileResponse { + data: Blob; + status: number; + fileName?: string; + headers?: { [name: string]: any }; +} \ No newline at end of file diff --git a/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx b/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx index 390751b..5be025d 100644 --- a/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx +++ b/src/Managing.WebApp/src/components/mollecules/TradesModal/TradesModal.tsx @@ -7,13 +7,15 @@ import Toast from '../Toast/Toast' interface TradesModalProps { showModal: boolean - botName: string | null + agentName: string | null + strategyName: string | null onClose: () => void } const TradesModal: React.FC = ({ showModal, - botName, + strategyName, + agentName, onClose, }) => { const { apiUrl } = useApiUrlStore() @@ -22,19 +24,19 @@ const TradesModal: React.FC = ({ const [closingPosition, setClosingPosition] = useState(null) useEffect(() => { - if (showModal && botName) { + if (showModal && strategyName && agentName) { fetchStrategyData() } - }, [showModal, botName]) + }, [showModal, strategyName, agentName]) const fetchStrategyData = async () => { - if (!botName) return + if (!strategyName || !agentName) return setLoading(true) const client = new DataClient({}, apiUrl) try { - const data = await client.data_GetUserStrategy("Oda", botName) + const data = await client.data_GetUserStrategy(agentName, strategyName) setStrategyData(data) } catch (error) { console.error('Error fetching strategy data:', error) @@ -46,7 +48,7 @@ const TradesModal: React.FC = ({ } const closePosition = async (position: Position) => { - if (!botName) return + if (!agentName) return try { setClosingPosition(position.identifier) @@ -56,7 +58,7 @@ const TradesModal: React.FC = ({ // Use BotClient instead of fetch const botClient = new BotClient({}, apiUrl) const request: ClosePositionRequest = { - botName: botName, + identifier: agentName, positionId: position.identifier } @@ -78,7 +80,7 @@ const TradesModal: React.FC = ({
{loading ? ( diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx index 919be98..d76d841 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx @@ -42,7 +42,9 @@ const BacktestModal: React.FC = ({ const { register, handleSubmit, setValue } = useForm({ defaultValues: { startDate: defaultStartDateString, - endDate: defaultEndDateString + endDate: defaultEndDateString, + cooldownPeriod: 1, // Default cooldown period of 1 minute + maxLossStreak: 0 // Default max loss streak of 0 (no limit) } }); const [selectedAccount, setSelectedAccount] = useState('') @@ -110,6 +112,8 @@ const BacktestModal: React.FC = ({ new Date(form.startDate), // startDate new Date(form.endDate), // endDate form.save, + form.cooldownPeriod, // Use the cooldown period from the form + form.maxLossStreak, // Add the max loss streak parameter customMoneyManagement ) .then((backtest: Backtest) => { @@ -362,6 +366,28 @@ const BacktestModal: React.FC = ({ /> + + + +
+ +
+ + + + (null as any); } - backtest_Run(accountName: string | null | undefined, botType: BotType | undefined, ticker: Ticker | undefined, scenarioName: string | null | undefined, timeframe: Timeframe | undefined, watchOnly: boolean | undefined, balance: number | undefined, moneyManagementName: string | null | undefined, startDate: Date | undefined, endDate: Date | undefined, save: boolean | undefined, moneyManagement: MoneyManagement | undefined): Promise { + backtest_Run(accountName: string | null | undefined, botType: BotType | undefined, ticker: Ticker | undefined, scenarioName: string | null | undefined, timeframe: Timeframe | undefined, watchOnly: boolean | undefined, balance: number | undefined, moneyManagementName: string | null | undefined, startDate: Date | undefined, endDate: Date | undefined, save: boolean | undefined, cooldownPeriod: number | undefined, maxLossStreak: number | undefined, moneyManagement: MoneyManagement | undefined): Promise { let url_ = this.baseUrl + "/Backtest/Run?"; if (accountName !== undefined && accountName !== null) url_ += "accountName=" + encodeURIComponent("" + accountName) + "&"; @@ -377,6 +377,14 @@ export class BacktestClient extends AuthorizedApiBase { throw new Error("The parameter 'save' cannot be null."); else if (save !== undefined) url_ += "save=" + encodeURIComponent("" + save) + "&"; + if (cooldownPeriod === null) + throw new Error("The parameter 'cooldownPeriod' cannot be null."); + else if (cooldownPeriod !== undefined) + url_ += "cooldownPeriod=" + encodeURIComponent("" + cooldownPeriod) + "&"; + if (maxLossStreak === null) + throw new Error("The parameter 'maxLossStreak' cannot be null."); + else if (maxLossStreak !== undefined) + url_ += "maxLossStreak=" + encodeURIComponent("" + maxLossStreak) + "&"; url_ = url_.replace(/[?&]$/, ""); const content_ = JSON.stringify(moneyManagement); @@ -465,14 +473,14 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_Stop(botType: BotType | undefined, botName: string | null | undefined): Promise { + bot_Stop(botType: BotType | undefined, identifier: string | null | undefined): Promise { let url_ = this.baseUrl + "/Bot/Stop?"; if (botType === null) throw new Error("The parameter 'botType' cannot be null."); else if (botType !== undefined) url_ += "botType=" + encodeURIComponent("" + botType) + "&"; - if (botName !== undefined && botName !== null) - url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + if (identifier !== undefined && identifier !== null) + url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -506,10 +514,10 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_Delete(botName: string | null | undefined): Promise { + bot_Delete(identifier: string | null | undefined): Promise { let url_ = this.baseUrl + "/Bot/Delete?"; - if (botName !== undefined && botName !== null) - url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + if (identifier !== undefined && identifier !== null) + url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -578,14 +586,14 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_Restart(botType: BotType | undefined, botName: string | null | undefined): Promise { + bot_Restart(botType: BotType | undefined, identifier: string | null | undefined): Promise { let url_ = this.baseUrl + "/Bot/Restart?"; if (botType === null) throw new Error("The parameter 'botType' cannot be null."); else if (botType !== undefined) url_ += "botType=" + encodeURIComponent("" + botType) + "&"; - if (botName !== undefined && botName !== null) - url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + if (identifier !== undefined && identifier !== null) + url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -654,10 +662,10 @@ export class BotClient extends AuthorizedApiBase { return Promise.resolve(null as any); } - bot_ToggleIsForWatching(botName: string | null | undefined): Promise { + bot_ToggleIsForWatching(identifier: string | null | undefined): Promise { let url_ = this.baseUrl + "/Bot/ToggleIsForWatching?"; - if (botName !== undefined && botName !== null) - url_ += "botName=" + encodeURIComponent("" + botName) + "&"; + if (identifier !== undefined && identifier !== null) + url_ += "identifier=" + encodeURIComponent("" + identifier) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -816,7 +824,7 @@ export class DataClient extends AuthorizedApiBase { this.baseUrl = baseUrl ?? "http://localhost:5000"; } - data_GetTickers(timeframe: Timeframe | undefined): Promise { + data_GetTickers(timeframe: Timeframe | undefined): Promise { let url_ = this.baseUrl + "/Data/GetTickers?"; if (timeframe === null) throw new Error("The parameter 'timeframe' cannot be null."); @@ -838,13 +846,13 @@ export class DataClient extends AuthorizedApiBase { }); } - protected processData_GetTickers(response: Response): Promise { + protected processData_GetTickers(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; if (status === 200) { return response.text().then((_responseText) => { let result200: any = null; - result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Ticker[]; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as TickerInfos[]; return result200; }); } else if (status !== 200 && status !== 204) { @@ -852,7 +860,7 @@ export class DataClient extends AuthorizedApiBase { return throwException("An unexpected server error occurred.", status, _responseText, _headers); }); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } data_GetSpotlight(): Promise { @@ -2202,6 +2210,80 @@ export class UserClient extends AuthorizedApiBase { } return Promise.resolve(null as any); } + + user_GetCurrentUser(): Promise { + let url_ = this.baseUrl + "/User"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processUser_GetCurrentUser(_response); + }); + } + + protected processUser_GetCurrentUser(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + user_UpdateAgentName(agentName: string): Promise { + let url_ = this.baseUrl + "/User/agent-name"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(agentName); + + let options_: RequestInit = { + body: content_, + method: "PUT", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processUser_UpdateAgentName(_response); + }); + } + + protected processUser_UpdateAgentName(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as User; + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } } export class WorkflowClient extends AuthorizedApiBase { @@ -2397,6 +2479,7 @@ export enum AccountType { export interface User { name?: string | null; accounts?: Account[] | null; + agentName?: string | null; } export interface Balance { @@ -2839,15 +2922,15 @@ export interface SuperTrendResult extends ResultBase { } export interface StartBotRequest { - botType: BotType; - botName: string; - ticker: Ticker; - timeframe: Timeframe; - isForWatchOnly: boolean; - scenario: string; - accountName: string; - moneyManagementName: string; - initialTradingBalance: number; + botType?: BotType; + identifier?: string | null; + ticker?: Ticker; + scenario?: string | null; + timeframe?: Timeframe; + accountName?: string | null; + moneyManagementName?: string | null; + isForWatchOnly?: boolean; + initialTradingBalance?: number; } export interface TradingBot { @@ -2865,18 +2948,25 @@ export interface TradingBot { botType: BotType; accountName: string; moneyManagement: MoneyManagement; + identifier: string; + agentName: string; } export interface OpenPositionManuallyRequest { - botName?: string | null; + identifier?: string | null; direction?: TradeDirection; } export interface ClosePositionRequest { - botName?: string | null; + identifier?: string | null; positionId?: string | null; } +export interface TickerInfos { + ticker?: Ticker; + imageUrl?: string | null; +} + export interface SpotlightOverview { spotlights: Spotlight[]; dateTime: Date; @@ -2962,7 +3052,7 @@ export interface PlatformSummaryViewModel { } export interface AgentSummaryViewModel { - username?: string | null; + agentName?: string | null; totalPnL?: number; pnLLast24h?: number; totalROI?: number; diff --git a/src/Managing.WebApp/src/global/type.tsx b/src/Managing.WebApp/src/global/type.tsx index 0d7933a..67863d9 100644 --- a/src/Managing.WebApp/src/global/type.tsx +++ b/src/Managing.WebApp/src/global/type.tsx @@ -111,6 +111,8 @@ export type IBacktestsFormInput = { loop: number startDate: string endDate: string + cooldownPeriod: number + maxLossStreak: number } export type IBacktestCards = { diff --git a/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx b/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx index 9d4a85e..3dedc13 100644 --- a/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx +++ b/src/Managing.WebApp/src/pages/backtestPage/backtest.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react' +import React, {useState} from 'react' import 'react-toastify/dist/ReactToastify.css' -import { Tabs } from '../../components/mollecules' -import type { TabsType } from '../../global/type' +import {Tabs} from '../../components/mollecules' +import type {TabsType} from '../../global/type' import BacktestLoop from './backtestLoop' import BacktestPlayground from './backtestPlayground' @@ -12,14 +12,14 @@ import BacktestUpload from './backtestUpload' // Tabs Array const tabs: TabsType = [ { - Component: BacktestScanner, + Component: BacktestPlayground, index: 1, - label: 'Scanner', + label: 'Playground', }, { - Component: BacktestPlayground, + Component: BacktestScanner, index: 2, - label: 'Playground', + label: 'Scanner', }, { Component: BacktestLoop, diff --git a/src/Managing.WebApp/src/pages/botsPage/botList.tsx b/src/Managing.WebApp/src/pages/botsPage/botList.tsx index ccd012f..677c9d4 100644 --- a/src/Managing.WebApp/src/pages/botsPage/botList.tsx +++ b/src/Managing.WebApp/src/pages/botsPage/botList.tsx @@ -37,13 +37,13 @@ const BotList: React.FC = ({ list }) => { const [showManualPositionModal, setShowManualPositionModal] = useState(false) const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState(null) const [showTradesModal, setShowTradesModal] = useState(false) - const [selectedBotForTrades, setSelectedBotForTrades] = useState(null) + const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ identifier: string; agentName: string } | null>(null) - function getIsForWatchingBadge(isForWatchingOnly: boolean, name: string) { + function getIsForWatchingBadge(isForWatchingOnly: boolean, identifier: string) { const classes = baseBadgeClass() + (isForWatchingOnly ? ' bg-accent' : ' bg-primary') return ( -
@@ -280,7 +280,8 @@ const BotList: React.FC = ({ list }) => { /> { setShowTradesModal(false) setSelectedBotForTrades(null) diff --git a/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx b/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx new file mode 100644 index 0000000..871a8b1 --- /dev/null +++ b/src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx @@ -0,0 +1,101 @@ +import {useState} from 'react' +import {useForm} from 'react-hook-form' +import {useQuery, useQueryClient} from '@tanstack/react-query' +import {UserClient} from '../../generated/ManagingApi' +import Modal from '../../components/mollecules/Modal/Modal' +import useApiUrlStore from '../../app/store/apiStore' +import {Toast} from '../../components/mollecules' + +type UpdateAgentNameForm = { + agentName: string +} + +function UserInfoSettings() { + const [showUpdateModal, setShowUpdateModal] = useState(false) + const queryClient = useQueryClient() + const { apiUrl } = useApiUrlStore() + const api = new UserClient({}, apiUrl) + + const { data: user } = useQuery({ + queryKey: ['user'], + queryFn: () => api.user_GetCurrentUser(), + }) + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm() + + const onSubmit = async (data: UpdateAgentNameForm) => { + const toast = new Toast('Updating agent name') + try { + await api.user_UpdateAgentName(data.agentName) + queryClient.invalidateQueries({ queryKey: ['user'] }) + setShowUpdateModal(false) + toast.update('success', 'Agent name updated successfully') + } catch (error) { + console.error('Error updating agent name:', error) + toast.update('error', 'Failed to update agent name') + } + } + + return ( +
+
+

User Information

+ +
+
+ +

{user?.name}

+
+ +
+ +

{user?.agentName || 'Not set'}

+ +
+
+
+ + setShowUpdateModal(false)} + onSubmit={handleSubmit(onSubmit)} + titleHeader="Update Agent Name" + > +
+ + + {errors.agentName && ( + + )} +
+
+ +
+
+
+ ) +} + +export default UserInfoSettings \ No newline at end of file diff --git a/src/Managing.WebApp/src/pages/settingsPage/settings.tsx b/src/Managing.WebApp/src/pages/settingsPage/settings.tsx index 05317d6..bf80132 100644 --- a/src/Managing.WebApp/src/pages/settingsPage/settings.tsx +++ b/src/Managing.WebApp/src/pages/settingsPage/settings.tsx @@ -1,12 +1,13 @@ -import { useState } from 'react' +import {useState} from 'react' -import { Tabs } from '../../components/mollecules' +import {Tabs} from '../../components/mollecules' import AccountSettings from './account/accountSettings' import HealthChecks from './healthchecks/healthChecks' import MoneyManagementSettings from './moneymanagement/moneyManagement' import Theme from './theme' import DefaultConfig from './defaultConfig/defaultConfig' +import UserInfoSettings from './UserInfoSettings' type TabsType = { label: string @@ -17,28 +18,33 @@ type TabsType = { // Tabs Array const tabs: TabsType = [ { - Component: MoneyManagementSettings, + Component: UserInfoSettings, index: 1, + label: 'User Info', + }, + { + Component: MoneyManagementSettings, + index: 2, label: 'Money Management', }, { Component: AccountSettings, - index: 2, + index: 3, label: 'Account Settings', }, { Component: Theme, - index: 3, + index: 4, label: 'Theme', }, { Component: DefaultConfig, - index: 4, + index: 5, label: 'Quick Start Config', }, { Component: HealthChecks, - index: 5, + index: 6, label: 'Health Checks', }, ]