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
This commit is contained in:
Oda
2025-05-09 17:40:31 +02:00
committed by GitHub
parent a8eb0aaf02
commit 7c38c27b4a
54 changed files with 5164 additions and 641 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -108,6 +108,8 @@ public class BacktestController : BaseController
/// <param name="moneyManagementName">The name of the money management strategy to use.</param> /// <param name="moneyManagementName">The name of the money management strategy to use.</param>
/// <param name="moneyManagement">The money management strategy details, if not using a named strategy.</param> /// <param name="moneyManagement">The money management strategy details, if not using a named strategy.</param>
/// <param name="save">Whether to save the backtest results.</param> /// <param name="save">Whether to save the backtest results.</param>
/// <param name="cooldownPeriod">The cooldown period for the backtest.</param>
/// <param name="maxLossStreak">The maximum loss streak for the backtest.</param>
/// <returns>The result of the backtest.</returns> /// <returns>The result of the backtest.</returns>
[HttpPost] [HttpPost]
[Route("Run")] [Route("Run")]
@@ -122,7 +124,9 @@ public class BacktestController : BaseController
DateTime startDate, DateTime startDate,
DateTime endDate, DateTime endDate,
MoneyManagement? moneyManagement = null, MoneyManagement? moneyManagement = null,
bool save = false) bool save = false,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{ {
if (string.IsNullOrEmpty(accountName)) if (string.IsNullOrEmpty(accountName))
{ {
@@ -174,7 +178,9 @@ public class BacktestController : BaseController
endDate, endDate,
user, user,
watchOnly, watchOnly,
save); save,
cooldownPeriod: cooldownPeriod,
maxLossStreak: maxLossStreak);
break; break;
case BotType.FlippingBot: case BotType.FlippingBot:
backtestResult = await _backtester.RunFlippingBotBacktest( backtestResult = await _backtester.RunFlippingBotBacktest(
@@ -188,7 +194,9 @@ public class BacktestController : BaseController
endDate, endDate,
user, user,
watchOnly, watchOnly,
save); save,
cooldownPeriod: cooldownPeriod,
maxLossStreak: maxLossStreak);
break; break;
} }

View File

@@ -1,7 +1,7 @@
using Managing.Application.Abstractions.Services; using System.Security.Claims;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Users; using Managing.Domain.Users;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace Managing.Api.Controllers; namespace Managing.Api.Controllers;
@@ -10,7 +10,7 @@ namespace Managing.Api.Controllers;
[Produces("application/json")] [Produces("application/json")]
public abstract class BaseController : ControllerBase public abstract class BaseController : ControllerBase
{ {
private readonly IUserService _userService; public readonly IUserService _userService;
public BaseController(IUserService userService) public BaseController(IUserService userService)
{ {

View File

@@ -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.Abstractions.Services;
using Managing.Application.Bots;
using Managing.Application.Hubs; using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands; using Managing.Application.ManageBot.Commands;
using Managing.Common; using Managing.Common;
@@ -56,10 +56,10 @@ public class BotController : BaseController
/// <summary> /// <summary>
/// Checks if the current authenticated user owns the account associated with the specified bot or account name /// Checks if the current authenticated user owns the account associated with the specified bot or account name
/// </summary> /// </summary>
/// <param name="botName">The name of the bot to check</param> /// <param name="identifier">The identifier of the bot to check</param>
/// <param name="accountName">Optional account name to check when creating a new bot</param> /// <param name="accountName">Optional account name to check when creating a new bot</param>
/// <returns>True if the user owns the account, False otherwise</returns> /// <returns>True if the user owns the account, False otherwise</returns>
private async Task<bool> UserOwnsBotAccount(string botName, string accountName = null) private async Task<bool> UserOwnsBotAccount(string identifier, string accountName = null)
{ {
try try
{ {
@@ -78,12 +78,12 @@ public class BotController : BaseController
// For existing bots, check if the user owns the bot's account // For existing bots, check if the user owns the bot's account
var activeBots = _botService.GetActiveBots(); var activeBots = _botService.GetActiveBots();
var bot = activeBots.FirstOrDefault(b => b.Name == botName); var bot = activeBots.FirstOrDefault(b => b.Identifier == identifier);
if (bot == null) if (bot == null)
return true; // Bot doesn't exist yet, so no ownership conflict return true; // Bot doesn't exist yet, so no ownership conflict
var botAccountService = HttpContext.RequestServices.GetRequiredService<IAccountService>(); var botAccountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
var botAccount = await botAccountService.GetAccount(bot.AccountName, true, false); var botAccount = await botAccountService.GetAccount(bot.Config.AccountName, true, false);
// Compare the user names // Compare the user names
return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name; return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name;
} }
@@ -106,7 +106,7 @@ public class BotController : BaseController
try try
{ {
// Check if user owns the account // 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"); 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}"); $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
} }
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker, var config = new TradingBotConfig
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user, {
request.IsForWatchOnly, request.InitialTradingBalance)); 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(); await NotifyBotSubscriberAsync();
return Ok(result); return Ok(result);
@@ -149,22 +159,22 @@ public class BotController : BaseController
/// Stops a bot specified by type and name. /// Stops a bot specified by type and name.
/// </summary> /// </summary>
/// <param name="botType">The type of the bot to stop.</param> /// <param name="botType">The type of the bot to stop.</param>
/// <param name="botName">The name of the bot to stop.</param> /// <param name="identifier">The identifier of the bot to stop.</param>
/// <returns>A string indicating the result of the stop operation.</returns> /// <returns>A string indicating the result of the stop operation.</returns>
[HttpGet] [HttpGet]
[Route("Stop")] [Route("Stop")]
public async Task<ActionResult<string>> Stop(BotType botType, string botName) public async Task<ActionResult<string>> Stop(BotType botType, string identifier)
{ {
try try
{ {
// Check if user owns the account // 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"); return Forbid("You don't have permission to stop this bot");
} }
var result = await _mediator.Send(new StopBotCommand(botType, botName)); var result = await _mediator.Send(new StopBotCommand(botType, identifier));
_logger.LogInformation($"{botType} type called {botName} is now {result}"); _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}");
await NotifyBotSubscriberAsync(); await NotifyBotSubscriberAsync();
@@ -180,21 +190,21 @@ public class BotController : BaseController
/// <summary> /// <summary>
/// Deletes a bot specified by name. /// Deletes a bot specified by name.
/// </summary> /// </summary>
/// <param name="botName">The name of the bot to delete.</param> /// <param name="identifier">The identifier of the bot to delete.</param>
/// <returns>A boolean indicating the result of the delete operation.</returns> /// <returns>A boolean indicating the result of the delete operation.</returns>
[HttpDelete] [HttpDelete]
[Route("Delete")] [Route("Delete")]
public async Task<ActionResult<bool>> Delete(string botName) public async Task<ActionResult<bool>> Delete(string identifier)
{ {
try try
{ {
// Check if user owns the account // 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"); 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(); await NotifyBotSubscriberAsync();
return Ok(result); return Ok(result);
} }
@@ -235,9 +245,9 @@ public class BotController : BaseController
foreach (var bot in userBots) 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", await _hubContext.Clients.All.SendAsync("SendNotification",
$"Bot {bot.Name} paused by {user.Name}.", "Info"); $"Bot {bot.Identifier} paused by {user.Name}.", "Info");
} }
await NotifyBotSubscriberAsync(); await NotifyBotSubscriberAsync();
@@ -254,22 +264,22 @@ public class BotController : BaseController
/// Restarts a bot specified by type and name. /// Restarts a bot specified by type and name.
/// </summary> /// </summary>
/// <param name="botType">The type of the bot to restart.</param> /// <param name="botType">The type of the bot to restart.</param>
/// <param name="botName">The name of the bot to restart.</param> /// <param name="identifier">The identifier of the bot to restart.</param>
/// <returns>A string indicating the result of the restart operation.</returns> /// <returns>A string indicating the result of the restart operation.</returns>
[HttpGet] [HttpGet]
[Route("Restart")] [Route("Restart")]
public async Task<ActionResult<string>> Restart(BotType botType, string botName) public async Task<ActionResult<string>> Restart(BotType botType, string identifier)
{ {
try try
{ {
// Check if user owns the account // 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"); return Forbid("You don't have permission to restart this bot");
} }
var result = await _mediator.Send(new RestartBotCommand(botType, botName)); var result = await _mediator.Send(new RestartBotCommand(botType, identifier));
_logger.LogInformation($"{botType} type called {botName} is now {result}"); _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}");
await NotifyBotSubscriberAsync(); await NotifyBotSubscriberAsync();
@@ -314,15 +324,15 @@ public class BotController : BaseController
{ {
// We can't directly restart a bot with just BotType and Name // 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 // 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 // Get the saved bot backup
var backup = _botService.GetBotBackup(bot.Name); var backup = _botService.GetBotBackup(bot.Identifier);
if (backup != null) if (backup != null)
{ {
_botService.StartBotFromBackup(backup); _botService.StartBotFromBackup(backup);
await _hubContext.Clients.All.SendAsync("SendNotification", 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
/// <summary> /// <summary>
/// Toggles the watching status of a bot specified by name. /// Toggles the watching status of a bot specified by name.
/// </summary> /// </summary>
/// <param name="botName">The name of the bot to toggle watching status.</param> /// <param name="identifier">The identifier of the bot to toggle watching status.</param>
/// <returns>A string indicating the new watching status of the bot.</returns> /// <returns>A string indicating the new watching status of the bot.</returns>
[HttpGet] [HttpGet]
[Route("ToggleIsForWatching")] [Route("ToggleIsForWatching")]
public async Task<ActionResult<string>> ToggleIsForWatching(string botName) public async Task<ActionResult<string>> ToggleIsForWatching(string identifier)
{ {
try try
{ {
// Check if user owns the account // 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"); return Forbid("You don't have permission to modify this bot");
} }
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName)); var result = await _mediator.Send(new ToggleIsForWatchingCommand(identifier));
_logger.LogInformation($"{botName} bot is now {result}"); _logger.LogInformation($"Bot with identifier {identifier} is now {result}");
await NotifyBotSubscriberAsync(); await NotifyBotSubscriberAsync();
@@ -397,13 +407,15 @@ public class BotController : BaseController
Candles = item.Candles.DistinctBy(c => c.Date).ToList(), Candles = item.Candles.DistinctBy(c => c.Date).ToList(),
WinRate = item.GetWinRate(), WinRate = item.GetWinRate(),
ProfitAndLoss = item.GetProfitAndLoss(), ProfitAndLoss = item.GetProfitAndLoss(),
Timeframe = item.Timeframe, Timeframe = item.Config.Timeframe,
Ticker = item.Ticker, Ticker = item.Config.Ticker,
Scenario = item.ScenarioName, Scenario = item.Config.ScenarioName,
IsForWatchingOnly = item.IsForWatchingOnly, IsForWatchingOnly = item.Config.IsForWatchingOnly,
BotType = item.BotType, BotType = item.Config.BotType,
AccountName = item.AccountName, AccountName = item.Config.AccountName,
MoneyManagement = item.MoneyManagement MoneyManagement = item.Config.MoneyManagement,
Identifier = item.Identifier,
AgentName = item.User.AgentName
}); });
} }
@@ -431,22 +443,22 @@ public class BotController : BaseController
try try
{ {
// Check if user owns the account // 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"); return Forbid("You don't have permission to open positions for this bot");
} }
var activeBots = _botService.GetActiveBots(); 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) 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()) 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( var position = await bot.OpenPositionManually(
@@ -475,29 +487,30 @@ public class BotController : BaseController
try try
{ {
// Check if user owns the account // 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"); return Forbid("You don't have permission to close positions for this bot");
} }
var activeBots = _botService.GetActiveBots(); 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) 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()) 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 // Find the position to close
var position = bot.Positions.FirstOrDefault(p => p.Identifier == request.PositionId); var position = bot.Positions.FirstOrDefault(p => p.Identifier == request.PositionId);
if (position == null) 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 // Find the signal associated with this position
@@ -528,18 +541,85 @@ public class BotController : BaseController
} }
} }
/// <summary>
/// Request model for opening a position manually
/// </summary>
public class OpenPositionManuallyRequest
{
/// <summary>
/// The identifier of the bot
/// </summary>
public string Identifier { get; set; }
/// <summary>
/// The direction of the position
/// </summary>
public TradeDirection Direction { get; set; }
}
/// <summary> /// <summary>
/// Request model for closing a position /// Request model for closing a position
/// </summary> /// </summary>
public class ClosePositionRequest public class ClosePositionRequest
{ {
/// <summary> /// <summary>
/// The name of the bot /// The identifier of the bot
/// </summary> /// </summary>
public string BotName { get; set; } public string Identifier { get; set; }
/// <summary> /// <summary>
/// The ID of the position to close /// The ID of the position to close
/// </summary> /// </summary>
public string PositionId { get; set; } public string PositionId { get; set; }
}
/// <summary>
/// Request model for starting a bot
/// </summary>
public class StartBotRequest
{
/// <summary>
/// The type of bot to start
/// </summary>
public BotType BotType { get; set; }
/// <summary>
/// The identifier of the bot
/// </summary>
public string Identifier { get; set; }
/// <summary>
/// The ticker to trade
/// </summary>
public Ticker Ticker { get; set; }
/// <summary>
/// The scenario to use
/// </summary>
public string Scenario { get; set; }
/// <summary>
/// The timeframe to use
/// </summary>
public Timeframe Timeframe { get; set; }
/// <summary>
/// The account name to use
/// </summary>
public string AccountName { get; set; }
/// <summary>
/// The money management name to use
/// </summary>
public string MoneyManagementName { get; set; }
/// <summary>
/// Whether the bot is for watching only
/// </summary>
public bool IsForWatchOnly { get; set; }
/// <summary>
/// The initial trading balance
/// </summary>
public decimal InitialTradingBalance { get; set; }
} }

View File

@@ -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.Abstractions.Services;
using Managing.Application.Hubs; using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands; using Managing.Application.ManageBot.Commands;
using Managing.Application.Workers.Abstractions; using Managing.Application.Workers.Abstractions;
using Managing.Api.Models.Responses;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.Shared.Helpers; using Managing.Domain.Shared.Helpers;
using Managing.Domain.Statistics; using Managing.Domain.Statistics;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using System.Linq;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Api.Controllers; namespace Managing.Api.Controllers;
@@ -65,20 +63,114 @@ public class DataController : ControllerBase
/// <param name="timeframe">The timeframe for which to retrieve tickers.</param> /// <param name="timeframe">The timeframe for which to retrieve tickers.</param>
/// <returns>An array of tickers.</returns> /// <returns>An array of tickers.</returns>
[HttpPost("GetTickers")] [HttpPost("GetTickers")]
public async Task<ActionResult<Ticker[]>> GetTickers(Timeframe timeframe) public async Task<ActionResult<List<TickerInfos>>> GetTickers(Timeframe timeframe)
{ {
var cacheKey = string.Concat(timeframe.ToString()); var cacheKey = string.Concat(timeframe.ToString());
var tickers = _cacheService.GetValue<List<Ticker>>(cacheKey); var tickers = _cacheService.GetValue<List<TickerInfos>>(cacheKey);
if (tickers == null || tickers.Count == 0) 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)); _cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2));
} }
return Ok(tickers); return Ok(tickers);
} }
private List<TickerInfos> MapTickerToTickerInfos(List<Ticker> availableTicker)
{
var tickerInfos = new List<TickerInfos>();
var tokens = new Dictionary<string, string>
{
{ "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;
}
/// <summary> /// <summary>
/// Retrieves the latest spotlight overview, using caching to enhance response times. /// Retrieves the latest spotlight overview, using caching to enhance response times.
/// </summary> /// </summary>
@@ -222,24 +314,24 @@ public class DataController : ControllerBase
public async Task<ActionResult<List<UserStrategyDetailsViewModel>>> GetUserStrategies(string agentName) public async Task<ActionResult<List<UserStrategyDetailsViewModel>>> GetUserStrategies(string agentName)
{ {
string cacheKey = $"UserStrategies_{agentName}"; string cacheKey = $"UserStrategies_{agentName}";
// Check if the user strategy details are already cached // Check if the user strategy details are already cached
var cachedDetails = _cacheService.GetValue<List<UserStrategyDetailsViewModel>>(cacheKey); var cachedDetails = _cacheService.GetValue<List<UserStrategyDetailsViewModel>>(cacheKey);
if (cachedDetails != null && cachedDetails.Count > 0) if (cachedDetails != null && cachedDetails.Count > 0)
{ {
return Ok(cachedDetails); return Ok(cachedDetails);
} }
// Get all strategies for the specified user // Get all strategies for the specified user
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName)); var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
// Convert to detailed view model with additional information // Convert to detailed view model with additional information
var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy)).ToList(); var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy)).ToList();
// Cache the results for 5 minutes // Cache the results for 5 minutes
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5)); _cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
return Ok(result); return Ok(result);
} }
@@ -253,32 +345,32 @@ public class DataController : ControllerBase
public async Task<ActionResult<UserStrategyDetailsViewModel>> GetUserStrategy(string agentName, string strategyName) public async Task<ActionResult<UserStrategyDetailsViewModel>> GetUserStrategy(string agentName, string strategyName)
{ {
string cacheKey = $"UserStrategy_{agentName}_{strategyName}"; string cacheKey = $"UserStrategy_{agentName}_{strategyName}";
// Check if the user strategy details are already cached // Check if the user strategy details are already cached
var cachedDetails = _cacheService.GetValue<UserStrategyDetailsViewModel>(cacheKey); var cachedDetails = _cacheService.GetValue<UserStrategyDetailsViewModel>(cacheKey);
if (cachedDetails != null) if (cachedDetails != null)
{ {
return Ok(cachedDetails); return Ok(cachedDetails);
} }
// Get the specific strategy for the user // Get the specific strategy for the user
var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName)); var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName));
if (strategy == null) if (strategy == null)
{ {
return NotFound($"Strategy '{strategyName}' not found for user '{agentName}'"); return NotFound($"Strategy '{strategyName}' not found for user '{agentName}'");
} }
// Map the strategy to a view model using the shared method // Map the strategy to a view model using the shared method
var result = MapStrategyToViewModel(strategy); var result = MapStrategyToViewModel(strategy);
// Cache the results for 5 minutes // Cache the results for 5 minutes
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5)); _cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
return Ok(result); return Ok(result);
} }
/// <summary> /// <summary>
/// Maps a trading bot to a strategy view model with detailed statistics /// Maps a trading bot to a strategy view model with detailed statistics
/// </summary> /// </summary>
@@ -288,7 +380,7 @@ public class DataController : ControllerBase
{ {
// Get the runtime directly from the bot // Get the runtime directly from the bot
TimeSpan runtimeSpan = strategy.GetRuntime(); TimeSpan runtimeSpan = strategy.GetRuntime();
// Get the startup time from the bot's internal property // Get the startup time from the bot's internal property
// If bot is not running, we use MinValue as a placeholder // If bot is not running, we use MinValue as a placeholder
DateTime startupTime = DateTime.MinValue; DateTime startupTime = DateTime.MinValue;
@@ -296,30 +388,30 @@ public class DataController : ControllerBase
{ {
startupTime = bot.StartupTime; startupTime = bot.StartupTime;
} }
// Calculate ROI percentage based on PnL relative to account value // Calculate ROI percentage based on PnL relative to account value
decimal pnl = strategy.GetProfitAndLoss(); decimal pnl = strategy.GetProfitAndLoss();
// If we had initial investment amount, we could calculate ROI like: // If we had initial investment amount, we could calculate ROI like:
decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account
decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0; decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0;
// Calculate volume statistics // Calculate volume statistics
decimal totalVolume = TradingBox.GetTotalVolumeTraded(strategy.Positions); decimal totalVolume = TradingBox.GetTotalVolumeTraded(strategy.Positions);
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(strategy.Positions); decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(strategy.Positions);
// Calculate win/loss statistics // Calculate win/loss statistics
(int wins, int losses) = TradingBox.GetWinLossCount(strategy.Positions); (int wins, int losses) = TradingBox.GetWinLossCount(strategy.Positions);
// Calculate ROI for last 24h // Calculate ROI for last 24h
decimal roiLast24h = TradingBox.GetLast24HROI(strategy.Positions); decimal roiLast24h = TradingBox.GetLast24HROI(strategy.Positions);
return new UserStrategyDetailsViewModel return new UserStrategyDetailsViewModel
{ {
Name = strategy.Name, Name = strategy.Name,
StrategyName = strategy.ScenarioName, StrategyName = strategy.Config.ScenarioName,
State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" : State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" :
strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED", strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED",
PnL = pnl, PnL = pnl,
ROIPercentage = roi, ROIPercentage = roi,
ROILast24H = roiLast24h, ROILast24H = roiLast24h,
@@ -329,7 +421,8 @@ public class DataController : ControllerBase
VolumeLast24H = volumeLast24h, VolumeLast24H = volumeLast24h,
Wins = wins, Wins = wins,
Losses = losses, 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 timeFilter = "Total"; // Default to Total if invalid
} }
string cacheKey = $"PlatformSummary_{timeFilter}"; string cacheKey = $"PlatformSummary_{timeFilter}";
// Check if the platform summary is already cached // Check if the platform summary is already cached
var cachedSummary = _cacheService.GetValue<PlatformSummaryViewModel>(cacheKey); var cachedSummary = _cacheService.GetValue<PlatformSummaryViewModel>(cacheKey);
if (cachedSummary != null) if (cachedSummary != null)
{ {
return Ok(cachedSummary); return Ok(cachedSummary);
} }
// Get all agents and their strategies // Get all agents and their strategies
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter)); var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
// Create the platform summary // Create the platform summary
var summary = new PlatformSummaryViewModel var summary = new PlatformSummaryViewModel
{ {
@@ -368,50 +461,50 @@ public class DataController : ControllerBase
TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count), TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count),
TimeFilter = timeFilter TimeFilter = timeFilter
}; };
// Calculate total platform metrics // Calculate total platform metrics
decimal totalPlatformPnL = 0; decimal totalPlatformPnL = 0;
decimal totalPlatformVolume = 0; decimal totalPlatformVolume = 0;
decimal totalPlatformVolumeLast24h = 0; decimal totalPlatformVolumeLast24h = 0;
// Create summaries for each agent // Create summaries for each agent
foreach (var agent in agentsWithStrategies) foreach (var agent in agentsWithStrategies)
{ {
var user = agent.Key; var user = agent.Key;
var strategies = agent.Value; var strategies = agent.Value;
if (strategies.Count == 0) if (strategies.Count == 0)
{ {
continue; // Skip agents with no strategies continue; // Skip agents with no strategies
} }
// Combine all positions from all strategies // Combine all positions from all strategies
var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList(); var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList();
// Calculate agent metrics // Calculate agent metrics
decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter); decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter);
decimal pnlLast24h = TradingBox.GetPnLInTimeRange(allPositions, "24H"); decimal pnlLast24h = TradingBox.GetPnLInTimeRange(allPositions, "24H");
decimal totalROI = TradingBox.GetROIInTimeRange(allPositions, timeFilter); decimal totalROI = TradingBox.GetROIInTimeRange(allPositions, timeFilter);
decimal roiLast24h = TradingBox.GetROIInTimeRange(allPositions, "24H"); decimal roiLast24h = TradingBox.GetROIInTimeRange(allPositions, "24H");
(int wins, int losses) = TradingBox.GetWinLossCountInTimeRange(allPositions, timeFilter); (int wins, int losses) = TradingBox.GetWinLossCountInTimeRange(allPositions, timeFilter);
// Calculate trading volumes // Calculate trading volumes
decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions); decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions);
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions); decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions);
// Calculate win rate // Calculate win rate
int averageWinRate = 0; int averageWinRate = 0;
if (wins + losses > 0) if (wins + losses > 0)
{ {
averageWinRate = (wins * 100) / (wins + losses); averageWinRate = (wins * 100) / (wins + losses);
} }
// Add to agent summaries // Add to agent summaries
var agentSummary = new AgentSummaryViewModel var agentSummary = new AgentSummaryViewModel
{ {
Username = user.Name, AgentName = user.AgentName,
TotalPnL = totalPnL, TotalPnL = totalPnL,
PnLLast24h = pnlLast24h, PnLLast24h = pnlLast24h,
TotalROI = totalROI, TotalROI = totalROI,
@@ -423,26 +516,26 @@ public class DataController : ControllerBase
TotalVolume = totalVolume, TotalVolume = totalVolume,
VolumeLast24h = volumeLast24h VolumeLast24h = volumeLast24h
}; };
summary.AgentSummaries.Add(agentSummary); summary.AgentSummaries.Add(agentSummary);
// Add to platform totals // Add to platform totals
totalPlatformPnL += totalPnL; totalPlatformPnL += totalPnL;
totalPlatformVolume += totalVolume; totalPlatformVolume += totalVolume;
totalPlatformVolumeLast24h += volumeLast24h; totalPlatformVolumeLast24h += volumeLast24h;
} }
// Set the platform totals // Set the platform totals
summary.TotalPlatformPnL = totalPlatformPnL; summary.TotalPlatformPnL = totalPlatformPnL;
summary.TotalPlatformVolume = totalPlatformVolume; summary.TotalPlatformVolume = totalPlatformVolume;
summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h; summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h;
// Sort agent summaries by total PnL (highest first) // Sort agent summaries by total PnL (highest first)
summary.AgentSummaries = summary.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList(); summary.AgentSummaries = summary.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList();
// Cache the results for 5 minutes // Cache the results for 5 minutes
_cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5)); _cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5));
return Ok(summary); return Ok(summary);
} }
} }

View File

@@ -1,6 +1,7 @@
using Managing.Api.Authorization; using Managing.Api.Authorization;
using Managing.Api.Models.Requests; using Managing.Api.Models.Requests;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Users;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -12,10 +13,9 @@ namespace Managing.Api.Controllers;
[ApiController] [ApiController]
[Route("[controller]")] [Route("[controller]")]
[Produces("application/json")] [Produces("application/json")]
public class UserController : ControllerBase public class UserController : BaseController
{ {
private IConfiguration _config; private IConfiguration _config;
private readonly IUserService _userService;
private readonly IJwtUtils _jwtUtils; private readonly IJwtUtils _jwtUtils;
/// <summary> /// <summary>
@@ -25,9 +25,9 @@ public class UserController : ControllerBase
/// <param name="userService">Service for user-related operations.</param> /// <param name="userService">Service for user-related operations.</param>
/// <param name="jwtUtils">Utility for JWT token operations.</param> /// <param name="jwtUtils">Utility for JWT token operations.</param>
public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils) public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils)
: base(userService)
{ {
_config = config; _config = config;
_userService = userService;
_jwtUtils = jwtUtils; _jwtUtils = jwtUtils;
} }
@@ -49,5 +49,30 @@ public class UserController : ControllerBase
} }
return Unauthorized(); return Unauthorized();
}
/// <summary>
/// Gets the current user's information.
/// </summary>
/// <returns>The current user's information.</returns>
[HttpGet]
public async Task<ActionResult<User>> GetCurrentUser()
{
var user = await base.GetUser();
return Ok(user);
} }
}
/// <summary>
/// Updates the agent name for the current user.
/// </summary>
/// <param name="agentName">The new agent name to set.</param>
/// <returns>The updated user with the new agent name.</returns>
[HttpPut("agent-name")]
public async Task<ActionResult<User>> UpdateAgentName([FromBody] string agentName)
{
var user = await GetUser();
var updatedUser = await _userService.UpdateAgentName(user, agentName);
return Ok(updatedUser);
}
}

View File

@@ -20,5 +20,12 @@ namespace Managing.Api.Models.Requests
[Required] [Required]
[Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")] [Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")]
public decimal InitialTradingBalance { get; set; } public decimal InitialTradingBalance { get; set; }
/// <summary>
/// Cooldown period in minutes between trades
/// </summary>
[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
} }
} }

View File

@@ -6,9 +6,9 @@ namespace Managing.Api.Models.Responses
public class AgentSummaryViewModel public class AgentSummaryViewModel
{ {
/// <summary> /// <summary>
/// Username of the agent /// AgentName of the agent
/// </summary> /// </summary>
public string Username { get; set; } public string AgentName { get; set; }
/// <summary> /// <summary>
/// Total profit and loss in USD /// Total profit and loss in USD

View File

@@ -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; }
}

View File

@@ -1,8 +1,8 @@
using Managing.Domain.Candles; using System.ComponentModel.DataAnnotations;
using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using System.ComponentModel.DataAnnotations;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Api.Models.Responses namespace Managing.Api.Models.Responses
@@ -23,5 +23,7 @@ namespace Managing.Api.Models.Responses
[Required] public BotType BotType { get; internal set; } [Required] public BotType BotType { get; internal set; }
[Required] public string AccountName { get; internal set; } [Required] public string AccountName { get; internal set; }
[Required] public MoneyManagement MoneyManagement { get; internal set; } [Required] public MoneyManagement MoneyManagement { get; internal set; }
[Required] public string Identifier { get; set; }
[Required] public string AgentName { get; set; }
} }
} }

View File

@@ -4,6 +4,7 @@ using HealthChecks.UI.Client;
using Managing.Api.Authorization; using Managing.Api.Authorization;
using Managing.Api.Filters; using Managing.Api.Filters;
using Managing.Api.HealthChecks; using Managing.Api.HealthChecks;
using Managing.Api.Workers;
using Managing.Application.Hubs; using Managing.Application.Hubs;
using Managing.Bootstrap; using Managing.Bootstrap;
using Managing.Common; using Managing.Common;
@@ -195,7 +196,10 @@ builder.Services.AddSwaggerGen(options =>
}); });
builder.WebHost.SetupDiscordBot(); builder.WebHost.SetupDiscordBot();
// builder.Services.AddHostedService<BotManagerWorker>(); if (builder.Configuration.GetValue<bool>("EnableBotManager", false))
{
builder.Services.AddHostedService<BotManagerWorker>();
}
// App // App
var app = builder.Build(); var app = builder.Build();

View File

@@ -24,5 +24,6 @@
"ElasticConfiguration": { "ElasticConfiguration": {
"Uri": "http://elasticsearch:9200" "Uri": "http://elasticsearch:9200"
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"EnableBotManager": true
} }

View File

@@ -4,6 +4,7 @@ namespace Managing.Application.Abstractions.Repositories;
public interface IUserRepository public interface IUserRepository
{ {
Task<User> GetUserByAgentNameAsync(string agentName);
Task<User> GetUserByNameAsync(string name); Task<User> GetUserByNameAsync(string name);
Task InsertUserAsync(User user); Task InsertUserAsync(User user);
Task UpdateUser(User user); Task UpdateUser(User user);

View File

@@ -22,7 +22,9 @@ namespace Managing.Application.Abstractions.Services
User user = null, User user = null,
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null); List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0);
Task<Backtest> RunFlippingBotBacktest( Task<Backtest> RunFlippingBotBacktest(
Account account, Account account,
@@ -36,16 +38,34 @@ namespace Managing.Application.Abstractions.Services
User user = null, User user = null,
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null); List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0);
bool DeleteBacktest(string id); bool DeleteBacktest(string id);
bool DeleteBacktests(); bool DeleteBacktests();
Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Task<Backtest> RunScalpingBotBacktest(
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null); Account account,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe,
List<Candle> candles,
decimal balance,
User user = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0);
Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario, Task<Backtest> RunFlippingBotBacktest(
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null); Account account,
MoneyManagement moneyManagement,
Scenario scenario,
Timeframe timeframe,
List<Candle> candles,
decimal balance,
User user = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0);
// User-specific operations // User-specific operations
Task<IEnumerable<Backtest>> GetBacktestsByUser(User user); Task<IEnumerable<Backtest>> GetBacktestsByUser(User user);

View File

@@ -6,4 +6,6 @@ public interface IUserService
{ {
Task<User> Authenticate(string name, string address, string message, string signature); Task<User> Authenticate(string name, string address, string message, string signature);
Task<User> GetUserByAddressAsync(string address); Task<User> GetUserByAddressAsync(string address);
Task<User> UpdateAgentName(User user, string agentName);
User GetUser(string name);
} }

View File

@@ -50,7 +50,7 @@ public abstract class BaseWorker<T> : BackgroundService where T : class
{ {
worker = await _workerService.GetWorker(_workerType); worker = await _workerService.GetWorker(_workerType);
if (worker.IsActive) if (worker.IsActive || worker.WorkerType.Equals(WorkerType.BotManager))
{ {
await Run(cancellationToken); await Run(cancellationToken);
_executionCount++; _executionCount++;

View File

@@ -1,16 +1,15 @@
using Managing.Domain.Bots; using Managing.Application.Bots;
using Managing.Domain.MoneyManagements; using Managing.Domain.Bots;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions namespace Managing.Application.Abstractions
{ {
public interface IBotFactory 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); IBot CreateSimpleBot(string botName, Workflow workflow);
ITradingBot CreateScalpingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
} }
} }

View File

@@ -1,38 +1,30 @@
using Managing.Common; using Managing.Application.Bots;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Users; using Managing.Domain.Users;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions; namespace Managing.Application.Abstractions;
public interface IBotService public interface IBotService
{ {
void SaveOrUpdateBotBackup(BotBackup botBackup); void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data);
void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data);
void AddSimpleBotToCache(IBot bot); void AddSimpleBotToCache(IBot bot);
void AddTradingBotToCache(ITradingBot bot); void AddTradingBotToCache(ITradingBot bot);
List<ITradingBot> GetActiveBots(); List<ITradingBot> GetActiveBots();
IEnumerable<BotBackup> GetSavedBots(); IEnumerable<BotBackup> GetSavedBots();
void StartBotFromBackup(BotBackup backupBot); void StartBotFromBackup(BotBackup backupBot);
BotBackup GetBotBackup(string name); BotBackup GetBotBackup(string identifier);
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker, ITradingBot CreateScalpingBot(TradingBotConfig config);
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount); ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
ITradingBot CreateFlippingBot(TradingBotConfig config);
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker, ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
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);
IBot CreateSimpleBot(string botName, Workflow workflow); IBot CreateSimpleBot(string botName, Workflow workflow);
Task<string> StopBot(string requestName); Task<string> StopBot(string botName);
Task<bool> DeleteBot(string requestName); Task<bool> DeleteBot(string botName);
Task<string> RestartBot(string requestName); Task<string> RestartBot(string botName);
void DeleteBotBackup(string backupBotName); void DeleteBotBackup(string backupBotName);
void ToggleIsForWatchingOnly(string botName); void ToggleIsForWatchingOnly(string botName);
} }

View File

@@ -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.Bots;
using Managing.Domain.Candles; using Managing.Domain.Candles;
using Managing.Domain.MoneyManagements; using Managing.Domain.Scenarios;
using Managing.Domain.Strategies; using Managing.Domain.Strategies;
using Managing.Domain.Strategies.Base; using Managing.Domain.Strategies.Base;
using Managing.Domain.Trades; using Managing.Domain.Trades;
using Managing.Domain.Users;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.Abstractions namespace Managing.Application.Abstractions
{ {
public interface ITradingBot : IBot public interface ITradingBot : IBot
{ {
HashSet<Signal> Signals { get; set; } TradingBotConfig Config { get; set; }
List<Position> Positions { get; set; } Account Account { get; set; }
HashSet<IStrategy> Strategies { get; set; }
FixedSizeQueue<Candle> OptimizedCandles { get; set; } FixedSizeQueue<Candle> OptimizedCandles { get; set; }
HashSet<Candle> Candles { get; set; } HashSet<Candle> Candles { get; set; }
Timeframe Timeframe { get; set; } HashSet<Signal> Signals { get; set; }
HashSet<IStrategy> Strategies { get; set; } List<Position> Positions { get; set; }
Ticker Ticker { get; }
string ScenarioName { get; }
string AccountName { get; }
bool IsForWatchingOnly { get; set; }
MoneyManagement MoneyManagement { get; set; }
BotType BotType { get; set; }
Dictionary<DateTime, decimal> WalletBalances { get; set; } Dictionary<DateTime, decimal> WalletBalances { get; set; }
Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; } Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
User User { get; set; } DateTime StartupTime { get; set; }
string Identifier { get; set; } DateTime PreloadSince { get; set; }
int PreloadedCandlesCount { get; set; }
decimal Fee { get; set; }
Scenario Scenario { get; set; }
Task Run(); Task Run();
Task ToggleIsForWatchOnly(); Task ToggleIsForWatchOnly();
@@ -38,5 +37,6 @@ namespace Managing.Application.Abstractions
void LoadScenario(string scenarioName); void LoadScenario(string scenarioName);
void UpdateStrategiesValues(); void UpdateStrategiesValues();
Task LoadAccount(); Task LoadAccount();
Task<Position> OpenPositionManually(TradeDirection direction);
} }
} }

View File

@@ -1,6 +1,7 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Bots;
using Managing.Core; using Managing.Core;
using Managing.Core.FixedSizedQueue; using Managing.Core.FixedSizedQueue;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
@@ -64,10 +65,26 @@ namespace Managing.Application.Backtesting
User user = null, User user = null,
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle> initialCandles = null) List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{ {
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", var config = new TradingBotConfig
timeframe, isForWatchingOnly, balance); {
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); scalpingBot.LoadScenario(scenario.Name);
await scalpingBot.LoadAccount(); await scalpingBot.LoadAccount();
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate); var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
@@ -91,21 +108,6 @@ namespace Managing.Application.Backtesting
return result; return result;
} }
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate)
{
List<Candle> 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<Backtest> RunFlippingBotBacktest( public async Task<Backtest> RunFlippingBotBacktest(
Account account, Account account,
MoneyManagement moneyManagement, MoneyManagement moneyManagement,
@@ -118,10 +120,26 @@ namespace Managing.Application.Backtesting
User user = null, User user = null,
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle> initialCandles = null) List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{ {
var flippingBot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", var config = new TradingBotConfig
timeframe, false, balance); {
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); flippingBot.LoadScenario(scenario.Name);
await flippingBot.LoadAccount(); await flippingBot.LoadAccount();
@@ -146,13 +164,34 @@ namespace Managing.Application.Backtesting
return result; return result;
} }
public async Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, public async Task<Backtest> RunScalpingBotBacktest(
Account account,
MoneyManagement moneyManagement,
Scenario scenario, Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null) Timeframe timeframe,
List<Candle> candles,
decimal balance,
User user = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario", var config = new TradingBotConfig
timeframe, false, balance); {
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); bot.LoadScenario(scenario.Name);
await bot.LoadAccount(); await bot.LoadAccount();
@@ -167,13 +206,34 @@ namespace Managing.Application.Backtesting
return result; return result;
} }
public async Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement, public async Task<Backtest> RunFlippingBotBacktest(
Account account,
MoneyManagement moneyManagement,
Scenario scenario, Scenario scenario,
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null) Timeframe timeframe,
List<Candle> candles,
decimal balance,
User user = null,
decimal cooldownPeriod = 1,
int maxLossStreak = 0)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
var bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario", var config = new TradingBotConfig
timeframe, false, balance); {
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); bot.LoadScenario(scenario.Name);
await bot.LoadAccount(); await bot.LoadAccount();
@@ -188,6 +248,21 @@ namespace Managing.Application.Backtesting
return result; return result;
} }
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
DateTime startDate, DateTime endDate)
{
List<Candle> 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( private Backtest GetBacktestingResult(
Ticker ticker, Ticker ticker,
Scenario scenario, Scenario scenario,
@@ -239,7 +314,7 @@ namespace Managing.Application.Backtesting
var score = BacktestScorer.CalculateTotalScore(scoringParams); var score = BacktestScorer.CalculateTotalScore(scoringParams);
var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles, 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, FinalPnl = finalPnl,
WinRate = winRate, WinRate = winRate,

View File

@@ -1,7 +1,6 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -38,82 +37,58 @@ namespace Managing.Application.Bots.Base
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService); 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( return new ScalpingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
_botService, _botService,
initialTradingBalance, config);
isForWatchingOnly: isForWatchingOnly);
} }
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( return new ScalpingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
_botService, _botService,
initialTradingBalance, config);
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
} }
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( return new FlippingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
_botService, _botService,
initialTradingBalance, config);
isForWatchingOnly: isForWatchingOnly);
} }
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( return new FlippingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
_botService, _botService,
initialTradingBalance, config);
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
} }
} }
} }

View File

@@ -1,6 +1,5 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -8,39 +7,18 @@ namespace Managing.Application.Bots
{ {
public class FlippingBot : TradingBot public class FlippingBot : TradingBot
{ {
public FlippingBot(string accountName, public FlippingBot(
MoneyManagement moneyManagement,
string name,
string scenarioName,
IExchangeService exchangeService, IExchangeService exchangeService,
Ticker ticker,
ITradingService tradingService,
ILogger<TradingBot> logger, ILogger<TradingBot> logger,
Timeframe timeframe, ITradingService tradingService,
IAccountService accountService, IAccountService accountService,
IMessengerService messengerService, IMessengerService messengerService,
IBotService botService, IBotService botService,
decimal initialTradingBalance, TradingBotConfig config)
bool isForBacktest = false, : base(exchangeService, logger, tradingService, accountService, messengerService, botService, config)
bool isForWatchingOnly = false)
: base(accountName,
moneyManagement,
name,
ticker,
scenarioName,
exchangeService,
logger,
tradingService,
timeframe,
accountService,
messengerService,
botService,
initialTradingBalance,
isForBacktest,
isForWatchingOnly,
flipPosition: true)
{ {
BotType = BotType.FlippingBot; Config.BotType = BotType.FlippingBot;
Config.FlipPosition = true;
} }
public sealed override void Start() public sealed override void Start()

View File

@@ -1,6 +1,5 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Domain.MoneyManagements;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
@@ -8,38 +7,17 @@ namespace Managing.Application.Bots
{ {
public class ScalpingBot : TradingBot public class ScalpingBot : TradingBot
{ {
public ScalpingBot(string accountName, public ScalpingBot(
MoneyManagement moneyManagement,
string name,
string scenarioName,
IExchangeService exchangeService, IExchangeService exchangeService,
Ticker ticker,
ITradingService tradingService,
ILogger<TradingBot> logger, ILogger<TradingBot> logger,
Timeframe timeframe, ITradingService tradingService,
IAccountService accountService, IAccountService accountService,
IMessengerService messengerService, IMessengerService messengerService,
IBotService botService, IBotService botService,
decimal initialTradingBalance, TradingBotConfig config)
bool isForBacktest = false, : base(exchangeService, logger, tradingService, accountService, messengerService, botService, config)
bool isForWatchingOnly = false)
: base(accountName,
moneyManagement,
name,
ticker,
scenarioName,
exchangeService,
logger,
tradingService,
timeframe,
accountService,
messengerService,
botService,
initialTradingBalance,
isForBacktest,
isForWatchingOnly)
{ {
BotType = BotType.ScalpingBot; Config.BotType = BotType.ScalpingBot;
} }
public sealed override void Start() public sealed override void Start()

View File

@@ -45,7 +45,7 @@ namespace Managing.Application.Bots
public override void SaveBackup() public override void SaveBackup()
{ {
var data = JsonConvert.SerializeObject(_workflow); 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) public override void LoadBackup(BotBackup backup)

View File

@@ -28,76 +28,47 @@ public class TradingBot : Bot, ITradingBot
private readonly ITradingService TradingService; private readonly ITradingService TradingService;
private readonly IBotService BotService; private readonly IBotService BotService;
public TradingBotConfig Config { get; set; }
public Account Account { get; set; } public Account Account { get; set; }
public HashSet<IStrategy> Strategies { get; set; } public HashSet<IStrategy> Strategies { get; set; }
public FixedSizeQueue<Candle> OptimizedCandles { get; set; } public FixedSizeQueue<Candle> OptimizedCandles { get; set; }
public HashSet<Candle> Candles { get; set; } public HashSet<Candle> Candles { get; set; }
public HashSet<Signal> Signals { get; set; } public HashSet<Signal> Signals { get; set; }
public List<Position> Positions { get; set; } public List<Position> 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<DateTime, decimal> WalletBalances { get; set; } public Dictionary<DateTime, decimal> WalletBalances { get; set; }
public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; } public Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
public DateTime StartupTime { get; set; } public DateTime StartupTime { get; set; }
public DateTime PreloadSince { get; set; }
/// <summary> public int PreloadedCandlesCount { get; set; }
/// The dedicated trading balance for this bot in USD public decimal Fee { get; set; }
/// </summary> public Scenario Scenario { get; set; }
public decimal BotTradingBalance { get; set; }
public TradingBot( public TradingBot(
string accountName,
MoneyManagement moneyManagement,
string name,
Ticker ticker,
string scenarioName,
IExchangeService exchangeService, IExchangeService exchangeService,
ILogger<TradingBot> logger, ILogger<TradingBot> logger,
ITradingService tradingService, ITradingService tradingService,
Timeframe timeframe,
IAccountService accountService, IAccountService accountService,
IMessengerService messengerService, IMessengerService messengerService,
IBotService botService, IBotService botService,
decimal initialTradingBalance, TradingBotConfig config
bool isForBacktest = false, )
bool isForWatchingOnly = false, : base(config.AccountName)
bool flipPosition = false)
: base(name)
{ {
ExchangeService = exchangeService; ExchangeService = exchangeService;
AccountService = accountService; AccountService = accountService;
MessengerService = messengerService; MessengerService = messengerService;
TradingService = tradingService; TradingService = tradingService;
BotService = botService; BotService = botService;
Logger = logger;
if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount) if (config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
{ {
throw new ArgumentException( throw new ArgumentException(
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}", $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}",
nameof(initialTradingBalance)); nameof(config.BotTradingBalance));
} }
IsForWatchingOnly = isForWatchingOnly; Config = config;
FlipPosition = flipPosition;
AccountName = accountName;
MoneyManagement = moneyManagement;
Ticker = ticker;
ScenarioName = scenarioName;
Timeframe = timeframe;
IsForBacktest = isForBacktest;
Logger = logger;
BotTradingBalance = initialTradingBalance;
Strategies = new HashSet<IStrategy>(); Strategies = new HashSet<IStrategy>();
Signals = new HashSet<Signal>(); Signals = new HashSet<Signal>();
@@ -107,10 +78,10 @@ public class TradingBot : Bot, ITradingBot
WalletBalances = new Dictionary<DateTime, decimal>(); WalletBalances = new Dictionary<DateTime, decimal>();
StrategiesValues = new Dictionary<StrategyType, StrategiesResultBase>(); StrategiesValues = new Dictionary<StrategyType, StrategiesResultBase>();
if (!isForBacktest) if (!Config.IsForBacktest)
{ {
Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe); Interval = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe);
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe); PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(Config.Timeframe);
} }
} }
@@ -120,9 +91,9 @@ public class TradingBot : Bot, ITradingBot
// Load account synchronously // Load account synchronously
await LoadAccount(); await LoadAccount();
if (!IsForBacktest) if (!Config.IsForBacktest)
{ {
LoadScenario(ScenarioName); LoadScenario(Config.ScenarioName);
await PreloadCandles(); await PreloadCandles();
await CancelAllOrders(); await CancelAllOrders();
@@ -146,10 +117,10 @@ public class TradingBot : Bot, ITradingBot
public async Task LoadAccount() public async Task LoadAccount()
{ {
var account = await AccountService.GetAccount(AccountName, false, false); var account = await AccountService.GetAccount(Config.AccountName, false, false);
if (account == null) if (account == null)
{ {
Logger.LogWarning($"No account found for this {AccountName}"); Logger.LogWarning($"No account found for this {Config.AccountName}");
Stop(); Stop();
} }
else else
@@ -185,7 +156,7 @@ public class TradingBot : Bot, ITradingBot
public async Task Run() public async Task Run()
{ {
if (!IsForBacktest) if (!Config.IsForBacktest)
{ {
// Check broker balance before running // Check broker balance before running
var balance = await ExchangeService.GetBalance(Account, false); var balance = await ExchangeService.GetBalance(Account, false);
@@ -200,25 +171,25 @@ public class TradingBot : Bot, ITradingBot
Logger.LogInformation($"____________________{Name}____________________"); Logger.LogInformation($"____________________{Name}____________________");
Logger.LogInformation( 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(); var previousLastCandle = OptimizedCandles.LastOrDefault();
if (!IsForBacktest) if (!Config.IsForBacktest)
await UpdateCandles(); await UpdateCandles();
var currentLastCandle = OptimizedCandles.LastOrDefault(); var currentLastCandle = OptimizedCandles.LastOrDefault();
if (currentLastCandle != previousLastCandle || IsForBacktest) if (currentLastCandle != previousLastCandle || Config.IsForBacktest)
await UpdateSignals(OptimizedCandles); await UpdateSignals(OptimizedCandles);
else 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(); await ManagePositions();
if (!IsForBacktest) if (!Config.IsForBacktest)
{ {
SaveBackup(); SaveBackup();
UpdateStrategiesValues(); UpdateStrategiesValues();
@@ -248,7 +219,8 @@ public class TradingBot : Bot, ITradingBot
if (OptimizedCandles.Any()) if (OptimizedCandles.Any())
return; 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())) foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
{ {
@@ -272,25 +244,22 @@ public class TradingBot : Bot, ITradingBot
await AddSignal(signal); await AddSignal(signal);
} }
private async Task AddSignal(Signal signal) private async Task AddSignal(Signal signal)
{ {
// if (!IsForBacktest) if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest))
// TradingService.InsertSignal(signal);
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
signal.Status = SignalStatus.Expired; signal.Status = SignalStatus.Expired;
Signals.Add(signal); Signals.Add(signal);
var signalText = $"{ScenarioName} trigger a signal. Signal told you " + var signalText = $"{Config.ScenarioName} trigger a signal. Signal told you " +
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}"; $"to {signal.Direction} {Config.Ticker} on {Config.Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
Logger.LogInformation(signalText); 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; return;
var lastCandle = OptimizedCandles.Last(); 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())) 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) if (WalletBalances.Count == 0)
{ {
// WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest); // WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
WalletBalances[date] = BotTradingBalance; WalletBalances[date] = Config.BotTradingBalance;
return; return;
} }
@@ -353,24 +323,23 @@ public class TradingBot : Bot, ITradingBot
} }
} }
private async Task UpdatePosition(Signal signal, Position positionForSignal) private async Task UpdatePosition(Signal signal, Position positionForSignal)
{ {
try try
{ {
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}"); Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
var position = IsForBacktest var position = Config.IsForBacktest
? positionForSignal ? positionForSignal
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier); : TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
var positionsExchange = IsForBacktest var positionsExchange = Config.IsForBacktest
? new List<Position> { position } ? new List<Position> { position }
: await TradingService.GetBrokerPositions(Account); : 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) if (brokerPosition != null)
{ {
UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized); UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);
@@ -395,7 +364,7 @@ public class TradingBot : Bot, ITradingBot
if (position.Status == PositionStatus.New) if (position.Status == PositionStatus.New)
{ {
var orders = await ExchangeService.GetOpenOrders(Account, Ticker); var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
if (orders.Any()) if (orders.Any())
{ {
await LogInformation( await LogInformation(
@@ -420,9 +389,9 @@ public class TradingBot : Bot, ITradingBot
// Position might be partially filled, meaning that TPSL havent been sended yet // 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 // 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() ? OptimizedCandles.Last()
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow); : ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow);
if (positionForSignal.OriginDirection == TradeDirection.Long) if (positionForSignal.OriginDirection == TradeDirection.Long)
{ {
@@ -512,7 +481,6 @@ public class TradingBot : Bot, ITradingBot
} }
} }
private async Task OpenPosition(Signal signal) private async Task OpenPosition(Signal signal)
{ {
// Check if a position is already open // 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 var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
&& p.SignalIdentifier != signal.Identifier); && p.SignalIdentifier != signal.Identifier);
var lastPrice = IsForBacktest var lastPrice = Config.IsForBacktest
? OptimizedCandles.Last().Close ? OptimizedCandles.Last().Close
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow); : ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
// If position open // If position open
if (openedPosition != null) if (openedPosition != null)
@@ -542,7 +510,7 @@ public class TradingBot : Bot, ITradingBot
{ {
// An operation is already open for the opposite direction // An operation is already open for the opposite direction
// ==> Flip the position // ==> Flip the position
if (FlipPosition) if (Config.FlipPosition)
{ {
await LogInformation("Try to flip the position because of an opposite direction signal"); await LogInformation("Try to flip the position because of an opposite direction signal");
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true); await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
@@ -561,10 +529,8 @@ public class TradingBot : Bot, ITradingBot
} }
else 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); SetSignalStatus(signal.Identifier, SignalStatus.Expired);
return; return;
} }
@@ -575,15 +541,15 @@ public class TradingBot : Bot, ITradingBot
try try
{ {
var command = new OpenPositionRequest( var command = new OpenPositionRequest(
AccountName, Config.AccountName,
MoneyManagement, Config.MoneyManagement,
signal.Direction, signal.Direction,
Ticker, Config.Ticker,
PositionInitiator.Bot, PositionInitiator.Bot,
signal.Date, signal.Date,
User, User,
BotTradingBalance, Config.BotTradingBalance,
IsForBacktest, Config.IsForBacktest,
lastPrice, lastPrice,
signalIdentifier: signal.Identifier); signalIdentifier: signal.Identifier);
@@ -598,7 +564,7 @@ public class TradingBot : Bot, ITradingBot
{ {
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen); SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
if (!IsForBacktest) if (!Config.IsForBacktest)
{ {
await MessengerService.SendPosition(position); await MessengerService.SendPosition(position);
} }
@@ -622,25 +588,142 @@ public class TradingBot : Bot, ITradingBot
} }
} }
private bool CanOpenPosition(Signal signal) private async Task<bool> 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; 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<bool> CheckLossStreak(Signal signal)
{
// If MaxLossStreak is 0, there's no limit
if (Config.MaxLossStreak <= 0)
{
return true; 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<bool> 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<bool> CheckCooldownPeriod(Signal signal)
{
var lastPosition = Positions.LastOrDefault(p => p.IsFinished() var lastPosition = Positions.LastOrDefault(p => p.IsFinished()
&& p.SignalIdentifier != signal.Identifier && p.SignalIdentifier != signal.Identifier
&& p.OriginDirection == signal.Direction); && p.OriginDirection == signal.Direction);
if (lastPosition == null) if (lastPosition == null)
{
return true; 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); 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, public async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
@@ -654,18 +737,18 @@ public class TradingBot : Bot, ITradingBot
} }
await LogInformation( 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}"); $"- Closing Position : {tradeClosingPosition}");
// Get status of position before closing it. The position might be already close by the exchange // 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"); Logger.LogInformation($"Trade already close on exchange");
await HandleClosedPosition(position); await HandleClosedPosition(position);
} }
else else
{ {
var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest); var command = new ClosePositionCommand(position, lastPrice, isForBacktest: Config.IsForBacktest);
try try
{ {
var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService) var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
@@ -710,12 +793,12 @@ public class TradingBot : Bot, ITradingBot
if (position.ProfitAndLoss != null) if (position.ProfitAndLoss != null)
{ {
// Add PnL (could be positive or negative) // Add PnL (could be positive or negative)
BotTradingBalance += position.ProfitAndLoss.Realized; Config.BotTradingBalance += position.ProfitAndLoss.Realized;
// Subtract fees // 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 else
@@ -723,7 +806,7 @@ public class TradingBot : Bot, ITradingBot
await LogWarning("Weird things happen - Trying to update position status, but no position found"); await LogWarning("Weird things happen - Trying to update position status, but no position found");
} }
if (!IsForBacktest) if (!Config.IsForBacktest)
{ {
await MessengerService.SendClosingPosition(position); await MessengerService.SendClosingPosition(position);
} }
@@ -733,15 +816,15 @@ public class TradingBot : Bot, ITradingBot
private async Task CancelAllOrders() private async Task CancelAllOrders()
{ {
if (!IsForBacktest && !IsForWatchingOnly) if (!Config.IsForBacktest && !Config.IsForWatchingOnly)
{ {
try try
{ {
var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker); var openOrders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
if (openOrders.Any()) if (openOrders.Any())
{ {
var openPositions = (await ExchangeService.GetBrokerPositions(Account)) var openPositions = (await ExchangeService.GetBrokerPositions(Account))
.Where(p => p.Ticker == Ticker); .Where(p => p.Ticker == Config.Ticker);
var cancelClose = openPositions.Any(); var cancelClose = openPositions.Any();
if (cancelClose) if (cancelClose)
@@ -750,15 +833,15 @@ public class TradingBot : Bot, ITradingBot
} }
else else
{ {
Logger.LogInformation($"Canceling all orders for {Ticker}"); Logger.LogInformation($"Canceling all orders for {Config.Ticker}");
await ExchangeService.CancelOrder(Account, Ticker); await ExchangeService.CancelOrder(Account, Config.Ticker);
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker); var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Config.Ticker);
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}"); Logger.LogInformation($"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}");
} }
} }
else else
{ {
Logger.LogInformation($"No need to cancel orders for {Ticker}"); Logger.LogInformation($"No need to cancel orders for {Config.Ticker}");
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -771,10 +854,11 @@ public class TradingBot : Bot, ITradingBot
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus) 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; Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
await LogInformation($"Position {signalIdentifier} new status {position.Status} => {positionStatus}");
} }
SetSignalStatus(signalIdentifier, SetSignalStatus(signalIdentifier,
@@ -864,8 +948,8 @@ public class TradingBot : Bot, ITradingBot
public async Task ToggleIsForWatchOnly() public async Task ToggleIsForWatchOnly()
{ {
IsForWatchingOnly = (!IsForWatchingOnly); Config.IsForWatchingOnly = !Config.IsForWatchingOnly;
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}"); await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {Config.IsForWatchingOnly}");
} }
private async Task LogInformation(string message) private async Task LogInformation(string message)
@@ -883,7 +967,7 @@ public class TradingBot : Bot, ITradingBot
private async Task SendTradeMessage(string message, bool isBadBehavior = false) private async Task SendTradeMessage(string message, bool isBadBehavior = false)
{ {
if (!IsForBacktest) if (!Config.IsForBacktest)
{ {
await MessengerService.SendTradeMessage(message, isBadBehavior); await MessengerService.SendTradeMessage(message, isBadBehavior);
} }
@@ -894,43 +978,47 @@ public class TradingBot : Bot, ITradingBot
var data = new TradingBotBackup var data = new TradingBotBackup
{ {
Name = Name, Name = Name,
BotType = BotType, BotType = Config.BotType,
Signals = Signals, Signals = Signals,
Positions = Positions, Positions = Positions,
Timeframe = Timeframe, Timeframe = Config.Timeframe,
Ticker = Ticker, Ticker = Config.Ticker,
ScenarioName = ScenarioName, ScenarioName = Config.ScenarioName,
AccountName = AccountName, AccountName = Config.AccountName,
IsForWatchingOnly = IsForWatchingOnly, IsForWatchingOnly = Config.IsForWatchingOnly,
WalletBalances = WalletBalances, WalletBalances = WalletBalances,
MoneyManagement = MoneyManagement, MoneyManagement = Config.MoneyManagement,
BotTradingBalance = BotTradingBalance, BotTradingBalance = Config.BotTradingBalance,
StartupTime = StartupTime, 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) public override void LoadBackup(BotBackup backup)
{ {
var data = JsonConvert.DeserializeObject<TradingBotBackup>(backup.Data); var data = JsonConvert.DeserializeObject<TradingBotBackup>(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; Signals = data.Signals;
Positions = data.Positions; Positions = data.Positions;
WalletBalances = data.WalletBalances; WalletBalances = data.WalletBalances;
// MoneyManagement = data.MoneyManagement; => loaded from database PreloadSince = data.StartupTime;
Timeframe = data.Timeframe;
Ticker = data.Ticker;
ScenarioName = data.ScenarioName;
AccountName = data.AccountName;
IsForWatchingOnly = data.IsForWatchingOnly;
BotTradingBalance = data.BotTradingBalance;
Identifier = backup.Identifier; Identifier = backup.Identifier;
User = backup.User; User = backup.User;
Status = backup.LastStatus;
// Restore the startup time if it was previously saved
if (data.StartupTime != DateTime.MinValue)
{
StartupTime = data.StartupTime;
}
} }
/// <summary> /// <summary>
@@ -949,7 +1037,8 @@ public class TradingBot : Bot, ITradingBot
} }
// Create a fake signal for manual position opening // 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); StrategyType.Stc, SignalType.Signal);
signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
signal.User = Account.User; // Assign user signal.User = Account.User; // Assign user
@@ -989,4 +1078,5 @@ public class TradingBotBackup
public MoneyManagement MoneyManagement { get; set; } public MoneyManagement MoneyManagement { get; set; }
public DateTime StartupTime { get; set; } public DateTime StartupTime { get; set; }
public decimal BotTradingBalance { get; set; } public decimal BotTradingBalance { get; set; }
public decimal CooldownPeriod { get; set; }
} }

View File

@@ -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
}

View File

@@ -3,13 +3,13 @@ using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services; using Managing.Application.Abstractions.Services;
using Managing.Application.Bots; using Managing.Application.Bots;
using Managing.Common;
using Managing.Domain.Bots; using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements; using Managing.Domain.MoneyManagements;
using Managing.Domain.Users; using Managing.Domain.Users;
using Managing.Domain.Workflows; using Managing.Domain.Workflows;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot namespace Managing.Application.ManageBot
{ {
@@ -22,13 +22,14 @@ namespace Managing.Application.ManageBot
private readonly ILogger<TradingBot> _tradingBotLogger; private readonly ILogger<TradingBot> _tradingBotLogger;
private readonly ITradingService _tradingService; private readonly ITradingService _tradingService;
private readonly IMoneyManagementService _moneyManagementService; private readonly IMoneyManagementService _moneyManagementService;
private readonly IUserService _userService;
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks = private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
new ConcurrentDictionary<string, BotTaskWrapper>(); new ConcurrentDictionary<string, BotTaskWrapper>();
public BotService(IBotRepository botRepository, IExchangeService exchangeService, public BotService(IBotRepository botRepository, IExchangeService exchangeService,
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger, IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
ITradingService tradingService, IMoneyManagementService moneyManagementService) ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService)
{ {
_botRepository = botRepository; _botRepository = botRepository;
_exchangeService = exchangeService; _exchangeService = exchangeService;
@@ -37,11 +38,7 @@ namespace Managing.Application.ManageBot
_tradingBotLogger = tradingBotLogger; _tradingBotLogger = tradingBotLogger;
_tradingService = tradingService; _tradingService = tradingService;
_moneyManagementService = moneyManagementService; _moneyManagementService = moneyManagementService;
} _userService = userService;
public async void SaveOrUpdateBotBackup(BotBackup botBackup)
{
await _botRepository.InsertBotAsync(botBackup);
} }
public BotBackup GetBotBackup(string identifier) public BotBackup GetBotBackup(string identifier)
@@ -49,12 +46,13 @@ namespace Managing.Application.ManageBot
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier); 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); var backup = GetBotBackup(identifier);
if (backup != null) if (backup != null)
{ {
backup.LastStatus = status;
backup.Data = data; backup.Data = data;
_botRepository.UpdateBackupBot(backup); _botRepository.UpdateBackupBot(backup);
} }
@@ -62,6 +60,7 @@ namespace Managing.Application.ManageBot
{ {
var botBackup = new BotBackup var botBackup = new BotBackup
{ {
LastStatus = status,
User = user, User = user,
Identifier = identifier, Identifier = identifier,
BotType = botType, BotType = botType,
@@ -127,7 +126,7 @@ namespace Managing.Application.ManageBot
// null); // Assuming null is an acceptable parameter for workflow // null); // Assuming null is an acceptable parameter for workflow
// botTask = Task.Run(() => ((IBot)bot).Start()); // botTask = Task.Run(() => ((IBot)bot).Start());
// break; // break;
case Enums.BotType.ScalpingBot: case BotType.ScalpingBot:
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data); var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var scalpingMoneyManagement = var scalpingMoneyManagement =
_moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result; _moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
@@ -142,7 +141,7 @@ namespace Managing.Application.ManageBot
scalpingBotData.BotTradingBalance); scalpingBotData.BotTradingBalance);
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot)); botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
break; break;
case Enums.BotType.FlippingBot: case BotType.FlippingBot:
var flippingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data); var flippingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
var flippingMoneyManagement = var flippingMoneyManagement =
_moneyManagementService.GetMoneyMangement(flippingBotData.MoneyManagement.Name).Result; _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(); bot.Start();
var user = _userService.GetUser(backupBot.User.Name);
backupBot.User = user;
bot.LoadBackup(backupBot); bot.LoadBackup(backupBot);
return () => { }; return () => { };
} }
@@ -178,9 +180,9 @@ namespace Managing.Application.ManageBot
return new SimpleBot(botName, _tradingBotLogger, workflow, this); return new SimpleBot(botName, _tradingBotLogger, workflow, this);
} }
public async Task<string> StopBot(string botName) public async Task<string> StopBot(string identifier)
{ {
if (_botTasks.TryGetValue(botName, out var botWrapper)) if (_botTasks.TryGetValue(identifier, out var botWrapper))
{ {
if (botWrapper.BotInstance is IBot bot) 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<bool> DeleteBot(string botName) public async Task<bool> DeleteBot(string identifier)
{ {
if (_botTasks.TryRemove(botName, out var botWrapper)) if (_botTasks.TryRemove(identifier, out var botWrapper))
{ {
try try
{ {
@@ -205,7 +207,7 @@ namespace Managing.Application.ManageBot
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods 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; return true;
} }
catch (Exception e) catch (Exception e)
@@ -218,9 +220,9 @@ namespace Managing.Application.ManageBot
return false; return false;
} }
public Task<string> RestartBot(string botName) public Task<string> RestartBot(string identifier)
{ {
if (_botTasks.TryGetValue(botName, out var botWrapper)) if (_botTasks.TryGetValue(identifier, out var botWrapper))
{ {
if (botWrapper.BotInstance is IBot bot) 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) botTaskWrapper.BotInstance is ITradingBot tradingBot)
{ {
tradingBot.ToggleIsForWatchOnly().Wait(); tradingBot.ToggleIsForWatchOnly().Wait();
@@ -247,89 +249,159 @@ namespace Managing.Application.ManageBot
} }
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, 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) 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( return new ScalpingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
this, this,
initialTradingBalance, config);
isForWatchingOnly: isForWatchingOnly);
} }
public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, 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) 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( return new ScalpingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
this, this,
initialTradingBalance, config);
isForBacktest: true,
isForWatchingOnly: isForWatchingOnly);
} }
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, 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) 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( return new FlippingBot(
accountName,
moneyManagement,
name,
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
this, this,
initialTradingBalance, config);
isForWatchingOnly: isForWatchingOnly);
} }
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, 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) 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( return new FlippingBot(
accountName,
moneyManagement,
"BacktestBot",
scenario,
_exchangeService, _exchangeService,
ticker,
_tradingService,
_tradingBotLogger, _tradingBotLogger,
interval, _tradingService,
_accountService, _accountService,
_messengerService, _messengerService,
this, this,
initialTradingBalance, config);
isForBacktest: true, }
isForWatchingOnly: isForWatchingOnly);
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);
} }
} }
} }

View File

@@ -1,44 +1,20 @@
using Managing.Domain.Users; using Managing.Application.Bots;
using Managing.Domain.Users;
using MediatR; using MediatR;
using static Managing.Common.Enums;
namespace Managing.Application.ManageBot.Commands namespace Managing.Application.ManageBot.Commands
{ {
public class StartBotCommand : IRequest<string> public class StartBotCommand : IRequest<string>
{ {
public string Name { get; } public string Name { get; }
public BotType BotType { get; } public TradingBotConfig Config { get; }
public Ticker Ticker { get; internal set; } public User User { get; }
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 StartBotCommand(BotType botType, public StartBotCommand(TradingBotConfig config, string name, User user)
string name,
Ticker ticker,
string scenario,
Timeframe timeframe,
string accountName,
string moneyManagementName,
User user,
bool isForWatchingOnly = false,
decimal initialTradingBalance = 0)
{ {
BotType = botType; Config = config;
Name = name; Name = name;
Scenario = scenario;
Ticker = ticker;
Timeframe = timeframe;
IsForWatchingOnly = isForWatchingOnly;
AccountName = accountName;
MoneyManagementName = moneyManagementName;
User = user; User = user;
InitialTradingBalance = initialTradingBalance > 0 ? initialTradingBalance :
throw new ArgumentException("Initial trading balance must be greater than zero", nameof(initialTradingBalance));
} }
} }
} }

View File

@@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot
{ {
var allActiveBots = _botService.GetActiveBots(); var allActiveBots = _botService.GetActiveBots();
var userBots = allActiveBots var userBots = allActiveBots
.Where(bot => bot.User != null && bot.User.Name == request.UserName) .Where(bot => bot.User != null && bot.User.AgentName == request.UserName)
.ToList(); .ToList();
return Task.FromResult(userBots); return Task.FromResult(userBots);

View File

@@ -19,15 +19,14 @@ namespace Managing.Application.ManageBot
public Task<ITradingBot> Handle(GetUserStrategyCommand request, CancellationToken cancellationToken) public Task<ITradingBot> Handle(GetUserStrategyCommand request, CancellationToken cancellationToken)
{ {
var allActiveBots = _botService.GetActiveBots(); var allActiveBots = _botService.GetActiveBots();
// Find the specific strategy that matches both user and strategy name // Find the specific strategy that matches both user and strategy name
var strategy = allActiveBots var strategy = allActiveBots
.FirstOrDefault(bot => .FirstOrDefault(bot =>
bot.User != null && bot.User.AgentName == request.AgentName &&
bot.User.Name == request.AgentName && bot.Identifier == request.StrategyName);
bot.Name == request.StrategyName);
return Task.FromResult(strategy); return Task.FromResult(strategy);
} }
} }
} }

View File

@@ -31,6 +31,13 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
{ {
try try
{ {
if (backupBot.LastStatus == BotStatus.Down)
{
_logger.LogInformation("Skipping backup bot {Identifier} as it is marked as Down.",
backupBot.Identifier);
continue;
}
var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier); var activeBot = _botService.GetActiveBots().FirstOrDefault(b => b.Identifier == backupBot.Identifier);
if (activeBot == null) if (activeBot == null)

View File

@@ -29,38 +29,38 @@ namespace Managing.Application.ManageBot
{ {
BotStatus botStatus = BotStatus.Down; 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) 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()); 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 = // Ensure cooldown period is set
await _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName); if (request.Config.CooldownPeriod <= 0)
switch (request.BotType) {
request.Config.CooldownPeriod = 1; // Default to 1 minute if not set
}
switch (request.Config.BotType)
{ {
case BotType.SimpleBot: case BotType.SimpleBot:
var bot = _botFactory.CreateSimpleBot(request.Name, null); var bot = _botFactory.CreateSimpleBot(request.Name, null);
_botService.AddSimpleBotToCache(bot); _botService.AddSimpleBotToCache(bot);
return bot.GetStatus(); return bot.GetStatus();
case BotType.ScalpingBot: case BotType.ScalpingBot:
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name, var sBot = _botFactory.CreateScalpingBot(request.Config);
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly,
request.InitialTradingBalance);
_botService.AddTradingBotToCache(sBot); _botService.AddTradingBotToCache(sBot);
return sBot.GetStatus(); return sBot.GetStatus();
case BotType.FlippingBot: case BotType.FlippingBot:
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name, var fBot = _botFactory.CreateFlippingBot(request.Config);
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly,
request.InitialTradingBalance);
_botService.AddTradingBotToCache(fBot); _botService.AddTradingBotToCache(fBot);
return fBot.GetStatus(); return fBot.GetStatus();
} }

View File

@@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot
{ {
_botService.ToggleIsForWatchingOnly(request.Name); _botService.ToggleIsForWatchingOnly(request.Name);
var bot = _botService.GetActiveBots().FirstOrDefault(b => b.Name == 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());
} }
} }
} }

View File

@@ -24,7 +24,7 @@ public class UserService : IUserService
"0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3", // Local optiflex "0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3", // Local optiflex
"0x932167388dD9aad41149b3cA23eBD489E2E2DD78", // Embedded wallet "0x932167388dD9aad41149b3cA23eBD489E2E2DD78", // Embedded wallet
"0x66CB57Fe3f53cE57376421106dFDa2D39186cBd0", // Embedded wallet optiflex "0x66CB57Fe3f53cE57376421106dFDa2D39186cBd0", // Embedded wallet optiflex
"0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain "0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain
"0xc8bC497534d0A43bAb2BBA9BA94d46D9Ddfaea6B" // DevCowchain2 "0xc8bC497534d0A43bAb2BBA9BA94d46D9Ddfaea6B" // DevCowchain2
]; ];
@@ -47,7 +47,8 @@ public class UserService : IUserService
if (!message.Equals("KaigenTeamXCowchain")) if (!message.Equals("KaigenTeamXCowchain"))
{ {
_logger.LogWarning($"Message {message} not starting with 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)) // if (!authorizedAddresses.Contains(recoveredAddress))
@@ -106,11 +107,19 @@ public class UserService : IUserService
{ {
account account
}; };
// Update user with the new account
await _userRepository.UpdateUser(user);
} }
return user; return user;
} }
public User GetUser(string name)
{
return _userRepository.GetUserByNameAsync(name).Result;
}
public async Task<User> GetUserByAddressAsync(string address) public async Task<User> GetUserByAddressAsync(string address)
{ {
var account = await _accountService.GetAccountByKey(address, true, false); var account = await _accountService.GetAccountByKey(address, true, false);
@@ -118,4 +127,18 @@ public class UserService : IUserService
user.Accounts = _accountService.GetAccountsByUser(user).ToList(); user.Accounts = _accountService.GetAccountsByUser(user).ToList();
return user; return user;
} }
}
public async Task<User> 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;
}
}

View File

@@ -115,6 +115,7 @@ public static class ApiBootstrap
services.AddTransient<IBotRepository, BotRepository>(); services.AddTransient<IBotRepository, BotRepository>();
services.AddTransient<IWorkerRepository, WorkerRepository>(); services.AddTransient<IWorkerRepository, WorkerRepository>();
// Cache // Cache
services.AddDistributedMemoryCache(); services.AddDistributedMemoryCache();
services.AddTransient<ICacheService, CacheService>(); services.AddTransient<ICacheService, CacheService>();
@@ -135,7 +136,7 @@ public static class ApiBootstrap
services.AddSingleton<IBotService, BotService>(); services.AddSingleton<IBotService, BotService>();
services.AddSingleton<IWorkerService, WorkerService>(); services.AddSingleton<IWorkerService, WorkerService>();
services.AddTransient<IPrivyService, PrivyService>(); services.AddTransient<IPrivyService, PrivyService>();
// Web3Proxy Configuration // Web3Proxy Configuration
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy")); services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
services.AddTransient<IWeb3ProxyService, Web3ProxyService>(); services.AddTransient<IWeb3ProxyService, Web3ProxyService>();

View File

@@ -14,6 +14,7 @@ using Managing.Application.Scenarios;
using Managing.Application.Shared; using Managing.Application.Shared;
using Managing.Application.Trading; using Managing.Application.Trading;
using Managing.Application.Trading.Commands; using Managing.Application.Trading.Commands;
using Managing.Application.Users;
using Managing.Application.Workers; using Managing.Application.Workers;
using Managing.Application.Workers.Abstractions; using Managing.Application.Workers.Abstractions;
using Managing.Domain.Trades; using Managing.Domain.Trades;
@@ -52,6 +53,7 @@ public static class WorkersBootstrap
private static IServiceCollection AddApplication(this IServiceCollection services) private static IServiceCollection AddApplication(this IServiceCollection services)
{ {
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<ITradingService, TradingService>(); services.AddSingleton<ITradingService, TradingService>();
services.AddSingleton<IBotFactory, BotFactory>(); services.AddSingleton<IBotFactory, BotFactory>();
services.AddSingleton<IScenarioService, ScenarioService>(); services.AddSingleton<IScenarioService, ScenarioService>();
@@ -102,6 +104,7 @@ public static class WorkersBootstrap
services.AddTransient<ITradingRepository, TradingRepository>(); services.AddTransient<ITradingRepository, TradingRepository>();
services.AddTransient<IBacktestRepository, BacktestRepository>(); services.AddTransient<IBacktestRepository, BacktestRepository>();
services.AddTransient<IBotRepository, BotRepository>(); services.AddTransient<IBotRepository, BotRepository>();
services.AddTransient<IUserRepository, UserRepository>();
// Cache // Cache
services.AddDistributedMemoryCache(); services.AddDistributedMemoryCache();

View File

@@ -9,4 +9,5 @@ public class BotBackup
public string Identifier { get; set; } public string Identifier { get; set; }
public User User { get; set; } public User User { get; set; }
public string Data { get; set; } public string Data { get; set; }
public BotStatus LastStatus { get; set; }
} }

View File

@@ -1,7 +1,10 @@
namespace Managing.Domain.Bots using Managing.Domain.Users;
namespace Managing.Domain.Bots
{ {
public interface IBot public interface IBot
{ {
User User { get; set; }
string Name { get; set; } string Name { get; set; }
void Start(); void Start();
void Stop(); void Stop();

View File

@@ -6,4 +6,5 @@ public class User
{ {
public string Name { get; set; } public string Name { get; set; }
public List<Account> Accounts { get; set; } public List<Account> Accounts { get; set; }
public string AgentName { get; set; }
} }

View File

@@ -12,4 +12,5 @@ public class BotDto : Document
public BotType BotType { get; set; } public BotType BotType { get; set; }
public string Identifier { get; set; } public string Identifier { get; set; }
public UserDto User { get; set; } public UserDto User { get; set; }
public BotStatus LastStatus { get; set; }
} }

View File

@@ -7,4 +7,5 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections;
public class UserDto : Document public class UserDto : Document
{ {
public string Name { get; set; } public string Name { get; set; }
public string AgentName { get; set; }
} }

View File

@@ -516,6 +516,7 @@ public static class MongoMappers
return new User return new User
{ {
Name = user.Name, Name = user.Name,
AgentName = user.AgentName
}; };
} }
@@ -523,7 +524,8 @@ public static class MongoMappers
{ {
return new UserDto return new UserDto
{ {
Name = user.Name Name = user.Name,
AgentName = user.AgentName
}; };
} }
@@ -724,6 +726,7 @@ public static class MongoMappers
Identifier = bot.Identifier, Identifier = bot.Identifier,
BotType = bot.BotType, BotType = bot.BotType,
Data = bot.Data, Data = bot.Data,
LastStatus = bot.LastStatus
}; };
} }
@@ -736,7 +739,8 @@ public static class MongoMappers
User = Map(b.User), User = Map(b.User),
Identifier = b.Identifier, Identifier = b.Identifier,
BotType = b.BotType, BotType = b.BotType,
Data = b.Data Data = b.Data,
LastStatus = b.LastStatus
}; };
} }

View File

@@ -15,6 +15,12 @@ public class UserRepository : IUserRepository
_userRepository = userRepository; _userRepository = userRepository;
} }
public async Task<User> GetUserByAgentNameAsync(string agentName)
{
var user = await _userRepository.FindOneAsync(u => u.AgentName == agentName);
return MongoMappers.Map(user);
}
public async Task<User> GetUserByNameAsync(string name) public async Task<User> GetUserByNameAsync(string name)
{ {
var user = await _userRepository.FindOneAsync(u => u.Name == name); var user = await _userRepository.FindOneAsync(u => u.Name == name);
@@ -31,7 +37,7 @@ public class UserRepository : IUserRepository
try try
{ {
var dto = await _userRepository.FindOneAsync(u => u.Name == user.Name); var dto = await _userRepository.FindOneAsync(u => u.Name == user.Name);
dto.Name = user.Name; dto.AgentName = user.AgentName;
_userRepository.Update(dto); _userRepository.Update(dto);
} }
catch (Exception e) catch (Exception e)

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,703 @@
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
/* 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 };
}

View File

@@ -7,13 +7,15 @@ import Toast from '../Toast/Toast'
interface TradesModalProps { interface TradesModalProps {
showModal: boolean showModal: boolean
botName: string | null agentName: string | null
strategyName: string | null
onClose: () => void onClose: () => void
} }
const TradesModal: React.FC<TradesModalProps> = ({ const TradesModal: React.FC<TradesModalProps> = ({
showModal, showModal,
botName, strategyName,
agentName,
onClose, onClose,
}) => { }) => {
const { apiUrl } = useApiUrlStore() const { apiUrl } = useApiUrlStore()
@@ -22,19 +24,19 @@ const TradesModal: React.FC<TradesModalProps> = ({
const [closingPosition, setClosingPosition] = useState<string | null>(null) const [closingPosition, setClosingPosition] = useState<string | null>(null)
useEffect(() => { useEffect(() => {
if (showModal && botName) { if (showModal && strategyName && agentName) {
fetchStrategyData() fetchStrategyData()
} }
}, [showModal, botName]) }, [showModal, strategyName, agentName])
const fetchStrategyData = async () => { const fetchStrategyData = async () => {
if (!botName) return if (!strategyName || !agentName) return
setLoading(true) setLoading(true)
const client = new DataClient({}, apiUrl) const client = new DataClient({}, apiUrl)
try { try {
const data = await client.data_GetUserStrategy("Oda", botName) const data = await client.data_GetUserStrategy(agentName, strategyName)
setStrategyData(data) setStrategyData(data)
} catch (error) { } catch (error) {
console.error('Error fetching strategy data:', error) console.error('Error fetching strategy data:', error)
@@ -46,7 +48,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
} }
const closePosition = async (position: Position) => { const closePosition = async (position: Position) => {
if (!botName) return if (!agentName) return
try { try {
setClosingPosition(position.identifier) setClosingPosition(position.identifier)
@@ -56,7 +58,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
// Use BotClient instead of fetch // Use BotClient instead of fetch
const botClient = new BotClient({}, apiUrl) const botClient = new BotClient({}, apiUrl)
const request: ClosePositionRequest = { const request: ClosePositionRequest = {
botName: botName, identifier: agentName,
positionId: position.identifier positionId: position.identifier
} }
@@ -78,7 +80,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
<Modal <Modal
showModal={showModal} showModal={showModal}
onClose={onClose} onClose={onClose}
titleHeader={`Trades for ${botName}`} titleHeader={`Trades for ${strategyName}`}
> >
<div className="mt-4"> <div className="mt-4">
{loading ? ( {loading ? (

View File

@@ -42,7 +42,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
const { register, handleSubmit, setValue } = useForm<IBacktestsFormInput>({ const { register, handleSubmit, setValue } = useForm<IBacktestsFormInput>({
defaultValues: { defaultValues: {
startDate: defaultStartDateString, 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<string>('') const [selectedAccount, setSelectedAccount] = useState<string>('')
@@ -110,6 +112,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
new Date(form.startDate), // startDate new Date(form.startDate), // startDate
new Date(form.endDate), // endDate new Date(form.endDate), // endDate
form.save, form.save,
form.cooldownPeriod, // Use the cooldown period from the form
form.maxLossStreak, // Add the max loss streak parameter
customMoneyManagement customMoneyManagement
) )
.then((backtest: Backtest) => { .then((backtest: Backtest) => {
@@ -362,6 +366,28 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
/> />
</FormInput> </FormInput>
<FormInput label="Cooldown Period (candles)" htmlFor="cooldownPeriod">
<input
type="number"
className="input input-bordered w-full"
min="1"
step="1"
{...register('cooldownPeriod', { valueAsNumber: true })}
/>
</FormInput>
</div>
<div className="grid grid-cols-2 gap-4">
<FormInput label="Max Loss Streak" htmlFor="maxLossStreak">
<input
type="number"
className="input input-bordered w-full"
min="0"
step="1"
{...register('maxLossStreak', { valueAsNumber: true })}
/>
</FormInput>
<FormInput label="Save" htmlFor="save"> <FormInput label="Save" htmlFor="save">
<input <input
type="checkbox" type="checkbox"

View File

@@ -337,7 +337,7 @@ export class BacktestClient extends AuthorizedApiBase {
return Promise.resolve<Backtest>(null as any); return Promise.resolve<Backtest>(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> { 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<Backtest> {
let url_ = this.baseUrl + "/Backtest/Run?"; let url_ = this.baseUrl + "/Backtest/Run?";
if (accountName !== undefined && accountName !== null) if (accountName !== undefined && accountName !== null)
url_ += "accountName=" + encodeURIComponent("" + accountName) + "&"; url_ += "accountName=" + encodeURIComponent("" + accountName) + "&";
@@ -377,6 +377,14 @@ export class BacktestClient extends AuthorizedApiBase {
throw new Error("The parameter 'save' cannot be null."); throw new Error("The parameter 'save' cannot be null.");
else if (save !== undefined) else if (save !== undefined)
url_ += "save=" + encodeURIComponent("" + save) + "&"; 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(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(moneyManagement); const content_ = JSON.stringify(moneyManagement);
@@ -465,14 +473,14 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<string>(null as any); return Promise.resolve<string>(null as any);
} }
bot_Stop(botType: BotType | undefined, botName: string | null | undefined): Promise<string> { bot_Stop(botType: BotType | undefined, identifier: string | null | undefined): Promise<string> {
let url_ = this.baseUrl + "/Bot/Stop?"; let url_ = this.baseUrl + "/Bot/Stop?";
if (botType === null) if (botType === null)
throw new Error("The parameter 'botType' cannot be null."); throw new Error("The parameter 'botType' cannot be null.");
else if (botType !== undefined) else if (botType !== undefined)
url_ += "botType=" + encodeURIComponent("" + botType) + "&"; url_ += "botType=" + encodeURIComponent("" + botType) + "&";
if (botName !== undefined && botName !== null) if (identifier !== undefined && identifier !== null)
url_ += "botName=" + encodeURIComponent("" + botName) + "&"; url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
@@ -506,10 +514,10 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<string>(null as any); return Promise.resolve<string>(null as any);
} }
bot_Delete(botName: string | null | undefined): Promise<boolean> { bot_Delete(identifier: string | null | undefined): Promise<boolean> {
let url_ = this.baseUrl + "/Bot/Delete?"; let url_ = this.baseUrl + "/Bot/Delete?";
if (botName !== undefined && botName !== null) if (identifier !== undefined && identifier !== null)
url_ += "botName=" + encodeURIComponent("" + botName) + "&"; url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
@@ -578,14 +586,14 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<string>(null as any); return Promise.resolve<string>(null as any);
} }
bot_Restart(botType: BotType | undefined, botName: string | null | undefined): Promise<string> { bot_Restart(botType: BotType | undefined, identifier: string | null | undefined): Promise<string> {
let url_ = this.baseUrl + "/Bot/Restart?"; let url_ = this.baseUrl + "/Bot/Restart?";
if (botType === null) if (botType === null)
throw new Error("The parameter 'botType' cannot be null."); throw new Error("The parameter 'botType' cannot be null.");
else if (botType !== undefined) else if (botType !== undefined)
url_ += "botType=" + encodeURIComponent("" + botType) + "&"; url_ += "botType=" + encodeURIComponent("" + botType) + "&";
if (botName !== undefined && botName !== null) if (identifier !== undefined && identifier !== null)
url_ += "botName=" + encodeURIComponent("" + botName) + "&"; url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
@@ -654,10 +662,10 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<string>(null as any); return Promise.resolve<string>(null as any);
} }
bot_ToggleIsForWatching(botName: string | null | undefined): Promise<string> { bot_ToggleIsForWatching(identifier: string | null | undefined): Promise<string> {
let url_ = this.baseUrl + "/Bot/ToggleIsForWatching?"; let url_ = this.baseUrl + "/Bot/ToggleIsForWatching?";
if (botName !== undefined && botName !== null) if (identifier !== undefined && identifier !== null)
url_ += "botName=" + encodeURIComponent("" + botName) + "&"; url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = { let options_: RequestInit = {
@@ -816,7 +824,7 @@ export class DataClient extends AuthorizedApiBase {
this.baseUrl = baseUrl ?? "http://localhost:5000"; this.baseUrl = baseUrl ?? "http://localhost:5000";
} }
data_GetTickers(timeframe: Timeframe | undefined): Promise<Ticker[]> { data_GetTickers(timeframe: Timeframe | undefined): Promise<TickerInfos[]> {
let url_ = this.baseUrl + "/Data/GetTickers?"; let url_ = this.baseUrl + "/Data/GetTickers?";
if (timeframe === null) if (timeframe === null)
throw new Error("The parameter 'timeframe' cannot be 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<Ticker[]> { protected processData_GetTickers(response: Response): Promise<TickerInfos[]> {
const status = response.status; const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) { if (status === 200) {
return response.text().then((_responseText) => { return response.text().then((_responseText) => {
let result200: any = null; 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; return result200;
}); });
} else if (status !== 200 && status !== 204) { } 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 throwException("An unexpected server error occurred.", status, _responseText, _headers);
}); });
} }
return Promise.resolve<Ticker[]>(null as any); return Promise.resolve<TickerInfos[]>(null as any);
} }
data_GetSpotlight(): Promise<SpotlightOverview> { data_GetSpotlight(): Promise<SpotlightOverview> {
@@ -2202,6 +2210,80 @@ export class UserClient extends AuthorizedApiBase {
} }
return Promise.resolve<string>(null as any); return Promise.resolve<string>(null as any);
} }
user_GetCurrentUser(): Promise<User> {
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<User> {
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<User>(null as any);
}
user_UpdateAgentName(agentName: string): Promise<User> {
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<User> {
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<User>(null as any);
}
} }
export class WorkflowClient extends AuthorizedApiBase { export class WorkflowClient extends AuthorizedApiBase {
@@ -2397,6 +2479,7 @@ export enum AccountType {
export interface User { export interface User {
name?: string | null; name?: string | null;
accounts?: Account[] | null; accounts?: Account[] | null;
agentName?: string | null;
} }
export interface Balance { export interface Balance {
@@ -2839,15 +2922,15 @@ export interface SuperTrendResult extends ResultBase {
} }
export interface StartBotRequest { export interface StartBotRequest {
botType: BotType; botType?: BotType;
botName: string; identifier?: string | null;
ticker: Ticker; ticker?: Ticker;
timeframe: Timeframe; scenario?: string | null;
isForWatchOnly: boolean; timeframe?: Timeframe;
scenario: string; accountName?: string | null;
accountName: string; moneyManagementName?: string | null;
moneyManagementName: string; isForWatchOnly?: boolean;
initialTradingBalance: number; initialTradingBalance?: number;
} }
export interface TradingBot { export interface TradingBot {
@@ -2865,18 +2948,25 @@ export interface TradingBot {
botType: BotType; botType: BotType;
accountName: string; accountName: string;
moneyManagement: MoneyManagement; moneyManagement: MoneyManagement;
identifier: string;
agentName: string;
} }
export interface OpenPositionManuallyRequest { export interface OpenPositionManuallyRequest {
botName?: string | null; identifier?: string | null;
direction?: TradeDirection; direction?: TradeDirection;
} }
export interface ClosePositionRequest { export interface ClosePositionRequest {
botName?: string | null; identifier?: string | null;
positionId?: string | null; positionId?: string | null;
} }
export interface TickerInfos {
ticker?: Ticker;
imageUrl?: string | null;
}
export interface SpotlightOverview { export interface SpotlightOverview {
spotlights: Spotlight[]; spotlights: Spotlight[];
dateTime: Date; dateTime: Date;
@@ -2962,7 +3052,7 @@ export interface PlatformSummaryViewModel {
} }
export interface AgentSummaryViewModel { export interface AgentSummaryViewModel {
username?: string | null; agentName?: string | null;
totalPnL?: number; totalPnL?: number;
pnLLast24h?: number; pnLLast24h?: number;
totalROI?: number; totalROI?: number;

View File

@@ -111,6 +111,8 @@ export type IBacktestsFormInput = {
loop: number loop: number
startDate: string startDate: string
endDate: string endDate: string
cooldownPeriod: number
maxLossStreak: number
} }
export type IBacktestCards = { export type IBacktestCards = {

View File

@@ -1,8 +1,8 @@
import React, { useState } from 'react' import React, {useState} from 'react'
import 'react-toastify/dist/ReactToastify.css' import 'react-toastify/dist/ReactToastify.css'
import { Tabs } from '../../components/mollecules' import {Tabs} from '../../components/mollecules'
import type { TabsType } from '../../global/type' import type {TabsType} from '../../global/type'
import BacktestLoop from './backtestLoop' import BacktestLoop from './backtestLoop'
import BacktestPlayground from './backtestPlayground' import BacktestPlayground from './backtestPlayground'
@@ -12,14 +12,14 @@ import BacktestUpload from './backtestUpload'
// Tabs Array // Tabs Array
const tabs: TabsType = [ const tabs: TabsType = [
{ {
Component: BacktestScanner, Component: BacktestPlayground,
index: 1, index: 1,
label: 'Scanner', label: 'Playground',
}, },
{ {
Component: BacktestPlayground, Component: BacktestScanner,
index: 2, index: 2,
label: 'Playground', label: 'Scanner',
}, },
{ {
Component: BacktestLoop, Component: BacktestLoop,

View File

@@ -37,13 +37,13 @@ const BotList: React.FC<IBotList> = ({ list }) => {
const [showManualPositionModal, setShowManualPositionModal] = useState(false) const [showManualPositionModal, setShowManualPositionModal] = useState(false)
const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState<string | null>(null) const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState<string | null>(null)
const [showTradesModal, setShowTradesModal] = useState(false) const [showTradesModal, setShowTradesModal] = useState(false)
const [selectedBotForTrades, setSelectedBotForTrades] = useState<string | null>(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 = const classes =
baseBadgeClass() + (isForWatchingOnly ? ' bg-accent' : ' bg-primary') baseBadgeClass() + (isForWatchingOnly ? ' bg-accent' : ' bg-primary')
return ( return (
<button className={classes} onClick={() => toggleIsForWatchingOnly(name)}> <button className={classes} onClick={() => toggleIsForWatchingOnly(identifier)}>
{isForWatchingOnly ? ( {isForWatchingOnly ? (
<p className="text-accent-content flex"> <p className="text-accent-content flex">
<EyeIcon width={12}></EyeIcon> <EyeIcon width={12}></EyeIcon>
@@ -59,16 +59,16 @@ const BotList: React.FC<IBotList> = ({ list }) => {
) )
} }
function toggleIsForWatchingOnly(name: string) { function toggleIsForWatchingOnly(identifier: string) {
const t = new Toast('Switch watch only') const t = new Toast('Switch watch only')
client.bot_ToggleIsForWatching(name).then(() => { client.bot_ToggleIsForWatching(identifier).then(() => {
t.update('success', 'Bot updated') t.update('success', 'Bot updated')
}) })
} }
function getDeleteBadge(name: string) { function getDeleteBadge(identifier: string) {
const classes = baseBadgeClass() + 'bg-error' const classes = baseBadgeClass() + 'bg-error'
return ( return (
<button className={classes} onClick={() => deleteBot(name)}> <button className={classes} onClick={() => deleteBot(identifier)}>
<p className="text-primary-content flex"> <p className="text-primary-content flex">
<TrashIcon width={15}></TrashIcon> <TrashIcon width={15}></TrashIcon>
</p> </p>
@@ -77,7 +77,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
} }
function getToggleBotStatusBadge( function getToggleBotStatusBadge(
status: string, status: string,
name: string, identifier: string,
botType: BotType botType: BotType
) { ) {
const classes = const classes =
@@ -85,7 +85,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
return ( return (
<button <button
className={classes} className={classes}
onClick={() => toggleBotStatus(status, name, botType)} onClick={() => toggleBotStatus(status, identifier, botType)}
> >
{status == 'Up' ? ( {status == 'Up' ? (
<p className="text-accent-content flex"> <p className="text-accent-content flex">
@@ -118,10 +118,10 @@ const BotList: React.FC<IBotList> = ({ list }) => {
) )
} }
function getManualPositionBadge(botName: string) { function getManualPositionBadge(botIdentifier: string) {
const classes = baseBadgeClass() + ' bg-info' const classes = baseBadgeClass() + ' bg-info'
return ( return (
<button className={classes} onClick={() => openManualPositionModal(botName)}> <button className={classes} onClick={() => openManualPositionModal(botIdentifier)}>
<p className="text-primary-content flex"> <p className="text-primary-content flex">
<PlusCircleIcon width={15}></PlusCircleIcon> <PlusCircleIcon width={15}></PlusCircleIcon>
</p> </p>
@@ -129,10 +129,10 @@ const BotList: React.FC<IBotList> = ({ list }) => {
) )
} }
function getTradesBadge(botName: string) { function getTradesBadge(botIdentifier: string, agentName: string) {
const classes = baseBadgeClass() + ' bg-secondary' const classes = baseBadgeClass() + ' bg-secondary'
return ( return (
<button className={classes} onClick={() => openTradesModal(botName)}> <button className={classes} onClick={() => openTradesModal(botIdentifier, agentName)}>
<p className="text-primary-content flex"> <p className="text-primary-content flex">
<ChartBarIcon width={15}></ChartBarIcon> <ChartBarIcon width={15}></ChartBarIcon>
</p> </p>
@@ -140,23 +140,23 @@ const BotList: React.FC<IBotList> = ({ list }) => {
) )
} }
function openManualPositionModal(botName: string) { function openManualPositionModal(botIdentifier: string) {
setSelectedBotForManualPosition(botName) setSelectedBotForManualPosition(botIdentifier)
setShowManualPositionModal(true) setShowManualPositionModal(true)
} }
function openTradesModal(botName: string) { function openTradesModal(botIdentifier: string, agentName: string) {
setSelectedBotForTrades(botName) setSelectedBotForTrades({ identifier: botIdentifier, agentName })
setShowTradesModal(true) setShowTradesModal(true)
} }
function toggleBotStatus(status: string, name: string, botType: BotType) { function toggleBotStatus(status: string, identifier: string, botType: BotType) {
const isUp = status == 'Up' const isUp = status == 'Up'
const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot') const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot')
if (status == 'Up') { if (status == 'Up') {
client client
.bot_Stop(botType, name) .bot_Stop(botType, identifier)
.then(() => { .then(() => {
t.update('success', 'Bot stopped') t.update('success', 'Bot stopped')
}) })
@@ -165,7 +165,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
}) })
} else if (status == 'Down') { } else if (status == 'Down') {
client client
.bot_Restart(botType, name) .bot_Restart(botType, identifier)
.then(() => { .then(() => {
t.update('success', 'Bot up') t.update('success', 'Bot up')
}) })
@@ -175,11 +175,11 @@ const BotList: React.FC<IBotList> = ({ list }) => {
} }
} }
function deleteBot(name: string) { function deleteBot(identifier: string) {
const t = new Toast('Deleting bot') const t = new Toast('Deleting bot')
client client
.bot_Delete(name) .bot_Delete(identifier)
.then(() => { .then(() => {
t.update('success', 'Bot deleted') t.update('success', 'Bot deleted')
}) })
@@ -211,10 +211,10 @@ const BotList: React.FC<IBotList> = ({ list }) => {
<h2 className="card-title text-sm"> <h2 className="card-title text-sm">
{bot.ticker} {bot.ticker}
{getMoneyManagementBadge(bot.moneyManagement)} {getMoneyManagementBadge(bot.moneyManagement)}
{getIsForWatchingBadge(bot.isForWatchingOnly, bot.name)} {getIsForWatchingBadge(bot.isForWatchingOnly, bot.identifier)}
{getToggleBotStatusBadge(bot.status, bot.name, bot.botType)} {getToggleBotStatusBadge(bot.status, bot.identifier, bot.botType)}
{getManualPositionBadge(bot.name)} {getManualPositionBadge(bot.identifier)}
{getDeleteBadge(bot.name)} {getDeleteBadge(bot.identifier)}
</h2> </h2>
@@ -256,7 +256,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
<div className={baseBadgeClass(true)}> <div className={baseBadgeClass(true)}>
PNL {bot.profitAndLoss.toFixed(2).toString()} $ PNL {bot.profitAndLoss.toFixed(2).toString()} $
</div> </div>
{getTradesBadge(bot.name)} {getTradesBadge(bot.identifier, bot.agentName)}
</div> </div>
</div> </div>
</div> </div>
@@ -280,7 +280,8 @@ const BotList: React.FC<IBotList> = ({ list }) => {
/> />
<TradesModal <TradesModal
showModal={showTradesModal} showModal={showTradesModal}
botName={selectedBotForTrades} strategyName={selectedBotForTrades?.identifier ?? null}
agentName={selectedBotForTrades?.agentName ?? null}
onClose={() => { onClose={() => {
setShowTradesModal(false) setShowTradesModal(false)
setSelectedBotForTrades(null) setSelectedBotForTrades(null)

View File

@@ -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<UpdateAgentNameForm>()
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 (
<div className="container mx-auto p-4">
<div className="bg-base-200 rounded-lg p-6 shadow-lg">
<h2 className="text-2xl font-bold mb-4">User Information</h2>
<div className="space-y-4">
<div>
<label className="font-semibold">Name:</label>
<p>{user?.name}</p>
</div>
<div>
<label className="font-semibold">Agent Name:</label>
<p>{user?.agentName || 'Not set'}</p>
<button
className="btn btn-primary mt-2"
onClick={() => setShowUpdateModal(true)}
>
Update Agent Name
</button>
</div>
</div>
</div>
<Modal
showModal={showUpdateModal}
onClose={() => setShowUpdateModal(false)}
onSubmit={handleSubmit(onSubmit)}
titleHeader="Update Agent Name"
>
<div className="form-control w-full">
<label className="label">
<span className="label-text">Agent Name</span>
</label>
<input
type="text"
className="input input-bordered w-full"
{...register('agentName', { required: 'Agent name is required' })}
defaultValue={user?.agentName || ''}
/>
{errors.agentName && (
<label className="label">
<span className="label-text-alt text-error">
{errors.agentName.message}
</span>
</label>
)}
</div>
<div className="modal-action">
<button type="submit" className="btn">
Update
</button>
</div>
</Modal>
</div>
)
}
export default UserInfoSettings

View File

@@ -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 AccountSettings from './account/accountSettings'
import HealthChecks from './healthchecks/healthChecks' import HealthChecks from './healthchecks/healthChecks'
import MoneyManagementSettings from './moneymanagement/moneyManagement' import MoneyManagementSettings from './moneymanagement/moneyManagement'
import Theme from './theme' import Theme from './theme'
import DefaultConfig from './defaultConfig/defaultConfig' import DefaultConfig from './defaultConfig/defaultConfig'
import UserInfoSettings from './UserInfoSettings'
type TabsType = { type TabsType = {
label: string label: string
@@ -17,28 +18,33 @@ type TabsType = {
// Tabs Array // Tabs Array
const tabs: TabsType = [ const tabs: TabsType = [
{ {
Component: MoneyManagementSettings, Component: UserInfoSettings,
index: 1, index: 1,
label: 'User Info',
},
{
Component: MoneyManagementSettings,
index: 2,
label: 'Money Management', label: 'Money Management',
}, },
{ {
Component: AccountSettings, Component: AccountSettings,
index: 2, index: 3,
label: 'Account Settings', label: 'Account Settings',
}, },
{ {
Component: Theme, Component: Theme,
index: 3, index: 4,
label: 'Theme', label: 'Theme',
}, },
{ {
Component: DefaultConfig, Component: DefaultConfig,
index: 4, index: 5,
label: 'Quick Start Config', label: 'Quick Start Config',
}, },
{ {
Component: HealthChecks, Component: HealthChecks,
index: 5, index: 6,
label: 'Health Checks', label: 'Health Checks',
}, },
] ]