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="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="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>
|
||||
[HttpPost]
|
||||
[Route("Run")]
|
||||
@@ -122,7 +124,9 @@ public class BacktestController : BaseController
|
||||
DateTime startDate,
|
||||
DateTime endDate,
|
||||
MoneyManagement? moneyManagement = null,
|
||||
bool save = false)
|
||||
bool save = false,
|
||||
decimal cooldownPeriod = 1,
|
||||
int maxLossStreak = 0)
|
||||
{
|
||||
if (string.IsNullOrEmpty(accountName))
|
||||
{
|
||||
@@ -174,7 +178,9 @@ public class BacktestController : BaseController
|
||||
endDate,
|
||||
user,
|
||||
watchOnly,
|
||||
save);
|
||||
save,
|
||||
cooldownPeriod: cooldownPeriod,
|
||||
maxLossStreak: maxLossStreak);
|
||||
break;
|
||||
case BotType.FlippingBot:
|
||||
backtestResult = await _backtester.RunFlippingBotBacktest(
|
||||
@@ -188,7 +194,9 @@ public class BacktestController : BaseController
|
||||
endDate,
|
||||
user,
|
||||
watchOnly,
|
||||
save);
|
||||
save,
|
||||
cooldownPeriod: cooldownPeriod,
|
||||
maxLossStreak: maxLossStreak);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using System.Security.Claims;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Managing.Api.Controllers;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Managing.Api.Controllers;
|
||||
[Produces("application/json")]
|
||||
public abstract class BaseController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
public readonly IUserService _userService;
|
||||
|
||||
public BaseController(IUserService userService)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Application.ManageBot.Commands;
|
||||
using Managing.Common;
|
||||
@@ -56,10 +56,10 @@ public class BotController : BaseController
|
||||
/// <summary>
|
||||
/// Checks if the current authenticated user owns the account associated with the specified bot or account name
|
||||
/// </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>
|
||||
/// <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
|
||||
{
|
||||
@@ -78,12 +78,12 @@ public class BotController : BaseController
|
||||
|
||||
// For existing bots, check if the user owns the bot's account
|
||||
var activeBots = _botService.GetActiveBots();
|
||||
var bot = activeBots.FirstOrDefault(b => b.Name == botName);
|
||||
var bot = activeBots.FirstOrDefault(b => b.Identifier == identifier);
|
||||
if (bot == null)
|
||||
return true; // Bot doesn't exist yet, so no ownership conflict
|
||||
|
||||
var botAccountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
|
||||
var botAccount = await botAccountService.GetAccount(bot.AccountName, true, false);
|
||||
var botAccount = await botAccountService.GetAccount(bot.Config.AccountName, true, false);
|
||||
// Compare the user names
|
||||
return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public class BotController : BaseController
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(request.BotName, request.AccountName))
|
||||
if (!await UserOwnsBotAccount(request.Identifier, request.AccountName))
|
||||
{
|
||||
return Forbid("You don't have permission to start this bot");
|
||||
}
|
||||
@@ -131,9 +131,19 @@ public class BotController : BaseController
|
||||
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
|
||||
}
|
||||
|
||||
var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker,
|
||||
request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user,
|
||||
request.IsForWatchOnly, request.InitialTradingBalance));
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = request.AccountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = request.Ticker,
|
||||
ScenarioName = request.Scenario,
|
||||
Timeframe = request.Timeframe,
|
||||
IsForWatchingOnly = request.IsForWatchOnly,
|
||||
BotTradingBalance = request.InitialTradingBalance,
|
||||
BotType = request.BotType
|
||||
};
|
||||
|
||||
var result = await _mediator.Send(new StartBotCommand(config, request.Identifier, user));
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
return Ok(result);
|
||||
@@ -149,22 +159,22 @@ public class BotController : BaseController
|
||||
/// Stops a bot specified by type and name.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
[HttpGet]
|
||||
[Route("Stop")]
|
||||
public async Task<ActionResult<string>> Stop(BotType botType, string botName)
|
||||
public async Task<ActionResult<string>> Stop(BotType botType, string identifier)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
if (!await UserOwnsBotAccount(identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to stop this bot");
|
||||
}
|
||||
|
||||
var result = await _mediator.Send(new StopBotCommand(botType, botName));
|
||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||
var result = await _mediator.Send(new StopBotCommand(botType, identifier));
|
||||
_logger.LogInformation($"{botType} type with identifier {identifier} is now {result}");
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
@@ -180,21 +190,21 @@ public class BotController : BaseController
|
||||
/// <summary>
|
||||
/// Deletes a bot specified by name.
|
||||
/// </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>
|
||||
[HttpDelete]
|
||||
[Route("Delete")]
|
||||
public async Task<ActionResult<bool>> Delete(string botName)
|
||||
public async Task<ActionResult<bool>> Delete(string identifier)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
if (!await UserOwnsBotAccount(identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to delete this bot");
|
||||
}
|
||||
|
||||
var result = await _botService.DeleteBot(botName);
|
||||
var result = await _botService.DeleteBot(identifier);
|
||||
await NotifyBotSubscriberAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
@@ -235,9 +245,9 @@ public class BotController : BaseController
|
||||
|
||||
foreach (var bot in userBots)
|
||||
{
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Identifier));
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {bot.Name} paused by {user.Name}.", "Info");
|
||||
$"Bot {bot.Identifier} paused by {user.Name}.", "Info");
|
||||
}
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
@@ -254,22 +264,22 @@ public class BotController : BaseController
|
||||
/// Restarts a bot specified by type and name.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
[HttpGet]
|
||||
[Route("Restart")]
|
||||
public async Task<ActionResult<string>> Restart(BotType botType, string botName)
|
||||
public async Task<ActionResult<string>> Restart(BotType botType, string identifier)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
if (!await UserOwnsBotAccount(identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to restart this bot");
|
||||
}
|
||||
|
||||
var result = await _mediator.Send(new RestartBotCommand(botType, botName));
|
||||
_logger.LogInformation($"{botType} type called {botName} is now {result}");
|
||||
var result = await _mediator.Send(new RestartBotCommand(botType, identifier));
|
||||
_logger.LogInformation($"{botType} type with identifier {identifier} is now {result}");
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
@@ -314,15 +324,15 @@ public class BotController : BaseController
|
||||
{
|
||||
// We can't directly restart a bot with just BotType and Name
|
||||
// Instead, stop the bot and then retrieve the backup to start it again
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name));
|
||||
await _mediator.Send(new StopBotCommand(bot.BotType, bot.Identifier));
|
||||
|
||||
// Get the saved bot backup
|
||||
var backup = _botService.GetBotBackup(bot.Name);
|
||||
var backup = _botService.GetBotBackup(bot.Identifier);
|
||||
if (backup != null)
|
||||
{
|
||||
_botService.StartBotFromBackup(backup);
|
||||
await _hubContext.Clients.All.SendAsync("SendNotification",
|
||||
$"Bot {bot.Name} restarted by {user.Name}.", "Info");
|
||||
$"Bot {bot.Identifier} restarted by {user.Name}.", "Info");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,22 +349,22 @@ public class BotController : BaseController
|
||||
/// <summary>
|
||||
/// Toggles the watching status of a bot specified by name.
|
||||
/// </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>
|
||||
[HttpGet]
|
||||
[Route("ToggleIsForWatching")]
|
||||
public async Task<ActionResult<string>> ToggleIsForWatching(string botName)
|
||||
public async Task<ActionResult<string>> ToggleIsForWatching(string identifier)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(botName))
|
||||
if (!await UserOwnsBotAccount(identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to modify this bot");
|
||||
}
|
||||
|
||||
var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName));
|
||||
_logger.LogInformation($"{botName} bot is now {result}");
|
||||
var result = await _mediator.Send(new ToggleIsForWatchingCommand(identifier));
|
||||
_logger.LogInformation($"Bot with identifier {identifier} is now {result}");
|
||||
|
||||
await NotifyBotSubscriberAsync();
|
||||
|
||||
@@ -397,13 +407,15 @@ public class BotController : BaseController
|
||||
Candles = item.Candles.DistinctBy(c => c.Date).ToList(),
|
||||
WinRate = item.GetWinRate(),
|
||||
ProfitAndLoss = item.GetProfitAndLoss(),
|
||||
Timeframe = item.Timeframe,
|
||||
Ticker = item.Ticker,
|
||||
Scenario = item.ScenarioName,
|
||||
IsForWatchingOnly = item.IsForWatchingOnly,
|
||||
BotType = item.BotType,
|
||||
AccountName = item.AccountName,
|
||||
MoneyManagement = item.MoneyManagement
|
||||
Timeframe = item.Config.Timeframe,
|
||||
Ticker = item.Config.Ticker,
|
||||
Scenario = item.Config.ScenarioName,
|
||||
IsForWatchingOnly = item.Config.IsForWatchingOnly,
|
||||
BotType = item.Config.BotType,
|
||||
AccountName = item.Config.AccountName,
|
||||
MoneyManagement = item.Config.MoneyManagement,
|
||||
Identifier = item.Identifier,
|
||||
AgentName = item.User.AgentName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -431,22 +443,22 @@ public class BotController : BaseController
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(request.BotName))
|
||||
if (!await UserOwnsBotAccount(request.Identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to open positions for this bot");
|
||||
}
|
||||
|
||||
var activeBots = _botService.GetActiveBots();
|
||||
var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot;
|
||||
var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier) as ApplicationTradingBot;
|
||||
|
||||
if (bot == null)
|
||||
{
|
||||
return NotFound($"Bot {request.BotName} not found or is not a trading bot");
|
||||
return NotFound($"Bot with identifier {request.Identifier} not found or is not a trading bot");
|
||||
}
|
||||
|
||||
if (bot.GetStatus() != BotStatus.Up.ToString())
|
||||
{
|
||||
return BadRequest($"Bot {request.BotName} is not running");
|
||||
return BadRequest($"Bot with identifier {request.Identifier} is not running");
|
||||
}
|
||||
|
||||
var position = await bot.OpenPositionManually(
|
||||
@@ -475,29 +487,30 @@ public class BotController : BaseController
|
||||
try
|
||||
{
|
||||
// Check if user owns the account
|
||||
if (!await UserOwnsBotAccount(request.BotName))
|
||||
if (!await UserOwnsBotAccount(request.Identifier))
|
||||
{
|
||||
return Forbid("You don't have permission to close positions for this bot");
|
||||
}
|
||||
|
||||
var activeBots = _botService.GetActiveBots();
|
||||
var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot;
|
||||
var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier) as ApplicationTradingBot;
|
||||
|
||||
if (bot == null)
|
||||
{
|
||||
return NotFound($"Bot {request.BotName} not found or is not a trading bot");
|
||||
return NotFound($"Bot with identifier {request.Identifier} not found or is not a trading bot");
|
||||
}
|
||||
|
||||
if (bot.GetStatus() != BotStatus.Up.ToString())
|
||||
{
|
||||
return BadRequest($"Bot {request.BotName} is not running");
|
||||
return BadRequest($"Bot with identifier {request.Identifier} is not running");
|
||||
}
|
||||
|
||||
// Find the position to close
|
||||
var position = bot.Positions.FirstOrDefault(p => p.Identifier == request.PositionId);
|
||||
if (position == null)
|
||||
{
|
||||
return NotFound($"Position with ID {request.PositionId} not found for bot {request.BotName}");
|
||||
return NotFound(
|
||||
$"Position with ID {request.PositionId} not found for bot with identifier {request.Identifier}");
|
||||
}
|
||||
|
||||
// Find the signal associated with this position
|
||||
@@ -528,18 +541,85 @@ public class BotController : BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Request model for closing a position
|
||||
/// </summary>
|
||||
public class ClosePositionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the bot
|
||||
/// The identifier of the bot
|
||||
/// </summary>
|
||||
public string BotName { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the position to close
|
||||
/// </summary>
|
||||
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.Hubs;
|
||||
using Managing.Application.ManageBot.Commands;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Api.Models.Responses;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Linq;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Controllers;
|
||||
@@ -65,20 +63,114 @@ public class DataController : ControllerBase
|
||||
/// <param name="timeframe">The timeframe for which to retrieve tickers.</param>
|
||||
/// <returns>An array of tickers.</returns>
|
||||
[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 tickers = _cacheService.GetValue<List<Ticker>>(cacheKey);
|
||||
var tickers = _cacheService.GetValue<List<TickerInfos>>(cacheKey);
|
||||
|
||||
if (tickers == null || tickers.Count == 0)
|
||||
{
|
||||
tickers = await _exchangeService.GetTickers(timeframe);
|
||||
var availableTicker = await _exchangeService.GetTickers(timeframe);
|
||||
|
||||
tickers = MapTickerToTickerInfos(availableTicker);
|
||||
|
||||
_cacheService.SaveValue(cacheKey, tickers, TimeSpan.FromHours(2));
|
||||
}
|
||||
|
||||
return Ok(tickers);
|
||||
}
|
||||
|
||||
private List<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>
|
||||
/// Retrieves the latest spotlight overview, using caching to enhance response times.
|
||||
/// </summary>
|
||||
@@ -222,24 +314,24 @@ public class DataController : ControllerBase
|
||||
public async Task<ActionResult<List<UserStrategyDetailsViewModel>>> GetUserStrategies(string agentName)
|
||||
{
|
||||
string cacheKey = $"UserStrategies_{agentName}";
|
||||
|
||||
|
||||
// Check if the user strategy details are already cached
|
||||
var cachedDetails = _cacheService.GetValue<List<UserStrategyDetailsViewModel>>(cacheKey);
|
||||
|
||||
|
||||
if (cachedDetails != null && cachedDetails.Count > 0)
|
||||
{
|
||||
return Ok(cachedDetails);
|
||||
}
|
||||
|
||||
|
||||
// Get all strategies for the specified user
|
||||
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
|
||||
|
||||
|
||||
// Convert to detailed view model with additional information
|
||||
var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy)).ToList();
|
||||
|
||||
|
||||
// Cache the results for 5 minutes
|
||||
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
|
||||
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
@@ -253,32 +345,32 @@ public class DataController : ControllerBase
|
||||
public async Task<ActionResult<UserStrategyDetailsViewModel>> GetUserStrategy(string agentName, string strategyName)
|
||||
{
|
||||
string cacheKey = $"UserStrategy_{agentName}_{strategyName}";
|
||||
|
||||
|
||||
// Check if the user strategy details are already cached
|
||||
var cachedDetails = _cacheService.GetValue<UserStrategyDetailsViewModel>(cacheKey);
|
||||
|
||||
|
||||
if (cachedDetails != null)
|
||||
{
|
||||
return Ok(cachedDetails);
|
||||
}
|
||||
|
||||
|
||||
// Get the specific strategy for the user
|
||||
var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName));
|
||||
|
||||
|
||||
if (strategy == null)
|
||||
{
|
||||
return NotFound($"Strategy '{strategyName}' not found for user '{agentName}'");
|
||||
}
|
||||
|
||||
|
||||
// Map the strategy to a view model using the shared method
|
||||
var result = MapStrategyToViewModel(strategy);
|
||||
|
||||
|
||||
// Cache the results for 5 minutes
|
||||
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
|
||||
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps a trading bot to a strategy view model with detailed statistics
|
||||
/// </summary>
|
||||
@@ -288,7 +380,7 @@ public class DataController : ControllerBase
|
||||
{
|
||||
// Get the runtime directly from the bot
|
||||
TimeSpan runtimeSpan = strategy.GetRuntime();
|
||||
|
||||
|
||||
// Get the startup time from the bot's internal property
|
||||
// If bot is not running, we use MinValue as a placeholder
|
||||
DateTime startupTime = DateTime.MinValue;
|
||||
@@ -296,30 +388,30 @@ public class DataController : ControllerBase
|
||||
{
|
||||
startupTime = bot.StartupTime;
|
||||
}
|
||||
|
||||
|
||||
// Calculate ROI percentage based on PnL relative to account value
|
||||
decimal pnl = strategy.GetProfitAndLoss();
|
||||
|
||||
|
||||
// If we had initial investment amount, we could calculate ROI like:
|
||||
decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account
|
||||
decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0;
|
||||
|
||||
|
||||
// Calculate volume statistics
|
||||
decimal totalVolume = TradingBox.GetTotalVolumeTraded(strategy.Positions);
|
||||
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(strategy.Positions);
|
||||
|
||||
|
||||
// Calculate win/loss statistics
|
||||
(int wins, int losses) = TradingBox.GetWinLossCount(strategy.Positions);
|
||||
|
||||
|
||||
// Calculate ROI for last 24h
|
||||
decimal roiLast24h = TradingBox.GetLast24HROI(strategy.Positions);
|
||||
|
||||
|
||||
return new UserStrategyDetailsViewModel
|
||||
{
|
||||
Name = strategy.Name,
|
||||
StrategyName = strategy.ScenarioName,
|
||||
State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" :
|
||||
strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED",
|
||||
StrategyName = strategy.Config.ScenarioName,
|
||||
State = strategy.GetStatus() == BotStatus.Up.ToString() ? "RUNNING" :
|
||||
strategy.GetStatus() == BotStatus.Down.ToString() ? "STOPPED" : "UNUSED",
|
||||
PnL = pnl,
|
||||
ROIPercentage = roi,
|
||||
ROILast24H = roiLast24h,
|
||||
@@ -329,7 +421,8 @@ public class DataController : ControllerBase
|
||||
VolumeLast24H = volumeLast24h,
|
||||
Wins = wins,
|
||||
Losses = losses,
|
||||
Positions = strategy.Positions.OrderByDescending(p => p.Date).ToList() // Include sorted positions with most recent first
|
||||
Positions = strategy.Positions.OrderByDescending(p => p.Date)
|
||||
.ToList() // Include sorted positions with most recent first
|
||||
};
|
||||
}
|
||||
|
||||
@@ -347,20 +440,20 @@ public class DataController : ControllerBase
|
||||
{
|
||||
timeFilter = "Total"; // Default to Total if invalid
|
||||
}
|
||||
|
||||
|
||||
string cacheKey = $"PlatformSummary_{timeFilter}";
|
||||
|
||||
|
||||
// Check if the platform summary is already cached
|
||||
var cachedSummary = _cacheService.GetValue<PlatformSummaryViewModel>(cacheKey);
|
||||
|
||||
|
||||
if (cachedSummary != null)
|
||||
{
|
||||
return Ok(cachedSummary);
|
||||
}
|
||||
|
||||
|
||||
// Get all agents and their strategies
|
||||
var agentsWithStrategies = await _mediator.Send(new GetAllAgentsCommand(timeFilter));
|
||||
|
||||
|
||||
// Create the platform summary
|
||||
var summary = new PlatformSummaryViewModel
|
||||
{
|
||||
@@ -368,50 +461,50 @@ public class DataController : ControllerBase
|
||||
TotalActiveStrategies = agentsWithStrategies.Values.Sum(list => list.Count),
|
||||
TimeFilter = timeFilter
|
||||
};
|
||||
|
||||
|
||||
// Calculate total platform metrics
|
||||
decimal totalPlatformPnL = 0;
|
||||
decimal totalPlatformVolume = 0;
|
||||
decimal totalPlatformVolumeLast24h = 0;
|
||||
|
||||
|
||||
// Create summaries for each agent
|
||||
foreach (var agent in agentsWithStrategies)
|
||||
{
|
||||
var user = agent.Key;
|
||||
var strategies = agent.Value;
|
||||
|
||||
|
||||
if (strategies.Count == 0)
|
||||
{
|
||||
continue; // Skip agents with no strategies
|
||||
}
|
||||
|
||||
|
||||
// Combine all positions from all strategies
|
||||
var allPositions = strategies.SelectMany<ITradingBot, Position>(s => s.Positions).ToList();
|
||||
|
||||
|
||||
// Calculate agent metrics
|
||||
decimal totalPnL = TradingBox.GetPnLInTimeRange(allPositions, timeFilter);
|
||||
decimal pnlLast24h = TradingBox.GetPnLInTimeRange(allPositions, "24H");
|
||||
|
||||
|
||||
decimal totalROI = TradingBox.GetROIInTimeRange(allPositions, timeFilter);
|
||||
decimal roiLast24h = TradingBox.GetROIInTimeRange(allPositions, "24H");
|
||||
|
||||
|
||||
(int wins, int losses) = TradingBox.GetWinLossCountInTimeRange(allPositions, timeFilter);
|
||||
|
||||
|
||||
// Calculate trading volumes
|
||||
decimal totalVolume = TradingBox.GetTotalVolumeTraded(allPositions);
|
||||
decimal volumeLast24h = TradingBox.GetLast24HVolumeTraded(allPositions);
|
||||
|
||||
|
||||
// Calculate win rate
|
||||
int averageWinRate = 0;
|
||||
if (wins + losses > 0)
|
||||
{
|
||||
averageWinRate = (wins * 100) / (wins + losses);
|
||||
}
|
||||
|
||||
|
||||
// Add to agent summaries
|
||||
var agentSummary = new AgentSummaryViewModel
|
||||
{
|
||||
Username = user.Name,
|
||||
AgentName = user.AgentName,
|
||||
TotalPnL = totalPnL,
|
||||
PnLLast24h = pnlLast24h,
|
||||
TotalROI = totalROI,
|
||||
@@ -423,26 +516,26 @@ public class DataController : ControllerBase
|
||||
TotalVolume = totalVolume,
|
||||
VolumeLast24h = volumeLast24h
|
||||
};
|
||||
|
||||
|
||||
summary.AgentSummaries.Add(agentSummary);
|
||||
|
||||
|
||||
// Add to platform totals
|
||||
totalPlatformPnL += totalPnL;
|
||||
totalPlatformVolume += totalVolume;
|
||||
totalPlatformVolumeLast24h += volumeLast24h;
|
||||
}
|
||||
|
||||
|
||||
// Set the platform totals
|
||||
summary.TotalPlatformPnL = totalPlatformPnL;
|
||||
summary.TotalPlatformVolume = totalPlatformVolume;
|
||||
summary.TotalPlatformVolumeLast24h = totalPlatformVolumeLast24h;
|
||||
|
||||
|
||||
// Sort agent summaries by total PnL (highest first)
|
||||
summary.AgentSummaries = summary.AgentSummaries.OrderByDescending(a => a.TotalPnL).ToList();
|
||||
|
||||
|
||||
// Cache the results for 5 minutes
|
||||
_cacheService.SaveValue(cacheKey, summary, TimeSpan.FromMinutes(5));
|
||||
|
||||
|
||||
return Ok(summary);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Api.Authorization;
|
||||
using Managing.Api.Models.Requests;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -12,10 +13,9 @@ namespace Managing.Api.Controllers;
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class UserController : ControllerBase
|
||||
public class UserController : BaseController
|
||||
{
|
||||
private IConfiguration _config;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IJwtUtils _jwtUtils;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,9 +25,9 @@ public class UserController : ControllerBase
|
||||
/// <param name="userService">Service for user-related operations.</param>
|
||||
/// <param name="jwtUtils">Utility for JWT token operations.</param>
|
||||
public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils)
|
||||
: base(userService)
|
||||
{
|
||||
_config = config;
|
||||
_userService = userService;
|
||||
_jwtUtils = jwtUtils;
|
||||
}
|
||||
|
||||
@@ -49,5 +49,30 @@ public class UserController : ControllerBase
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
/// <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]
|
||||
[Range(10.00, double.MaxValue, ErrorMessage = "Initial trading balance must be greater than ten")]
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Username of the agent
|
||||
/// AgentName of the agent
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
public string AgentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.Strategies;
|
||||
using Managing.Domain.Trades;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Api.Models.Responses
|
||||
@@ -23,5 +23,7 @@ namespace Managing.Api.Models.Responses
|
||||
[Required] public BotType BotType { get; internal set; }
|
||||
[Required] public string AccountName { get; internal set; }
|
||||
[Required] public MoneyManagement MoneyManagement { get; internal set; }
|
||||
[Required] public string Identifier { get; set; }
|
||||
[Required] public string AgentName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using HealthChecks.UI.Client;
|
||||
using Managing.Api.Authorization;
|
||||
using Managing.Api.Filters;
|
||||
using Managing.Api.HealthChecks;
|
||||
using Managing.Api.Workers;
|
||||
using Managing.Application.Hubs;
|
||||
using Managing.Bootstrap;
|
||||
using Managing.Common;
|
||||
@@ -195,7 +196,10 @@ builder.Services.AddSwaggerGen(options =>
|
||||
});
|
||||
|
||||
builder.WebHost.SetupDiscordBot();
|
||||
// builder.Services.AddHostedService<BotManagerWorker>();
|
||||
if (builder.Configuration.GetValue<bool>("EnableBotManager", false))
|
||||
{
|
||||
builder.Services.AddHostedService<BotManagerWorker>();
|
||||
}
|
||||
|
||||
// App
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -24,5 +24,6 @@
|
||||
"ElasticConfiguration": {
|
||||
"Uri": "http://elasticsearch:9200"
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
"AllowedHosts": "*",
|
||||
"EnableBotManager": true
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Managing.Application.Abstractions.Repositories;
|
||||
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<User> GetUserByAgentNameAsync(string agentName);
|
||||
Task<User> GetUserByNameAsync(string name);
|
||||
Task InsertUserAsync(User user);
|
||||
Task UpdateUser(User user);
|
||||
|
||||
@@ -22,7 +22,9 @@ namespace Managing.Application.Abstractions.Services
|
||||
User user = null,
|
||||
bool isForWatchingOnly = false,
|
||||
bool save = false,
|
||||
List<Candle>? initialCandles = null);
|
||||
List<Candle>? initialCandles = null,
|
||||
decimal cooldownPeriod = 1,
|
||||
int maxLossStreak = 0);
|
||||
|
||||
Task<Backtest> RunFlippingBotBacktest(
|
||||
Account account,
|
||||
@@ -36,16 +38,34 @@ namespace Managing.Application.Abstractions.Services
|
||||
User user = null,
|
||||
bool isForWatchingOnly = false,
|
||||
bool save = false,
|
||||
List<Candle>? initialCandles = null);
|
||||
List<Candle>? initialCandles = null,
|
||||
decimal cooldownPeriod = 1,
|
||||
int maxLossStreak = 0);
|
||||
|
||||
bool DeleteBacktest(string id);
|
||||
bool DeleteBacktests();
|
||||
|
||||
Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement, Scenario scenario,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null);
|
||||
Task<Backtest> RunScalpingBotBacktest(
|
||||
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,
|
||||
Timeframe timeframe, List<Candle> candles, decimal balance, User user = null);
|
||||
Task<Backtest> RunFlippingBotBacktest(
|
||||
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
|
||||
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> 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);
|
||||
|
||||
if (worker.IsActive)
|
||||
if (worker.IsActive || worker.WorkerType.Equals(WorkerType.BotManager))
|
||||
{
|
||||
await Run(cancellationToken);
|
||||
_executionCount++;
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Workflows;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions
|
||||
{
|
||||
public interface IBotFactory
|
||||
{
|
||||
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
|
||||
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
|
||||
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
|
||||
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance);
|
||||
IBot CreateSimpleBot(string botName, Workflow workflow);
|
||||
ITradingBot CreateScalpingBot(TradingBotConfig config);
|
||||
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
|
||||
ITradingBot CreateFlippingBot(TradingBotConfig config);
|
||||
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,30 @@
|
||||
using Managing.Common;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Domain.Workflows;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions;
|
||||
|
||||
public interface IBotService
|
||||
{
|
||||
void SaveOrUpdateBotBackup(BotBackup botBackup);
|
||||
void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data);
|
||||
void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data);
|
||||
void AddSimpleBotToCache(IBot bot);
|
||||
void AddTradingBotToCache(ITradingBot bot);
|
||||
List<ITradingBot> GetActiveBots();
|
||||
IEnumerable<BotBackup> GetSavedBots();
|
||||
void StartBotFromBackup(BotBackup backupBot);
|
||||
BotBackup GetBotBackup(string name);
|
||||
BotBackup GetBotBackup(string identifier);
|
||||
|
||||
ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
|
||||
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
|
||||
|
||||
ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker,
|
||||
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
|
||||
|
||||
ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Enums.Ticker ticker,
|
||||
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
|
||||
|
||||
ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Enums.Ticker ticker,
|
||||
string scenario, Enums.Timeframe interval, bool isForWatchingOnly, decimal initialTradingAmount);
|
||||
ITradingBot CreateScalpingBot(TradingBotConfig config);
|
||||
ITradingBot CreateBacktestScalpingBot(TradingBotConfig config);
|
||||
ITradingBot CreateFlippingBot(TradingBotConfig config);
|
||||
ITradingBot CreateBacktestFlippingBot(TradingBotConfig config);
|
||||
|
||||
IBot CreateSimpleBot(string botName, Workflow workflow);
|
||||
Task<string> StopBot(string requestName);
|
||||
Task<bool> DeleteBot(string requestName);
|
||||
Task<string> RestartBot(string requestName);
|
||||
Task<string> StopBot(string botName);
|
||||
Task<bool> DeleteBot(string botName);
|
||||
Task<string> RestartBot(string botName);
|
||||
void DeleteBotBackup(string backupBotName);
|
||||
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.Candles;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.Abstractions
|
||||
{
|
||||
public interface ITradingBot : IBot
|
||||
{
|
||||
HashSet<Signal> Signals { get; set; }
|
||||
List<Position> Positions { get; set; }
|
||||
TradingBotConfig Config { get; set; }
|
||||
Account Account { get; set; }
|
||||
HashSet<IStrategy> Strategies { get; set; }
|
||||
FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
||||
HashSet<Candle> Candles { get; set; }
|
||||
Timeframe Timeframe { get; set; }
|
||||
HashSet<IStrategy> Strategies { get; set; }
|
||||
Ticker Ticker { get; }
|
||||
string ScenarioName { get; }
|
||||
string AccountName { get; }
|
||||
bool IsForWatchingOnly { get; set; }
|
||||
MoneyManagement MoneyManagement { get; set; }
|
||||
BotType BotType { get; set; }
|
||||
HashSet<Signal> Signals { get; set; }
|
||||
List<Position> Positions { get; set; }
|
||||
Dictionary<DateTime, decimal> WalletBalances { get; set; }
|
||||
Dictionary<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
||||
User User { get; set; }
|
||||
string Identifier { get; set; }
|
||||
DateTime StartupTime { get; set; }
|
||||
DateTime PreloadSince { get; set; }
|
||||
int PreloadedCandlesCount { get; set; }
|
||||
decimal Fee { get; set; }
|
||||
Scenario Scenario { get; set; }
|
||||
|
||||
Task Run();
|
||||
Task ToggleIsForWatchOnly();
|
||||
@@ -38,5 +37,6 @@ namespace Managing.Application.Abstractions
|
||||
void LoadScenario(string scenarioName);
|
||||
void UpdateStrategiesValues();
|
||||
Task LoadAccount();
|
||||
Task<Position> OpenPositionManually(TradeDirection direction);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Core;
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
@@ -64,10 +65,26 @@ namespace Managing.Application.Backtesting
|
||||
User user = null,
|
||||
bool isForWatchingOnly = false,
|
||||
bool save = false,
|
||||
List<Candle> initialCandles = null)
|
||||
List<Candle>? initialCandles = null,
|
||||
decimal cooldownPeriod = 1,
|
||||
int maxLossStreak = 0)
|
||||
{
|
||||
var scalpingBot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, isForWatchingOnly, balance);
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = account.Name,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario.Name,
|
||||
Timeframe = timeframe,
|
||||
IsForWatchingOnly = isForWatchingOnly,
|
||||
BotTradingBalance = balance,
|
||||
BotType = BotType.ScalpingBot,
|
||||
IsForBacktest = true,
|
||||
CooldownPeriod = cooldownPeriod,
|
||||
MaxLossStreak = maxLossStreak
|
||||
};
|
||||
|
||||
var scalpingBot = _botFactory.CreateBacktestScalpingBot(config);
|
||||
scalpingBot.LoadScenario(scenario.Name);
|
||||
await scalpingBot.LoadAccount();
|
||||
var candles = initialCandles ?? GetCandles(account, ticker, timeframe, startDate, endDate);
|
||||
@@ -91,21 +108,6 @@ namespace Managing.Application.Backtesting
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<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(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
@@ -118,10 +120,26 @@ namespace Managing.Application.Backtesting
|
||||
User user = null,
|
||||
bool isForWatchingOnly = 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",
|
||||
timeframe, false, balance);
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = account.Name,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario.Name,
|
||||
Timeframe = timeframe,
|
||||
IsForWatchingOnly = isForWatchingOnly,
|
||||
BotTradingBalance = balance,
|
||||
BotType = BotType.FlippingBot,
|
||||
IsForBacktest = true,
|
||||
CooldownPeriod = cooldownPeriod,
|
||||
MaxLossStreak = maxLossStreak
|
||||
};
|
||||
|
||||
var flippingBot = _botFactory.CreateBacktestFlippingBot(config);
|
||||
flippingBot.LoadScenario(scenario.Name);
|
||||
await flippingBot.LoadAccount();
|
||||
|
||||
@@ -146,13 +164,34 @@ namespace Managing.Application.Backtesting
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Backtest> RunScalpingBotBacktest(Account account, MoneyManagement moneyManagement,
|
||||
public async Task<Backtest> RunScalpingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
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 bot = _botFactory.CreateBacktestScalpingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, false, balance);
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = account.Name,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario.Name,
|
||||
Timeframe = timeframe,
|
||||
IsForWatchingOnly = false,
|
||||
BotTradingBalance = balance,
|
||||
BotType = BotType.ScalpingBot,
|
||||
IsForBacktest = true,
|
||||
CooldownPeriod = cooldownPeriod,
|
||||
MaxLossStreak = maxLossStreak
|
||||
};
|
||||
|
||||
var bot = _botFactory.CreateBacktestScalpingBot(config);
|
||||
bot.LoadScenario(scenario.Name);
|
||||
await bot.LoadAccount();
|
||||
|
||||
@@ -167,13 +206,34 @@ namespace Managing.Application.Backtesting
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Backtest> RunFlippingBotBacktest(Account account, MoneyManagement moneyManagement,
|
||||
public async Task<Backtest> RunFlippingBotBacktest(
|
||||
Account account,
|
||||
MoneyManagement moneyManagement,
|
||||
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 bot = _botFactory.CreateBacktestFlippingBot(account.Name, moneyManagement, ticker, "scenario",
|
||||
timeframe, false, balance);
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = account.Name,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario.Name,
|
||||
Timeframe = timeframe,
|
||||
IsForWatchingOnly = false,
|
||||
BotTradingBalance = balance,
|
||||
BotType = BotType.FlippingBot,
|
||||
IsForBacktest = true,
|
||||
CooldownPeriod = cooldownPeriod,
|
||||
MaxLossStreak = maxLossStreak
|
||||
};
|
||||
|
||||
var bot = _botFactory.CreateBacktestFlippingBot(config);
|
||||
bot.LoadScenario(scenario.Name);
|
||||
await bot.LoadAccount();
|
||||
|
||||
@@ -188,6 +248,21 @@ namespace Managing.Application.Backtesting
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<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(
|
||||
Ticker ticker,
|
||||
Scenario scenario,
|
||||
@@ -239,7 +314,7 @@ namespace Managing.Application.Backtesting
|
||||
var score = BacktestScorer.CalculateTotalScore(scoringParams);
|
||||
|
||||
var result = new Backtest(ticker, scenario.Name, bot.Positions, bot.Signals.ToList(), timeframe, candles,
|
||||
bot.BotType, account.Name)
|
||||
bot.Config.BotType, account.Name)
|
||||
{
|
||||
FinalPnl = finalPnl,
|
||||
WinRate = winRate,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Workflows;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -38,82 +37,58 @@ namespace Managing.Application.Bots.Base
|
||||
return new SimpleBot(botName, _tradingBotLogger, workflow, _botService);
|
||||
}
|
||||
|
||||
ITradingBot IBotFactory.CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
|
||||
ITradingBot IBotFactory.CreateScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
config.BotType = BotType.ScalpingBot;
|
||||
return new ScalpingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
_botService,
|
||||
initialTradingBalance,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
ITradingBot IBotFactory.CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
|
||||
ITradingBot IBotFactory.CreateBacktestScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
config.BotType = BotType.ScalpingBot;
|
||||
config.IsForBacktest = true;
|
||||
return new ScalpingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
"BacktestBot",
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
_botService,
|
||||
initialTradingBalance,
|
||||
isForBacktest: true,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
|
||||
public ITradingBot CreateFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
config.BotType = BotType.FlippingBot;
|
||||
return new FlippingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
_botService,
|
||||
initialTradingBalance,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement, Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly, decimal initialTradingBalance)
|
||||
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
config.BotType = BotType.FlippingBot;
|
||||
config.IsForBacktest = true;
|
||||
return new FlippingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
"BacktestBot",
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
_botService,
|
||||
initialTradingBalance,
|
||||
isForBacktest: true,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -8,39 +7,18 @@ namespace Managing.Application.Bots
|
||||
{
|
||||
public class FlippingBot : TradingBot
|
||||
{
|
||||
public FlippingBot(string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
string name,
|
||||
string scenarioName,
|
||||
public FlippingBot(
|
||||
IExchangeService exchangeService,
|
||||
Ticker ticker,
|
||||
ITradingService tradingService,
|
||||
ILogger<TradingBot> logger,
|
||||
Timeframe timeframe,
|
||||
ITradingService tradingService,
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
IBotService botService,
|
||||
decimal initialTradingBalance,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false)
|
||||
: base(accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
ticker,
|
||||
scenarioName,
|
||||
exchangeService,
|
||||
logger,
|
||||
tradingService,
|
||||
timeframe,
|
||||
accountService,
|
||||
messengerService,
|
||||
botService,
|
||||
initialTradingBalance,
|
||||
isForBacktest,
|
||||
isForWatchingOnly,
|
||||
flipPosition: true)
|
||||
TradingBotConfig config)
|
||||
: base(exchangeService, logger, tradingService, accountService, messengerService, botService, config)
|
||||
{
|
||||
BotType = BotType.FlippingBot;
|
||||
Config.BotType = BotType.FlippingBot;
|
||||
Config.FlipPosition = true;
|
||||
}
|
||||
|
||||
public sealed override void Start()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -8,38 +7,17 @@ namespace Managing.Application.Bots
|
||||
{
|
||||
public class ScalpingBot : TradingBot
|
||||
{
|
||||
public ScalpingBot(string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
string name,
|
||||
string scenarioName,
|
||||
public ScalpingBot(
|
||||
IExchangeService exchangeService,
|
||||
Ticker ticker,
|
||||
ITradingService tradingService,
|
||||
ILogger<TradingBot> logger,
|
||||
Timeframe timeframe,
|
||||
ITradingService tradingService,
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
IBotService botService,
|
||||
decimal initialTradingBalance,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false)
|
||||
: base(accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
ticker,
|
||||
scenarioName,
|
||||
exchangeService,
|
||||
logger,
|
||||
tradingService,
|
||||
timeframe,
|
||||
accountService,
|
||||
messengerService,
|
||||
botService,
|
||||
initialTradingBalance,
|
||||
isForBacktest,
|
||||
isForWatchingOnly)
|
||||
TradingBotConfig config)
|
||||
: base(exchangeService, logger, tradingService, accountService, messengerService, botService, config)
|
||||
{
|
||||
BotType = BotType.ScalpingBot;
|
||||
Config.BotType = BotType.ScalpingBot;
|
||||
}
|
||||
|
||||
public sealed override void Start()
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Managing.Application.Bots
|
||||
public override void SaveBackup()
|
||||
{
|
||||
var data = JsonConvert.SerializeObject(_workflow);
|
||||
_botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, data);
|
||||
_botService.SaveOrUpdateBotBackup(User, Identifier, BotType.SimpleBot, Status, data);
|
||||
}
|
||||
|
||||
public override void LoadBackup(BotBackup backup)
|
||||
|
||||
@@ -28,76 +28,47 @@ public class TradingBot : Bot, ITradingBot
|
||||
private readonly ITradingService TradingService;
|
||||
private readonly IBotService BotService;
|
||||
|
||||
public TradingBotConfig Config { get; set; }
|
||||
public Account Account { get; set; }
|
||||
public HashSet<IStrategy> Strategies { get; set; }
|
||||
public FixedSizeQueue<Candle> OptimizedCandles { get; set; }
|
||||
public HashSet<Candle> Candles { get; set; }
|
||||
public HashSet<Signal> Signals { 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<StrategyType, StrategiesResultBase> StrategiesValues { get; set; }
|
||||
public DateTime StartupTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The dedicated trading balance for this bot in USD
|
||||
/// </summary>
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
public DateTime PreloadSince { get; set; }
|
||||
public int PreloadedCandlesCount { get; set; }
|
||||
public decimal Fee { get; set; }
|
||||
public Scenario Scenario { get; set; }
|
||||
|
||||
public TradingBot(
|
||||
string accountName,
|
||||
MoneyManagement moneyManagement,
|
||||
string name,
|
||||
Ticker ticker,
|
||||
string scenarioName,
|
||||
IExchangeService exchangeService,
|
||||
ILogger<TradingBot> logger,
|
||||
ITradingService tradingService,
|
||||
Timeframe timeframe,
|
||||
IAccountService accountService,
|
||||
IMessengerService messengerService,
|
||||
IBotService botService,
|
||||
decimal initialTradingBalance,
|
||||
bool isForBacktest = false,
|
||||
bool isForWatchingOnly = false,
|
||||
bool flipPosition = false)
|
||||
: base(name)
|
||||
TradingBotConfig config
|
||||
)
|
||||
: base(config.AccountName)
|
||||
{
|
||||
ExchangeService = exchangeService;
|
||||
AccountService = accountService;
|
||||
MessengerService = messengerService;
|
||||
TradingService = tradingService;
|
||||
BotService = botService;
|
||||
Logger = logger;
|
||||
|
||||
if (initialTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||
if (config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}",
|
||||
nameof(initialTradingBalance));
|
||||
nameof(config.BotTradingBalance));
|
||||
}
|
||||
|
||||
IsForWatchingOnly = isForWatchingOnly;
|
||||
FlipPosition = flipPosition;
|
||||
AccountName = accountName;
|
||||
MoneyManagement = moneyManagement;
|
||||
Ticker = ticker;
|
||||
ScenarioName = scenarioName;
|
||||
Timeframe = timeframe;
|
||||
IsForBacktest = isForBacktest;
|
||||
Logger = logger;
|
||||
BotTradingBalance = initialTradingBalance;
|
||||
Config = config;
|
||||
|
||||
Strategies = new HashSet<IStrategy>();
|
||||
Signals = new HashSet<Signal>();
|
||||
@@ -107,10 +78,10 @@ public class TradingBot : Bot, ITradingBot
|
||||
WalletBalances = new Dictionary<DateTime, decimal>();
|
||||
StrategiesValues = new Dictionary<StrategyType, StrategiesResultBase>();
|
||||
|
||||
if (!isForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
Interval = CandleExtensions.GetIntervalFromTimeframe(timeframe);
|
||||
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(timeframe);
|
||||
Interval = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe);
|
||||
PreloadSince = CandleExtensions.GetBotPreloadSinceFromTimeframe(Config.Timeframe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +91,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
// Load account synchronously
|
||||
await LoadAccount();
|
||||
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
LoadScenario(ScenarioName);
|
||||
LoadScenario(Config.ScenarioName);
|
||||
await PreloadCandles();
|
||||
await CancelAllOrders();
|
||||
|
||||
@@ -146,10 +117,10 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
public async Task LoadAccount()
|
||||
{
|
||||
var account = await AccountService.GetAccount(AccountName, false, false);
|
||||
var account = await AccountService.GetAccount(Config.AccountName, false, false);
|
||||
if (account == null)
|
||||
{
|
||||
Logger.LogWarning($"No account found for this {AccountName}");
|
||||
Logger.LogWarning($"No account found for this {Config.AccountName}");
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
@@ -185,7 +156,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
// Check broker balance before running
|
||||
var balance = await ExchangeService.GetBalance(Account, false);
|
||||
@@ -200,25 +171,25 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
Logger.LogInformation($"____________________{Name}____________________");
|
||||
Logger.LogInformation(
|
||||
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {BotType} - Ticker : {Ticker}");
|
||||
$"Time : {DateTime.Now} - Server time {DateTime.Now.ToUniversalTime()} - Last candle : {OptimizedCandles.Last().Date} - Bot : {Name} - Type {Config.BotType} - Ticker : {Config.Ticker}");
|
||||
}
|
||||
|
||||
var previousLastCandle = OptimizedCandles.LastOrDefault();
|
||||
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
await UpdateCandles();
|
||||
|
||||
var currentLastCandle = OptimizedCandles.LastOrDefault();
|
||||
|
||||
if (currentLastCandle != previousLastCandle || IsForBacktest)
|
||||
if (currentLastCandle != previousLastCandle || Config.IsForBacktest)
|
||||
await UpdateSignals(OptimizedCandles);
|
||||
else
|
||||
Logger.LogInformation($"No need to update signals for {Ticker}");
|
||||
Logger.LogInformation($"No need to update signals for {Config.Ticker}");
|
||||
|
||||
if (!IsForWatchingOnly)
|
||||
if (!Config.IsForWatchingOnly)
|
||||
await ManagePositions();
|
||||
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
SaveBackup();
|
||||
UpdateStrategiesValues();
|
||||
@@ -248,7 +219,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (OptimizedCandles.Any())
|
||||
return;
|
||||
|
||||
var candles = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, PreloadSince, Timeframe);
|
||||
var candles =
|
||||
await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, PreloadSince, Config.Timeframe);
|
||||
|
||||
foreach (var candle in candles.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||
{
|
||||
@@ -272,25 +244,22 @@ public class TradingBot : Bot, ITradingBot
|
||||
await AddSignal(signal);
|
||||
}
|
||||
|
||||
|
||||
private async Task AddSignal(Signal signal)
|
||||
{
|
||||
// if (!IsForBacktest)
|
||||
// TradingService.InsertSignal(signal);
|
||||
|
||||
if (IsForWatchingOnly || (ExecutionCount < 1 && !IsForBacktest))
|
||||
if (Config.IsForWatchingOnly || (ExecutionCount < 1 && !Config.IsForBacktest))
|
||||
signal.Status = SignalStatus.Expired;
|
||||
|
||||
Signals.Add(signal);
|
||||
|
||||
var signalText = $"{ScenarioName} trigger a signal. Signal told you " +
|
||||
$"to {signal.Direction} {Ticker} on {Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
||||
var signalText = $"{Config.ScenarioName} trigger a signal. Signal told you " +
|
||||
$"to {signal.Direction} {Config.Ticker} on {Config.Timeframe}. The confidence in this signal is {signal.Confidence}. Identifier : {signal.Identifier}";
|
||||
|
||||
Logger.LogInformation(signalText);
|
||||
|
||||
if (IsForWatchingOnly && !IsForBacktest && ExecutionCount > 0)
|
||||
if (Config.IsForWatchingOnly && !Config.IsForBacktest && ExecutionCount > 0)
|
||||
{
|
||||
await MessengerService.SendSignal(signalText, Account.Exchange, Ticker, signal.Direction, Timeframe);
|
||||
await MessengerService.SendSignal(signalText, Account.Exchange, Config.Ticker, signal.Direction,
|
||||
Config.Timeframe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +269,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
return;
|
||||
|
||||
var lastCandle = OptimizedCandles.Last();
|
||||
var newCandle = await ExchangeService.GetCandlesInflux(Account.Exchange, Ticker, lastCandle.Date, Timeframe);
|
||||
var newCandle =
|
||||
await ExchangeService.GetCandlesInflux(Account.Exchange, Config.Ticker, lastCandle.Date, Config.Timeframe);
|
||||
|
||||
foreach (var candle in newCandle.Where(c => c.Date < DateTime.Now.ToUniversalTime()))
|
||||
{
|
||||
@@ -342,7 +312,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (WalletBalances.Count == 0)
|
||||
{
|
||||
// WalletBalances[date] = await ExchangeService.GetBalance(Account, IsForBacktest);
|
||||
WalletBalances[date] = BotTradingBalance;
|
||||
WalletBalances[date] = Config.BotTradingBalance;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -353,24 +323,23 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task UpdatePosition(Signal signal, Position positionForSignal)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInformation($"Updating position {positionForSignal.SignalIdentifier}");
|
||||
|
||||
var position = IsForBacktest
|
||||
var position = Config.IsForBacktest
|
||||
? positionForSignal
|
||||
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
|
||||
|
||||
var positionsExchange = IsForBacktest
|
||||
var positionsExchange = Config.IsForBacktest
|
||||
? new List<Position> { position }
|
||||
: await TradingService.GetBrokerPositions(Account);
|
||||
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Ticker);
|
||||
var brokerPosition = positionsExchange.FirstOrDefault(p => p.Ticker == Config.Ticker);
|
||||
if (brokerPosition != null)
|
||||
{
|
||||
UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);
|
||||
@@ -395,7 +364,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
if (position.Status == PositionStatus.New)
|
||||
{
|
||||
var orders = await ExchangeService.GetOpenOrders(Account, Ticker);
|
||||
var orders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
|
||||
if (orders.Any())
|
||||
{
|
||||
await LogInformation(
|
||||
@@ -420,9 +389,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
// Position might be partially filled, meaning that TPSL havent been sended yet
|
||||
// But the position might already been closed by the exchange so we have to check should be closed
|
||||
var lastCandle = IsForBacktest
|
||||
var lastCandle = Config.IsForBacktest
|
||||
? OptimizedCandles.Last()
|
||||
: ExchangeService.GetCandle(Account, Ticker, DateTime.UtcNow);
|
||||
: ExchangeService.GetCandle(Account, Config.Ticker, DateTime.UtcNow);
|
||||
|
||||
if (positionForSignal.OriginDirection == TradeDirection.Long)
|
||||
{
|
||||
@@ -512,7 +481,6 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task OpenPosition(Signal signal)
|
||||
{
|
||||
// Check if a position is already open
|
||||
@@ -521,9 +489,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
var openedPosition = Positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
||||
&& p.SignalIdentifier != signal.Identifier);
|
||||
|
||||
var lastPrice = IsForBacktest
|
||||
var lastPrice = Config.IsForBacktest
|
||||
? OptimizedCandles.Last().Close
|
||||
: ExchangeService.GetPrice(Account, Ticker, DateTime.UtcNow);
|
||||
: ExchangeService.GetPrice(Account, Config.Ticker, DateTime.UtcNow);
|
||||
|
||||
// If position open
|
||||
if (openedPosition != null)
|
||||
@@ -542,7 +510,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
// An operation is already open for the opposite direction
|
||||
// ==> Flip the position
|
||||
if (FlipPosition)
|
||||
if (Config.FlipPosition)
|
||||
{
|
||||
await LogInformation("Try to flip the position because of an opposite direction signal");
|
||||
await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||
@@ -561,10 +529,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!CanOpenPosition(signal))
|
||||
if (!(await CanOpenPosition(signal)))
|
||||
{
|
||||
await LogInformation(
|
||||
"Tried to open position but last position was a loss. Wait for an opposition direction side or wait x candles to open a new position");
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return;
|
||||
}
|
||||
@@ -575,15 +541,15 @@ public class TradingBot : Bot, ITradingBot
|
||||
try
|
||||
{
|
||||
var command = new OpenPositionRequest(
|
||||
AccountName,
|
||||
MoneyManagement,
|
||||
Config.AccountName,
|
||||
Config.MoneyManagement,
|
||||
signal.Direction,
|
||||
Ticker,
|
||||
Config.Ticker,
|
||||
PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
User,
|
||||
BotTradingBalance,
|
||||
IsForBacktest,
|
||||
Config.BotTradingBalance,
|
||||
Config.IsForBacktest,
|
||||
lastPrice,
|
||||
signalIdentifier: signal.Identifier);
|
||||
|
||||
@@ -598,7 +564,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
{
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
|
||||
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await MessengerService.SendPosition(position);
|
||||
}
|
||||
@@ -622,25 +588,142 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanOpenPosition(Signal signal)
|
||||
private async Task<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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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()
|
||||
&& p.SignalIdentifier != signal.Identifier
|
||||
&& p.OriginDirection == signal.Direction);
|
||||
|
||||
if (lastPosition == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var cooldownCandle = OptimizedCandles.TakeLast((int)Config.CooldownPeriod).FirstOrDefault();
|
||||
if (cooldownCandle == null)
|
||||
{
|
||||
await LogWarning("Cannot check cooldown period: Not enough candles available");
|
||||
return false;
|
||||
}
|
||||
|
||||
var tenCandleAgo = OptimizedCandles.TakeLast(10).First();
|
||||
var positionSignal = Signals.FirstOrDefault(s => s.Identifier == lastPosition.SignalIdentifier);
|
||||
if (positionSignal == null)
|
||||
{
|
||||
await LogWarning($"Cannot find signal for last position {lastPosition.Identifier}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return positionSignal.Date < tenCandleAgo.Date;
|
||||
var canOpenPosition = positionSignal.Date < cooldownCandle.Date;
|
||||
if (!canOpenPosition)
|
||||
{
|
||||
await LogInformation(
|
||||
$"Position cannot be opened: Cooldown period not elapsed. Last position date: {positionSignal.Date}, Cooldown candle date: {cooldownCandle.Date}");
|
||||
}
|
||||
|
||||
return canOpenPosition;
|
||||
}
|
||||
|
||||
public async Task CloseTrade(Signal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
||||
@@ -654,18 +737,18 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
await LogInformation(
|
||||
$"Trying to close trade {Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
|
||||
$"Trying to close trade {Config.Ticker} at {lastPrice} - Type : {tradeToClose.TradeType} - Quantity : {tradeToClose.Quantity} " +
|
||||
$"- Closing Position : {tradeClosingPosition}");
|
||||
|
||||
// Get status of position before closing it. The position might be already close by the exchange
|
||||
if (!IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Ticker) == 0)
|
||||
if (!Config.IsForBacktest && await ExchangeService.GetQuantityInPosition(Account, Config.Ticker) == 0)
|
||||
{
|
||||
Logger.LogInformation($"Trade already close on exchange");
|
||||
await HandleClosedPosition(position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var command = new ClosePositionCommand(position, lastPrice, isForBacktest: IsForBacktest);
|
||||
var command = new ClosePositionCommand(position, lastPrice, isForBacktest: Config.IsForBacktest);
|
||||
try
|
||||
{
|
||||
var closedPosition = (new ClosePositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||
@@ -710,12 +793,12 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (position.ProfitAndLoss != null)
|
||||
{
|
||||
// Add PnL (could be positive or negative)
|
||||
BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||
|
||||
// Subtract fees
|
||||
BotTradingBalance -= GetPositionFees(position);
|
||||
Config.BotTradingBalance -= GetPositionFees(position);
|
||||
|
||||
Logger.LogInformation($"Updated bot trading balance to: {BotTradingBalance}");
|
||||
Logger.LogInformation($"Updated bot trading balance to: {Config.BotTradingBalance}");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -723,7 +806,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
await LogWarning("Weird things happen - Trying to update position status, but no position found");
|
||||
}
|
||||
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await MessengerService.SendClosingPosition(position);
|
||||
}
|
||||
@@ -733,15 +816,15 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task CancelAllOrders()
|
||||
{
|
||||
if (!IsForBacktest && !IsForWatchingOnly)
|
||||
if (!Config.IsForBacktest && !Config.IsForWatchingOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
var openOrders = await ExchangeService.GetOpenOrders(Account, Ticker);
|
||||
var openOrders = await ExchangeService.GetOpenOrders(Account, Config.Ticker);
|
||||
if (openOrders.Any())
|
||||
{
|
||||
var openPositions = (await ExchangeService.GetBrokerPositions(Account))
|
||||
.Where(p => p.Ticker == Ticker);
|
||||
.Where(p => p.Ticker == Config.Ticker);
|
||||
var cancelClose = openPositions.Any();
|
||||
|
||||
if (cancelClose)
|
||||
@@ -750,15 +833,15 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation($"Canceling all orders for {Ticker}");
|
||||
await ExchangeService.CancelOrder(Account, Ticker);
|
||||
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
|
||||
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
|
||||
Logger.LogInformation($"Canceling all orders for {Config.Ticker}");
|
||||
await ExchangeService.CancelOrder(Account, Config.Ticker);
|
||||
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Config.Ticker);
|
||||
Logger.LogInformation($"Closing all {Config.Ticker} orders status : {closePendingOrderStatus}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInformation($"No need to cancel orders for {Ticker}");
|
||||
Logger.LogInformation($"No need to cancel orders for {Config.Ticker}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -771,10 +854,11 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task SetPositionStatus(string signalIdentifier, PositionStatus positionStatus)
|
||||
{
|
||||
if (!Positions.First(p => p.SignalIdentifier == signalIdentifier).Status.Equals(positionStatus))
|
||||
var position = Positions.First(p => p.SignalIdentifier == signalIdentifier);
|
||||
if (!position.Status.Equals(positionStatus))
|
||||
{
|
||||
await LogInformation($"Position {signalIdentifier} is now {positionStatus}");
|
||||
Positions.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||
await LogInformation($"Position {signalIdentifier} new status {position.Status} => {positionStatus}");
|
||||
}
|
||||
|
||||
SetSignalStatus(signalIdentifier,
|
||||
@@ -864,8 +948,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
public async Task ToggleIsForWatchOnly()
|
||||
{
|
||||
IsForWatchingOnly = (!IsForWatchingOnly);
|
||||
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {IsForWatchingOnly}");
|
||||
Config.IsForWatchingOnly = !Config.IsForWatchingOnly;
|
||||
await LogInformation($"Watch only toggle for bot : {Name} - Watch only : {Config.IsForWatchingOnly}");
|
||||
}
|
||||
|
||||
private async Task LogInformation(string message)
|
||||
@@ -883,7 +967,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
private async Task SendTradeMessage(string message, bool isBadBehavior = false)
|
||||
{
|
||||
if (!IsForBacktest)
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await MessengerService.SendTradeMessage(message, isBadBehavior);
|
||||
}
|
||||
@@ -894,43 +978,47 @@ public class TradingBot : Bot, ITradingBot
|
||||
var data = new TradingBotBackup
|
||||
{
|
||||
Name = Name,
|
||||
BotType = BotType,
|
||||
BotType = Config.BotType,
|
||||
Signals = Signals,
|
||||
Positions = Positions,
|
||||
Timeframe = Timeframe,
|
||||
Ticker = Ticker,
|
||||
ScenarioName = ScenarioName,
|
||||
AccountName = AccountName,
|
||||
IsForWatchingOnly = IsForWatchingOnly,
|
||||
Timeframe = Config.Timeframe,
|
||||
Ticker = Config.Ticker,
|
||||
ScenarioName = Config.ScenarioName,
|
||||
AccountName = Config.AccountName,
|
||||
IsForWatchingOnly = Config.IsForWatchingOnly,
|
||||
WalletBalances = WalletBalances,
|
||||
MoneyManagement = MoneyManagement,
|
||||
BotTradingBalance = BotTradingBalance,
|
||||
MoneyManagement = Config.MoneyManagement,
|
||||
BotTradingBalance = Config.BotTradingBalance,
|
||||
StartupTime = StartupTime,
|
||||
CooldownPeriod = Config.CooldownPeriod,
|
||||
};
|
||||
BotService.SaveOrUpdateBotBackup(User, Identifier, BotType, JsonConvert.SerializeObject(data));
|
||||
BotService.SaveOrUpdateBotBackup(User, Identifier, Config.BotType, Status, JsonConvert.SerializeObject(data));
|
||||
}
|
||||
|
||||
public override void LoadBackup(BotBackup backup)
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<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;
|
||||
Positions = data.Positions;
|
||||
WalletBalances = data.WalletBalances;
|
||||
// MoneyManagement = data.MoneyManagement; => loaded from database
|
||||
Timeframe = data.Timeframe;
|
||||
Ticker = data.Ticker;
|
||||
ScenarioName = data.ScenarioName;
|
||||
AccountName = data.AccountName;
|
||||
IsForWatchingOnly = data.IsForWatchingOnly;
|
||||
BotTradingBalance = data.BotTradingBalance;
|
||||
PreloadSince = data.StartupTime;
|
||||
Identifier = backup.Identifier;
|
||||
User = backup.User;
|
||||
|
||||
// Restore the startup time if it was previously saved
|
||||
if (data.StartupTime != DateTime.MinValue)
|
||||
{
|
||||
StartupTime = data.StartupTime;
|
||||
}
|
||||
Status = backup.LastStatus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -949,7 +1037,8 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
|
||||
// Create a fake signal for manual position opening
|
||||
var signal = new Signal(Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date, TradingExchanges.GmxV2,
|
||||
var signal = new Signal(Config.Ticker, direction, Confidence.Low, lastCandle, lastCandle.Date,
|
||||
TradingExchanges.GmxV2,
|
||||
StrategyType.Stc, SignalType.Signal);
|
||||
signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
|
||||
signal.User = Account.User; // Assign user
|
||||
@@ -989,4 +1078,5 @@ public class TradingBotBackup
|
||||
public MoneyManagement MoneyManagement { get; set; }
|
||||
public DateTime StartupTime { get; set; }
|
||||
public decimal BotTradingBalance { get; set; }
|
||||
public decimal CooldownPeriod { get; set; }
|
||||
}
|
||||
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.Services;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Domain.Workflows;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot
|
||||
{
|
||||
@@ -22,13 +22,14 @@ namespace Managing.Application.ManageBot
|
||||
private readonly ILogger<TradingBot> _tradingBotLogger;
|
||||
private readonly ITradingService _tradingService;
|
||||
private readonly IMoneyManagementService _moneyManagementService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
private ConcurrentDictionary<string, BotTaskWrapper> _botTasks =
|
||||
new ConcurrentDictionary<string, BotTaskWrapper>();
|
||||
|
||||
public BotService(IBotRepository botRepository, IExchangeService exchangeService,
|
||||
IMessengerService messengerService, IAccountService accountService, ILogger<TradingBot> tradingBotLogger,
|
||||
ITradingService tradingService, IMoneyManagementService moneyManagementService)
|
||||
ITradingService tradingService, IMoneyManagementService moneyManagementService, IUserService userService)
|
||||
{
|
||||
_botRepository = botRepository;
|
||||
_exchangeService = exchangeService;
|
||||
@@ -37,11 +38,7 @@ namespace Managing.Application.ManageBot
|
||||
_tradingBotLogger = tradingBotLogger;
|
||||
_tradingService = tradingService;
|
||||
_moneyManagementService = moneyManagementService;
|
||||
}
|
||||
|
||||
public async void SaveOrUpdateBotBackup(BotBackup botBackup)
|
||||
{
|
||||
await _botRepository.InsertBotAsync(botBackup);
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public BotBackup GetBotBackup(string identifier)
|
||||
@@ -49,12 +46,13 @@ namespace Managing.Application.ManageBot
|
||||
return _botRepository.GetBots().FirstOrDefault(b => b.Identifier == identifier);
|
||||
}
|
||||
|
||||
public void SaveOrUpdateBotBackup(User user, string identifier, Enums.BotType botType, string data)
|
||||
public void SaveOrUpdateBotBackup(User user, string identifier, BotType botType, BotStatus status, string data)
|
||||
{
|
||||
var backup = GetBotBackup(identifier);
|
||||
|
||||
if (backup != null)
|
||||
{
|
||||
backup.LastStatus = status;
|
||||
backup.Data = data;
|
||||
_botRepository.UpdateBackupBot(backup);
|
||||
}
|
||||
@@ -62,6 +60,7 @@ namespace Managing.Application.ManageBot
|
||||
{
|
||||
var botBackup = new BotBackup
|
||||
{
|
||||
LastStatus = status,
|
||||
User = user,
|
||||
Identifier = identifier,
|
||||
BotType = botType,
|
||||
@@ -127,7 +126,7 @@ namespace Managing.Application.ManageBot
|
||||
// null); // Assuming null is an acceptable parameter for workflow
|
||||
// botTask = Task.Run(() => ((IBot)bot).Start());
|
||||
// break;
|
||||
case Enums.BotType.ScalpingBot:
|
||||
case BotType.ScalpingBot:
|
||||
var scalpingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
||||
var scalpingMoneyManagement =
|
||||
_moneyManagementService.GetMoneyMangement(scalpingBotData.MoneyManagement.Name).Result;
|
||||
@@ -142,7 +141,7 @@ namespace Managing.Application.ManageBot
|
||||
scalpingBotData.BotTradingBalance);
|
||||
botTask = Task.Run(() => InitBot((ITradingBot)bot, backupBot));
|
||||
break;
|
||||
case Enums.BotType.FlippingBot:
|
||||
case BotType.FlippingBot:
|
||||
var flippingBotData = JsonConvert.DeserializeObject<TradingBotBackup>(backupBot.Data);
|
||||
var flippingMoneyManagement =
|
||||
_moneyManagementService.GetMoneyMangement(flippingBotData.MoneyManagement.Name).Result;
|
||||
@@ -166,9 +165,12 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
}
|
||||
|
||||
private static Action InitBot(ITradingBot bot, BotBackup backupBot)
|
||||
private Action InitBot(ITradingBot bot, BotBackup backupBot)
|
||||
{
|
||||
bot.Start();
|
||||
|
||||
var user = _userService.GetUser(backupBot.User.Name);
|
||||
backupBot.User = user;
|
||||
bot.LoadBackup(backupBot);
|
||||
return () => { };
|
||||
}
|
||||
@@ -178,9 +180,9 @@ namespace Managing.Application.ManageBot
|
||||
return new SimpleBot(botName, _tradingBotLogger, workflow, this);
|
||||
}
|
||||
|
||||
public async Task<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)
|
||||
{
|
||||
@@ -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
|
||||
{
|
||||
@@ -205,7 +207,7 @@ namespace Managing.Application.ManageBot
|
||||
bot.Stop()); // Assuming Stop is an asynchronous process wrapped in Task.Run for synchronous methods
|
||||
}
|
||||
|
||||
await _botRepository.DeleteBotBackup(botName);
|
||||
await _botRepository.DeleteBotBackup(identifier);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -218,9 +220,9 @@ namespace Managing.Application.ManageBot
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<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)
|
||||
{
|
||||
@@ -229,17 +231,17 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(Enums.BotStatus.Down.ToString());
|
||||
return Task.FromResult(BotStatus.Down.ToString());
|
||||
}
|
||||
|
||||
public void DeleteBotBackup(string backupBotName)
|
||||
public void DeleteBotBackup(string identifier)
|
||||
{
|
||||
_botRepository.DeleteBotBackup(backupBotName);
|
||||
_botRepository.DeleteBotBackup(identifier);
|
||||
}
|
||||
|
||||
public void ToggleIsForWatchingOnly(string botName)
|
||||
public void ToggleIsForWatchingOnly(string identifier)
|
||||
{
|
||||
if (_botTasks.TryGetValue(botName, out var botTaskWrapper) &&
|
||||
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
||||
botTaskWrapper.BotInstance is ITradingBot tradingBot)
|
||||
{
|
||||
tradingBot.ToggleIsForWatchOnly().Wait();
|
||||
@@ -247,89 +249,159 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
|
||||
public ITradingBot CreateScalpingBot(string accountName, MoneyManagement moneyManagement, string name,
|
||||
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
|
||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
||||
decimal initialTradingBalance)
|
||||
{
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = accountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario,
|
||||
Timeframe = interval,
|
||||
IsForWatchingOnly = isForWatchingOnly,
|
||||
BotTradingBalance = initialTradingBalance,
|
||||
BotType = BotType.ScalpingBot
|
||||
};
|
||||
|
||||
return new ScalpingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
initialTradingBalance,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateBacktestScalpingBot(string accountName, MoneyManagement moneyManagement,
|
||||
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
|
||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
||||
decimal initialTradingBalance)
|
||||
{
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = accountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario,
|
||||
Timeframe = interval,
|
||||
IsForWatchingOnly = isForWatchingOnly,
|
||||
BotTradingBalance = initialTradingBalance,
|
||||
BotType = BotType.ScalpingBot,
|
||||
IsForBacktest = true
|
||||
};
|
||||
|
||||
return new ScalpingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
"BacktestBot",
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
initialTradingBalance,
|
||||
isForBacktest: true,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateFlippingBot(string accountName, MoneyManagement moneyManagement, string name,
|
||||
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
|
||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
||||
decimal initialTradingBalance)
|
||||
{
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = accountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario,
|
||||
Timeframe = interval,
|
||||
IsForWatchingOnly = isForWatchingOnly,
|
||||
BotTradingBalance = initialTradingBalance,
|
||||
BotType = BotType.FlippingBot
|
||||
};
|
||||
|
||||
return new FlippingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
name,
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
initialTradingBalance,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateBacktestFlippingBot(string accountName, MoneyManagement moneyManagement,
|
||||
Enums.Ticker ticker, string scenario, Enums.Timeframe interval, bool isForWatchingOnly,
|
||||
Ticker ticker, string scenario, Timeframe interval, bool isForWatchingOnly,
|
||||
decimal initialTradingBalance)
|
||||
{
|
||||
var config = new TradingBotConfig
|
||||
{
|
||||
AccountName = accountName,
|
||||
MoneyManagement = moneyManagement,
|
||||
Ticker = ticker,
|
||||
ScenarioName = scenario,
|
||||
Timeframe = interval,
|
||||
IsForWatchingOnly = isForWatchingOnly,
|
||||
BotTradingBalance = initialTradingBalance,
|
||||
BotType = BotType.FlippingBot,
|
||||
IsForBacktest = true
|
||||
};
|
||||
|
||||
return new FlippingBot(
|
||||
accountName,
|
||||
moneyManagement,
|
||||
"BacktestBot",
|
||||
scenario,
|
||||
_exchangeService,
|
||||
ticker,
|
||||
_tradingService,
|
||||
_tradingBotLogger,
|
||||
interval,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
initialTradingBalance,
|
||||
isForBacktest: true,
|
||||
isForWatchingOnly: isForWatchingOnly);
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
return new ScalpingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
config.IsForBacktest = true;
|
||||
return new ScalpingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
return new FlippingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
config);
|
||||
}
|
||||
|
||||
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
config.IsForBacktest = true;
|
||||
return new FlippingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
this,
|
||||
config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,20 @@
|
||||
using Managing.Domain.Users;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Domain.Users;
|
||||
using MediatR;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Application.ManageBot.Commands
|
||||
{
|
||||
public class StartBotCommand : IRequest<string>
|
||||
{
|
||||
public string Name { get; }
|
||||
public BotType BotType { get; }
|
||||
public Ticker Ticker { get; internal set; }
|
||||
public Timeframe Timeframe { get; internal set; }
|
||||
public bool IsForWatchingOnly { get; internal set; }
|
||||
public string Scenario { get; internal set; }
|
||||
public string AccountName { get; internal set; }
|
||||
public string MoneyManagementName { get; internal set; }
|
||||
public User User { get; internal set; }
|
||||
public decimal InitialTradingBalance { get; internal set; }
|
||||
public TradingBotConfig Config { get; }
|
||||
public User User { get; }
|
||||
|
||||
public StartBotCommand(BotType botType,
|
||||
string name,
|
||||
Ticker ticker,
|
||||
string scenario,
|
||||
Timeframe timeframe,
|
||||
string accountName,
|
||||
string moneyManagementName,
|
||||
User user,
|
||||
bool isForWatchingOnly = false,
|
||||
decimal initialTradingBalance = 0)
|
||||
public StartBotCommand(TradingBotConfig config, string name, User user)
|
||||
{
|
||||
BotType = botType;
|
||||
Config = config;
|
||||
Name = name;
|
||||
Scenario = scenario;
|
||||
Ticker = ticker;
|
||||
Timeframe = timeframe;
|
||||
IsForWatchingOnly = isForWatchingOnly;
|
||||
AccountName = accountName;
|
||||
MoneyManagementName = moneyManagementName;
|
||||
User = user;
|
||||
InitialTradingBalance = initialTradingBalance > 0 ? initialTradingBalance :
|
||||
throw new ArgumentException("Initial trading balance must be greater than zero", nameof(initialTradingBalance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot
|
||||
{
|
||||
var allActiveBots = _botService.GetActiveBots();
|
||||
var userBots = allActiveBots
|
||||
.Where(bot => bot.User != null && bot.User.Name == request.UserName)
|
||||
.Where(bot => bot.User != null && bot.User.AgentName == request.UserName)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(userBots);
|
||||
|
||||
@@ -19,15 +19,14 @@ namespace Managing.Application.ManageBot
|
||||
public Task<ITradingBot> Handle(GetUserStrategyCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var allActiveBots = _botService.GetActiveBots();
|
||||
|
||||
|
||||
// Find the specific strategy that matches both user and strategy name
|
||||
var strategy = allActiveBots
|
||||
.FirstOrDefault(bot =>
|
||||
bot.User != null &&
|
||||
bot.User.Name == request.AgentName &&
|
||||
bot.Name == request.StrategyName);
|
||||
.FirstOrDefault(bot =>
|
||||
bot.User.AgentName == request.AgentName &&
|
||||
bot.Identifier == request.StrategyName);
|
||||
|
||||
return Task.FromResult(strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,13 @@ public class LoadBackupBotCommandHandler : IRequestHandler<LoadBackupBotCommand,
|
||||
{
|
||||
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);
|
||||
|
||||
if (activeBot == null)
|
||||
|
||||
@@ -29,38 +29,38 @@ namespace Managing.Application.ManageBot
|
||||
{
|
||||
BotStatus botStatus = BotStatus.Down;
|
||||
|
||||
var account = await _accountService.GetAccount(request.AccountName, true, true);
|
||||
var account = await _accountService.GetAccount(request.Config.AccountName, true, true);
|
||||
|
||||
if (account == null)
|
||||
{
|
||||
throw new Exception($"Account {request.AccountName} not found");
|
||||
throw new Exception($"Account {request.Config.AccountName} not found");
|
||||
}
|
||||
|
||||
var usdcBalance = account.Balances.FirstOrDefault(b => b.TokenName == Ticker.USDC.ToString());
|
||||
|
||||
if (usdcBalance == null || usdcBalance.Value < request.InitialTradingBalance)
|
||||
if (usdcBalance == null || usdcBalance.Value < request.Config.BotTradingBalance)
|
||||
{
|
||||
throw new Exception($"Account {request.AccountName} has no USDC balance or not enough balance");
|
||||
throw new Exception($"Account {request.Config.AccountName} has no USDC balance or not enough balance");
|
||||
}
|
||||
|
||||
var moneyManagement =
|
||||
await _moneyManagementService.GetMoneyMangement(request.User, request.MoneyManagementName);
|
||||
switch (request.BotType)
|
||||
// Ensure cooldown period is set
|
||||
if (request.Config.CooldownPeriod <= 0)
|
||||
{
|
||||
request.Config.CooldownPeriod = 1; // Default to 1 minute if not set
|
||||
}
|
||||
|
||||
switch (request.Config.BotType)
|
||||
{
|
||||
case BotType.SimpleBot:
|
||||
var bot = _botFactory.CreateSimpleBot(request.Name, null);
|
||||
_botService.AddSimpleBotToCache(bot);
|
||||
return bot.GetStatus();
|
||||
case BotType.ScalpingBot:
|
||||
var sBot = _botFactory.CreateScalpingBot(request.AccountName, moneyManagement, request.Name,
|
||||
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly,
|
||||
request.InitialTradingBalance);
|
||||
var sBot = _botFactory.CreateScalpingBot(request.Config);
|
||||
_botService.AddTradingBotToCache(sBot);
|
||||
return sBot.GetStatus();
|
||||
case BotType.FlippingBot:
|
||||
var fBot = _botFactory.CreateFlippingBot(request.AccountName, moneyManagement, request.Name,
|
||||
request.Ticker, request.Scenario, request.Timeframe, request.IsForWatchingOnly,
|
||||
request.InitialTradingBalance);
|
||||
var fBot = _botFactory.CreateFlippingBot(request.Config);
|
||||
_botService.AddTradingBotToCache(fBot);
|
||||
return fBot.GetStatus();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Managing.Application.ManageBot
|
||||
{
|
||||
_botService.ToggleIsForWatchingOnly(request.Name);
|
||||
var bot = _botService.GetActiveBots().FirstOrDefault(b => b.Name == request.Name);
|
||||
return Task.FromResult(bot?.IsForWatchingOnly.ToString());
|
||||
return Task.FromResult(bot?.Config.IsForWatchingOnly.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class UserService : IUserService
|
||||
"0x23AA99254cfaA2c374bE2bA5B55C68018cCdFCb3", // Local optiflex
|
||||
"0x932167388dD9aad41149b3cA23eBD489E2E2DD78", // Embedded wallet
|
||||
"0x66CB57Fe3f53cE57376421106dFDa2D39186cBd0", // Embedded wallet optiflex
|
||||
"0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain
|
||||
"0x7baBf95621f22bEf2DB67E500D022Ca110722FaD", // DevCowchain
|
||||
"0xc8bC497534d0A43bAb2BBA9BA94d46D9Ddfaea6B" // DevCowchain2
|
||||
];
|
||||
|
||||
@@ -47,7 +47,8 @@ public class UserService : IUserService
|
||||
if (!message.Equals("KaigenTeamXCowchain"))
|
||||
{
|
||||
_logger.LogWarning($"Message {message} not starting with KaigenTeamXCowchain");
|
||||
throw new Exception($"Message not good : {message} - Address : {address} - User : {name} - Signature : {signature}");
|
||||
throw new Exception(
|
||||
$"Message not good : {message} - Address : {address} - User : {name} - Signature : {signature}");
|
||||
}
|
||||
|
||||
// if (!authorizedAddresses.Contains(recoveredAddress))
|
||||
@@ -106,11 +107,19 @@ public class UserService : IUserService
|
||||
{
|
||||
account
|
||||
};
|
||||
|
||||
// Update user with the new account
|
||||
await _userRepository.UpdateUser(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
public User GetUser(string name)
|
||||
{
|
||||
return _userRepository.GetUserByNameAsync(name).Result;
|
||||
}
|
||||
|
||||
public async Task<User> GetUserByAddressAsync(string address)
|
||||
{
|
||||
var account = await _accountService.GetAccountByKey(address, true, false);
|
||||
@@ -118,4 +127,18 @@ public class UserService : IUserService
|
||||
user.Accounts = _accountService.GetAccountsByUser(user).ToList();
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<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<IWorkerRepository, WorkerRepository>();
|
||||
|
||||
|
||||
// Cache
|
||||
services.AddDistributedMemoryCache();
|
||||
services.AddTransient<ICacheService, CacheService>();
|
||||
@@ -135,7 +136,7 @@ public static class ApiBootstrap
|
||||
services.AddSingleton<IBotService, BotService>();
|
||||
services.AddSingleton<IWorkerService, WorkerService>();
|
||||
services.AddTransient<IPrivyService, PrivyService>();
|
||||
|
||||
|
||||
// Web3Proxy Configuration
|
||||
services.Configure<Web3ProxySettings>(configuration.GetSection("Web3Proxy"));
|
||||
services.AddTransient<IWeb3ProxyService, Web3ProxyService>();
|
||||
|
||||
@@ -14,6 +14,7 @@ using Managing.Application.Scenarios;
|
||||
using Managing.Application.Shared;
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Users;
|
||||
using Managing.Application.Workers;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Domain.Trades;
|
||||
@@ -52,6 +53,7 @@ public static class WorkersBootstrap
|
||||
|
||||
private static IServiceCollection AddApplication(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IUserService, UserService>();
|
||||
services.AddSingleton<ITradingService, TradingService>();
|
||||
services.AddSingleton<IBotFactory, BotFactory>();
|
||||
services.AddSingleton<IScenarioService, ScenarioService>();
|
||||
@@ -102,6 +104,7 @@ public static class WorkersBootstrap
|
||||
services.AddTransient<ITradingRepository, TradingRepository>();
|
||||
services.AddTransient<IBacktestRepository, BacktestRepository>();
|
||||
services.AddTransient<IBotRepository, BotRepository>();
|
||||
services.AddTransient<IUserRepository, UserRepository>();
|
||||
|
||||
// Cache
|
||||
services.AddDistributedMemoryCache();
|
||||
|
||||
@@ -9,4 +9,5 @@ public class BotBackup
|
||||
public string Identifier { get; set; }
|
||||
public User User { get; set; }
|
||||
public string Data { get; set; }
|
||||
public BotStatus LastStatus { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
namespace Managing.Domain.Bots
|
||||
using Managing.Domain.Users;
|
||||
|
||||
namespace Managing.Domain.Bots
|
||||
{
|
||||
public interface IBot
|
||||
{
|
||||
User User { get; set; }
|
||||
string Name { get; set; }
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
@@ -6,4 +6,5 @@ public class User
|
||||
{
|
||||
public string Name { 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 string Identifier { 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 string Name { get; set; }
|
||||
public string AgentName { get; set; }
|
||||
}
|
||||
@@ -516,6 +516,7 @@ public static class MongoMappers
|
||||
return new User
|
||||
{
|
||||
Name = user.Name,
|
||||
AgentName = user.AgentName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -523,7 +524,8 @@ public static class MongoMappers
|
||||
{
|
||||
return new UserDto
|
||||
{
|
||||
Name = user.Name
|
||||
Name = user.Name,
|
||||
AgentName = user.AgentName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -724,6 +726,7 @@ public static class MongoMappers
|
||||
Identifier = bot.Identifier,
|
||||
BotType = bot.BotType,
|
||||
Data = bot.Data,
|
||||
LastStatus = bot.LastStatus
|
||||
};
|
||||
}
|
||||
|
||||
@@ -736,7 +739,8 @@ public static class MongoMappers
|
||||
User = Map(b.User),
|
||||
Identifier = b.Identifier,
|
||||
BotType = b.BotType,
|
||||
Data = b.Data
|
||||
Data = b.Data,
|
||||
LastStatus = b.LastStatus
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ public class UserRepository : IUserRepository
|
||||
_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)
|
||||
{
|
||||
var user = await _userRepository.FindOneAsync(u => u.Name == name);
|
||||
@@ -31,7 +37,7 @@ public class UserRepository : IUserRepository
|
||||
try
|
||||
{
|
||||
var dto = await _userRepository.FindOneAsync(u => u.Name == user.Name);
|
||||
dto.Name = user.Name;
|
||||
dto.AgentName = user.AgentName;
|
||||
_userRepository.Update(dto);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
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 {
|
||||
showModal: boolean
|
||||
botName: string | null
|
||||
agentName: string | null
|
||||
strategyName: string | null
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const TradesModal: React.FC<TradesModalProps> = ({
|
||||
showModal,
|
||||
botName,
|
||||
strategyName,
|
||||
agentName,
|
||||
onClose,
|
||||
}) => {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
@@ -22,19 +24,19 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
||||
const [closingPosition, setClosingPosition] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (showModal && botName) {
|
||||
if (showModal && strategyName && agentName) {
|
||||
fetchStrategyData()
|
||||
}
|
||||
}, [showModal, botName])
|
||||
}, [showModal, strategyName, agentName])
|
||||
|
||||
const fetchStrategyData = async () => {
|
||||
if (!botName) return
|
||||
if (!strategyName || !agentName) return
|
||||
|
||||
setLoading(true)
|
||||
const client = new DataClient({}, apiUrl)
|
||||
|
||||
try {
|
||||
const data = await client.data_GetUserStrategy("Oda", botName)
|
||||
const data = await client.data_GetUserStrategy(agentName, strategyName)
|
||||
setStrategyData(data)
|
||||
} catch (error) {
|
||||
console.error('Error fetching strategy data:', error)
|
||||
@@ -46,7 +48,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
||||
}
|
||||
|
||||
const closePosition = async (position: Position) => {
|
||||
if (!botName) return
|
||||
if (!agentName) return
|
||||
|
||||
try {
|
||||
setClosingPosition(position.identifier)
|
||||
@@ -56,7 +58,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
||||
// Use BotClient instead of fetch
|
||||
const botClient = new BotClient({}, apiUrl)
|
||||
const request: ClosePositionRequest = {
|
||||
botName: botName,
|
||||
identifier: agentName,
|
||||
positionId: position.identifier
|
||||
}
|
||||
|
||||
@@ -78,7 +80,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
||||
<Modal
|
||||
showModal={showModal}
|
||||
onClose={onClose}
|
||||
titleHeader={`Trades for ${botName}`}
|
||||
titleHeader={`Trades for ${strategyName}`}
|
||||
>
|
||||
<div className="mt-4">
|
||||
{loading ? (
|
||||
|
||||
@@ -42,7 +42,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
const { register, handleSubmit, setValue } = useForm<IBacktestsFormInput>({
|
||||
defaultValues: {
|
||||
startDate: defaultStartDateString,
|
||||
endDate: defaultEndDateString
|
||||
endDate: defaultEndDateString,
|
||||
cooldownPeriod: 1, // Default cooldown period of 1 minute
|
||||
maxLossStreak: 0 // Default max loss streak of 0 (no limit)
|
||||
}
|
||||
});
|
||||
const [selectedAccount, setSelectedAccount] = useState<string>('')
|
||||
@@ -110,6 +112,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
new Date(form.startDate), // startDate
|
||||
new Date(form.endDate), // endDate
|
||||
form.save,
|
||||
form.cooldownPeriod, // Use the cooldown period from the form
|
||||
form.maxLossStreak, // Add the max loss streak parameter
|
||||
customMoneyManagement
|
||||
)
|
||||
.then((backtest: Backtest) => {
|
||||
@@ -362,6 +366,28 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
/>
|
||||
</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">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -337,7 +337,7 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
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?";
|
||||
if (accountName !== undefined && accountName !== null)
|
||||
url_ += "accountName=" + encodeURIComponent("" + accountName) + "&";
|
||||
@@ -377,6 +377,14 @@ export class BacktestClient extends AuthorizedApiBase {
|
||||
throw new Error("The parameter 'save' cannot be null.");
|
||||
else if (save !== undefined)
|
||||
url_ += "save=" + encodeURIComponent("" + save) + "&";
|
||||
if (cooldownPeriod === null)
|
||||
throw new Error("The parameter 'cooldownPeriod' cannot be null.");
|
||||
else if (cooldownPeriod !== undefined)
|
||||
url_ += "cooldownPeriod=" + encodeURIComponent("" + cooldownPeriod) + "&";
|
||||
if (maxLossStreak === null)
|
||||
throw new Error("The parameter 'maxLossStreak' cannot be null.");
|
||||
else if (maxLossStreak !== undefined)
|
||||
url_ += "maxLossStreak=" + encodeURIComponent("" + maxLossStreak) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(moneyManagement);
|
||||
@@ -465,14 +473,14 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<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?";
|
||||
if (botType === null)
|
||||
throw new Error("The parameter 'botType' cannot be null.");
|
||||
else if (botType !== undefined)
|
||||
url_ += "botType=" + encodeURIComponent("" + botType) + "&";
|
||||
if (botName !== undefined && botName !== null)
|
||||
url_ += "botName=" + encodeURIComponent("" + botName) + "&";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
@@ -506,10 +514,10 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<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?";
|
||||
if (botName !== undefined && botName !== null)
|
||||
url_ += "botName=" + encodeURIComponent("" + botName) + "&";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
@@ -578,14 +586,14 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<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?";
|
||||
if (botType === null)
|
||||
throw new Error("The parameter 'botType' cannot be null.");
|
||||
else if (botType !== undefined)
|
||||
url_ += "botType=" + encodeURIComponent("" + botType) + "&";
|
||||
if (botName !== undefined && botName !== null)
|
||||
url_ += "botName=" + encodeURIComponent("" + botName) + "&";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
@@ -654,10 +662,10 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<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?";
|
||||
if (botName !== undefined && botName !== null)
|
||||
url_ += "botName=" + encodeURIComponent("" + botName) + "&";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
@@ -816,7 +824,7 @@ export class DataClient extends AuthorizedApiBase {
|
||||
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||
}
|
||||
|
||||
data_GetTickers(timeframe: Timeframe | undefined): Promise<Ticker[]> {
|
||||
data_GetTickers(timeframe: Timeframe | undefined): Promise<TickerInfos[]> {
|
||||
let url_ = this.baseUrl + "/Data/GetTickers?";
|
||||
if (timeframe === null)
|
||||
throw new Error("The parameter 'timeframe' cannot be null.");
|
||||
@@ -838,13 +846,13 @@ export class DataClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processData_GetTickers(response: Response): Promise<Ticker[]> {
|
||||
protected processData_GetTickers(response: Response): Promise<TickerInfos[]> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Ticker[];
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as TickerInfos[];
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -852,7 +860,7 @@ export class DataClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<Ticker[]>(null as any);
|
||||
return Promise.resolve<TickerInfos[]>(null as any);
|
||||
}
|
||||
|
||||
data_GetSpotlight(): Promise<SpotlightOverview> {
|
||||
@@ -2202,6 +2210,80 @@ export class UserClient extends AuthorizedApiBase {
|
||||
}
|
||||
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 {
|
||||
@@ -2397,6 +2479,7 @@ export enum AccountType {
|
||||
export interface User {
|
||||
name?: string | null;
|
||||
accounts?: Account[] | null;
|
||||
agentName?: string | null;
|
||||
}
|
||||
|
||||
export interface Balance {
|
||||
@@ -2839,15 +2922,15 @@ export interface SuperTrendResult extends ResultBase {
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
botType: BotType;
|
||||
botName: string;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchOnly: boolean;
|
||||
scenario: string;
|
||||
accountName: string;
|
||||
moneyManagementName: string;
|
||||
initialTradingBalance: number;
|
||||
botType?: BotType;
|
||||
identifier?: string | null;
|
||||
ticker?: Ticker;
|
||||
scenario?: string | null;
|
||||
timeframe?: Timeframe;
|
||||
accountName?: string | null;
|
||||
moneyManagementName?: string | null;
|
||||
isForWatchOnly?: boolean;
|
||||
initialTradingBalance?: number;
|
||||
}
|
||||
|
||||
export interface TradingBot {
|
||||
@@ -2865,18 +2948,25 @@ export interface TradingBot {
|
||||
botType: BotType;
|
||||
accountName: string;
|
||||
moneyManagement: MoneyManagement;
|
||||
identifier: string;
|
||||
agentName: string;
|
||||
}
|
||||
|
||||
export interface OpenPositionManuallyRequest {
|
||||
botName?: string | null;
|
||||
identifier?: string | null;
|
||||
direction?: TradeDirection;
|
||||
}
|
||||
|
||||
export interface ClosePositionRequest {
|
||||
botName?: string | null;
|
||||
identifier?: string | null;
|
||||
positionId?: string | null;
|
||||
}
|
||||
|
||||
export interface TickerInfos {
|
||||
ticker?: Ticker;
|
||||
imageUrl?: string | null;
|
||||
}
|
||||
|
||||
export interface SpotlightOverview {
|
||||
spotlights: Spotlight[];
|
||||
dateTime: Date;
|
||||
@@ -2962,7 +3052,7 @@ export interface PlatformSummaryViewModel {
|
||||
}
|
||||
|
||||
export interface AgentSummaryViewModel {
|
||||
username?: string | null;
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
pnLLast24h?: number;
|
||||
totalROI?: number;
|
||||
|
||||
@@ -111,6 +111,8 @@ export type IBacktestsFormInput = {
|
||||
loop: number
|
||||
startDate: string
|
||||
endDate: string
|
||||
cooldownPeriod: number
|
||||
maxLossStreak: number
|
||||
}
|
||||
|
||||
export type IBacktestCards = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, {useState} from 'react'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import { Tabs } from '../../components/mollecules'
|
||||
import type { TabsType } from '../../global/type'
|
||||
import {Tabs} from '../../components/mollecules'
|
||||
import type {TabsType} from '../../global/type'
|
||||
|
||||
import BacktestLoop from './backtestLoop'
|
||||
import BacktestPlayground from './backtestPlayground'
|
||||
@@ -12,14 +12,14 @@ import BacktestUpload from './backtestUpload'
|
||||
// Tabs Array
|
||||
const tabs: TabsType = [
|
||||
{
|
||||
Component: BacktestScanner,
|
||||
Component: BacktestPlayground,
|
||||
index: 1,
|
||||
label: 'Scanner',
|
||||
label: 'Playground',
|
||||
},
|
||||
{
|
||||
Component: BacktestPlayground,
|
||||
Component: BacktestScanner,
|
||||
index: 2,
|
||||
label: 'Playground',
|
||||
label: 'Scanner',
|
||||
},
|
||||
{
|
||||
Component: BacktestLoop,
|
||||
|
||||
@@ -37,13 +37,13 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
const [showManualPositionModal, setShowManualPositionModal] = useState(false)
|
||||
const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState<string | null>(null)
|
||||
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 =
|
||||
baseBadgeClass() + (isForWatchingOnly ? ' bg-accent' : ' bg-primary')
|
||||
return (
|
||||
<button className={classes} onClick={() => toggleIsForWatchingOnly(name)}>
|
||||
<button className={classes} onClick={() => toggleIsForWatchingOnly(identifier)}>
|
||||
{isForWatchingOnly ? (
|
||||
<p className="text-accent-content flex">
|
||||
<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')
|
||||
client.bot_ToggleIsForWatching(name).then(() => {
|
||||
client.bot_ToggleIsForWatching(identifier).then(() => {
|
||||
t.update('success', 'Bot updated')
|
||||
})
|
||||
}
|
||||
function getDeleteBadge(name: string) {
|
||||
function getDeleteBadge(identifier: string) {
|
||||
const classes = baseBadgeClass() + 'bg-error'
|
||||
return (
|
||||
<button className={classes} onClick={() => deleteBot(name)}>
|
||||
<button className={classes} onClick={() => deleteBot(identifier)}>
|
||||
<p className="text-primary-content flex">
|
||||
<TrashIcon width={15}></TrashIcon>
|
||||
</p>
|
||||
@@ -77,7 +77,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
}
|
||||
function getToggleBotStatusBadge(
|
||||
status: string,
|
||||
name: string,
|
||||
identifier: string,
|
||||
botType: BotType
|
||||
) {
|
||||
const classes =
|
||||
@@ -85,7 +85,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
return (
|
||||
<button
|
||||
className={classes}
|
||||
onClick={() => toggleBotStatus(status, name, botType)}
|
||||
onClick={() => toggleBotStatus(status, identifier, botType)}
|
||||
>
|
||||
{status == 'Up' ? (
|
||||
<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'
|
||||
return (
|
||||
<button className={classes} onClick={() => openManualPositionModal(botName)}>
|
||||
<button className={classes} onClick={() => openManualPositionModal(botIdentifier)}>
|
||||
<p className="text-primary-content flex">
|
||||
<PlusCircleIcon width={15}></PlusCircleIcon>
|
||||
</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'
|
||||
return (
|
||||
<button className={classes} onClick={() => openTradesModal(botName)}>
|
||||
<button className={classes} onClick={() => openTradesModal(botIdentifier, agentName)}>
|
||||
<p className="text-primary-content flex">
|
||||
<ChartBarIcon width={15}></ChartBarIcon>
|
||||
</p>
|
||||
@@ -140,23 +140,23 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
)
|
||||
}
|
||||
|
||||
function openManualPositionModal(botName: string) {
|
||||
setSelectedBotForManualPosition(botName)
|
||||
function openManualPositionModal(botIdentifier: string) {
|
||||
setSelectedBotForManualPosition(botIdentifier)
|
||||
setShowManualPositionModal(true)
|
||||
}
|
||||
|
||||
function openTradesModal(botName: string) {
|
||||
setSelectedBotForTrades(botName)
|
||||
function openTradesModal(botIdentifier: string, agentName: string) {
|
||||
setSelectedBotForTrades({ identifier: botIdentifier, agentName })
|
||||
setShowTradesModal(true)
|
||||
}
|
||||
|
||||
function toggleBotStatus(status: string, name: string, botType: BotType) {
|
||||
function toggleBotStatus(status: string, identifier: string, botType: BotType) {
|
||||
const isUp = status == 'Up'
|
||||
const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot')
|
||||
|
||||
if (status == 'Up') {
|
||||
client
|
||||
.bot_Stop(botType, name)
|
||||
.bot_Stop(botType, identifier)
|
||||
.then(() => {
|
||||
t.update('success', 'Bot stopped')
|
||||
})
|
||||
@@ -165,7 +165,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
})
|
||||
} else if (status == 'Down') {
|
||||
client
|
||||
.bot_Restart(botType, name)
|
||||
.bot_Restart(botType, identifier)
|
||||
.then(() => {
|
||||
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')
|
||||
|
||||
client
|
||||
.bot_Delete(name)
|
||||
.bot_Delete(identifier)
|
||||
.then(() => {
|
||||
t.update('success', 'Bot deleted')
|
||||
})
|
||||
@@ -211,10 +211,10 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
<h2 className="card-title text-sm">
|
||||
{bot.ticker}
|
||||
{getMoneyManagementBadge(bot.moneyManagement)}
|
||||
{getIsForWatchingBadge(bot.isForWatchingOnly, bot.name)}
|
||||
{getToggleBotStatusBadge(bot.status, bot.name, bot.botType)}
|
||||
{getManualPositionBadge(bot.name)}
|
||||
{getDeleteBadge(bot.name)}
|
||||
{getIsForWatchingBadge(bot.isForWatchingOnly, bot.identifier)}
|
||||
{getToggleBotStatusBadge(bot.status, bot.identifier, bot.botType)}
|
||||
{getManualPositionBadge(bot.identifier)}
|
||||
{getDeleteBadge(bot.identifier)}
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
<div className={baseBadgeClass(true)}>
|
||||
PNL {bot.profitAndLoss.toFixed(2).toString()} $
|
||||
</div>
|
||||
{getTradesBadge(bot.name)}
|
||||
{getTradesBadge(bot.identifier, bot.agentName)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -280,7 +280,8 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
/>
|
||||
<TradesModal
|
||||
showModal={showTradesModal}
|
||||
botName={selectedBotForTrades}
|
||||
strategyName={selectedBotForTrades?.identifier ?? null}
|
||||
agentName={selectedBotForTrades?.agentName ?? null}
|
||||
onClose={() => {
|
||||
setShowTradesModal(false)
|
||||
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 HealthChecks from './healthchecks/healthChecks'
|
||||
import MoneyManagementSettings from './moneymanagement/moneyManagement'
|
||||
import Theme from './theme'
|
||||
import DefaultConfig from './defaultConfig/defaultConfig'
|
||||
import UserInfoSettings from './UserInfoSettings'
|
||||
|
||||
type TabsType = {
|
||||
label: string
|
||||
@@ -17,28 +18,33 @@ type TabsType = {
|
||||
// Tabs Array
|
||||
const tabs: TabsType = [
|
||||
{
|
||||
Component: MoneyManagementSettings,
|
||||
Component: UserInfoSettings,
|
||||
index: 1,
|
||||
label: 'User Info',
|
||||
},
|
||||
{
|
||||
Component: MoneyManagementSettings,
|
||||
index: 2,
|
||||
label: 'Money Management',
|
||||
},
|
||||
{
|
||||
Component: AccountSettings,
|
||||
index: 2,
|
||||
index: 3,
|
||||
label: 'Account Settings',
|
||||
},
|
||||
{
|
||||
Component: Theme,
|
||||
index: 3,
|
||||
index: 4,
|
||||
label: 'Theme',
|
||||
},
|
||||
{
|
||||
Component: DefaultConfig,
|
||||
index: 4,
|
||||
index: 5,
|
||||
label: 'Quick Start Config',
|
||||
},
|
||||
{
|
||||
Component: HealthChecks,
|
||||
index: 5,
|
||||
index: 6,
|
||||
label: 'Health Checks',
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user