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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
9
src/Managing.Api/Models/Responses/TickerInfos.cs
Normal file
9
src/Managing.Api/Models/Responses/TickerInfos.cs
Normal 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; }
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -24,5 +24,6 @@
|
|||||||
"ElasticConfiguration": {
|
"ElasticConfiguration": {
|
||||||
"Uri": "http://elasticsearch:9200"
|
"Uri": "http://elasticsearch:9200"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"EnableBotManager": true
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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++;
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
20
src/Managing.Application/Bots/TradingBotConfig.cs
Normal file
20
src/Managing.Application/Bots/TradingBotConfig.cs
Normal 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
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
BIN
src/Managing.Nswag/.DS_Store
vendored
BIN
src/Managing.Nswag/.DS_Store
vendored
Binary file not shown.
3135
src/Managing.Nswag/ManagingApi.ts
Normal file
3135
src/Managing.Nswag/ManagingApi.ts
Normal file
File diff suppressed because it is too large
Load Diff
703
src/Managing.Nswag/ManagingApiTypes.ts
Normal file
703
src/Managing.Nswag/ManagingApiTypes.ts
Normal 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 };
|
||||||
|
}
|
||||||
@@ -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 ? (
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
101
src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx
Normal file
101
src/Managing.WebApp/src/pages/settingsPage/UserInfoSettings.tsx
Normal 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
|
||||||
@@ -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',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user