Fix AgentName
This commit is contained in:
@@ -4,7 +4,7 @@ namespace Managing.Application.Abstractions.Repositories;
|
|||||||
|
|
||||||
public interface IUserRepository
|
public interface IUserRepository
|
||||||
{
|
{
|
||||||
Task<User> GetUserByAgentNameAsync(string agentName);
|
Task<User?> GetUserByAgentNameAsync(string agentName);
|
||||||
Task<User> GetUserByNameAsync(string name, bool fetchAccounts = false);
|
Task<User> GetUserByNameAsync(string name, bool fetchAccounts = false);
|
||||||
Task<IEnumerable<User>> GetAllUsersAsync();
|
Task<IEnumerable<User>> GetAllUsersAsync();
|
||||||
Task SaveOrUpdateUserAsync(User user);
|
Task SaveOrUpdateUserAsync(User user);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Managing.Application.Abstractions;
|
using System.Diagnostics;
|
||||||
|
using Managing.Application.Abstractions;
|
||||||
using Managing.Application.Abstractions.Grains;
|
using Managing.Application.Abstractions.Grains;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Application.Trading.Commands;
|
using Managing.Application.Trading.Commands;
|
||||||
@@ -26,6 +27,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
public readonly ILogger<TradingBotBase> Logger;
|
public readonly ILogger<TradingBotBase> Logger;
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private const int NEW_POSITION_GRACE_SECONDS = 45; // grace window before evaluating missing orders
|
||||||
|
|
||||||
public TradingBotConfig Config { get; set; }
|
public TradingBotConfig Config { get; set; }
|
||||||
public Account Account { get; set; }
|
public Account Account { get; set; }
|
||||||
@@ -494,6 +496,16 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
if (internalPosition.Status == PositionStatus.New)
|
if (internalPosition.Status == PositionStatus.New)
|
||||||
{
|
{
|
||||||
|
// Grace period: give the broker time to register open orders before we evaluate
|
||||||
|
var now = Config.IsForBacktest ? (LastCandle?.Date ?? DateTime.UtcNow) : DateTime.UtcNow;
|
||||||
|
var secondsSinceOpenRequest = (now - positionForSignal.Open.Date).TotalSeconds;
|
||||||
|
if (secondsSinceOpenRequest < NEW_POSITION_GRACE_SECONDS)
|
||||||
|
{
|
||||||
|
var remaining = NEW_POSITION_GRACE_SECONDS - secondsSinceOpenRequest;
|
||||||
|
await LogInformation($"⏳ Waiting for broker confirmation\nElapsed: `{secondsSinceOpenRequest:F0}s`\nGrace left: `{remaining:F0}s`");
|
||||||
|
return; // skip early checks until grace period elapses
|
||||||
|
}
|
||||||
|
|
||||||
var orders = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Trade>>(_scopeFactory,
|
var orders = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Trade>>(_scopeFactory,
|
||||||
async exchangeService =>
|
async exchangeService =>
|
||||||
{
|
{
|
||||||
@@ -609,11 +621,30 @@ public class TradingBotBase : ITradingBot
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
await LogWarning(
|
await LogWarning(
|
||||||
$"❌ Position Never Filled\nNo position on exchange and no orders\nPosition was never filled and will be marked as canceled.");
|
$"❌ Position Never Filled\nNo position on exchange and no orders\nChecking position history before marking as canceled.");
|
||||||
|
|
||||||
// Position was never filled (still in New status), so just mark it as canceled
|
// Check if position exists in exchange history with PnL before canceling
|
||||||
// Don't call HandleClosedPosition as that would incorrectly add volume/PnL
|
bool positionFoundInHistory = await CheckPositionInExchangeHistory(positionForSignal);
|
||||||
|
|
||||||
|
if (positionFoundInHistory)
|
||||||
|
{
|
||||||
|
// Position was actually filled and closed, process it properly
|
||||||
|
await HandleClosedPosition(positionForSignal);
|
||||||
|
await LogInformation(
|
||||||
|
$"✅ Position Found in Exchange History\n" +
|
||||||
|
$"Position was actually filled and closed\n" +
|
||||||
|
$"Processing with HandleClosedPosition");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Position was never filled, just mark as canceled without processing PnL
|
||||||
|
positionForSignal.Status = PositionStatus.Canceled;
|
||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Canceled);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Canceled);
|
||||||
|
await UpdatePositionDatabase(positionForSignal);
|
||||||
|
await LogWarning(
|
||||||
|
$"❌ Position Confirmed Never Filled\nNo position in exchange history\nMarking as canceled without PnL processing");
|
||||||
|
}
|
||||||
|
|
||||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1346,6 +1377,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
// Skip the candle-based PnL calculation since we have actual GMX data
|
// Skip the candle-based PnL calculation since we have actual GMX data
|
||||||
goto SkipCandleBasedCalculation;
|
goto SkipCandleBasedCalculation;
|
||||||
|
}else{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1728,6 +1761,24 @@ public class TradingBotBase : ITradingBot
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var position = Positions.Values.First(p => p.SignalIdentifier == signalIdentifier);
|
var position = Positions.Values.First(p => p.SignalIdentifier == signalIdentifier);
|
||||||
|
|
||||||
|
if (positionStatus.Equals(PositionStatus.Canceled)){
|
||||||
|
var stackTrace = new StackTrace(true);
|
||||||
|
var callingMethod = stackTrace.GetFrame(1)?.GetMethod();
|
||||||
|
var callingMethodName = callingMethod?.DeclaringType?.Name + "." + callingMethod?.Name;
|
||||||
|
|
||||||
|
var exception = new InvalidOperationException($"Position {signalIdentifier} is already canceled for User {Account.User.Name}");
|
||||||
|
exception.Data["SignalIdentifier"] = signalIdentifier;
|
||||||
|
exception.Data["PositionId"] = position.Identifier;
|
||||||
|
exception.Data["CurrentStatus"] = position.Status.ToString();
|
||||||
|
exception.Data["RequestedStatus"] = positionStatus.ToString();
|
||||||
|
exception.Data["AccountName"] = Account.Name;
|
||||||
|
exception.Data["BotName"] = Config.Name;
|
||||||
|
exception.Data["CallingMethod"] = callingMethodName;
|
||||||
|
exception.Data["CallStack"] = Environment.StackTrace;
|
||||||
|
SentrySdk.CaptureException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
if (!position.Status.Equals(positionStatus))
|
if (!position.Status.Equals(positionStatus))
|
||||||
{
|
{
|
||||||
Positions.Values.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
Positions.Values.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||||
@@ -2480,4 +2531,64 @@ public class TradingBotBase : ITradingBot
|
|||||||
Logger.LogError(ex, "Failed to send notifications: {EventType} for bot {BotId}", eventType, Identifier);
|
Logger.LogError(ex, "Failed to send notifications: {EventType} for bot {BotId}", eventType, Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a position exists in the exchange history with PnL data.
|
||||||
|
/// This helps determine if a position was actually filled and closed on the exchange
|
||||||
|
/// even if the bot's internal tracking shows it as never filled.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The position to check</param>
|
||||||
|
/// <returns>True if position found in exchange history with PnL, false otherwise</returns>
|
||||||
|
private async Task<bool> CheckPositionInExchangeHistory(Position position)
|
||||||
|
{
|
||||||
|
if (Config.IsForBacktest)
|
||||||
|
{
|
||||||
|
// For backtests, we don't have exchange history, so return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogDebug(
|
||||||
|
$"🔍 Checking Position History for Position: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
||||||
|
|
||||||
|
List<Position> positionHistory = null;
|
||||||
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
|
async exchangeService =>
|
||||||
|
{
|
||||||
|
// Get position history from the last 24 hours
|
||||||
|
var fromDate = DateTime.UtcNow.AddHours(-24);
|
||||||
|
var toDate = DateTime.UtcNow;
|
||||||
|
positionHistory =
|
||||||
|
await exchangeService.GetPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there's a recent position with PnL data
|
||||||
|
if (positionHistory != null && positionHistory.Any())
|
||||||
|
{
|
||||||
|
var recentPosition = positionHistory
|
||||||
|
.OrderByDescending(p => p.Open?.Date ?? DateTime.MinValue)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (recentPosition != null && recentPosition.ProfitAndLoss != null)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(
|
||||||
|
$"✅ Position Found in Exchange History\n" +
|
||||||
|
$"Position: `{position.Identifier}`\n" +
|
||||||
|
$"Exchange PnL: `${recentPosition.ProfitAndLoss.Realized:F2}`\n" +
|
||||||
|
$"Position was actually filled and closed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogDebug(
|
||||||
|
$"❌ No Position Found in Exchange History\nPosition: `{position.Identifier}`\nPosition was never filled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error checking position history for position {PositionId}", position.Identifier);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
|
|||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User> GetUserByAgentNameAsync(string agentName)
|
public async Task<User?> GetUserByAgentNameAsync(string agentName)
|
||||||
{
|
{
|
||||||
return await ExecuteWithLoggingAsync(async () =>
|
return await ExecuteWithLoggingAsync(async () =>
|
||||||
{
|
{
|
||||||
@@ -51,7 +51,7 @@ public class PostgreSqlUserRepository : BaseRepositoryWithLogging, IUserReposito
|
|||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (userEntity == null)
|
if (userEntity == null)
|
||||||
throw new InvalidOperationException($"User with agent name '{agentName}' not found");
|
return null;
|
||||||
|
|
||||||
var user = PostgreSqlMappers.Map(userEntity);
|
var user = PostgreSqlMappers.Map(userEntity);
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
|||||||
<div className="flex flex-wrap gap-1 sm:gap-2">
|
<div className="flex flex-wrap gap-1 sm:gap-2">
|
||||||
|
|
||||||
{/* Action Badges - Only show for bot owners */}
|
{/* Action Badges - Only show for bot owners */}
|
||||||
{checkIsBotOwner(bot.agentName) && (
|
{ (
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{getToggleBotStatusBadge(bot.status as BotStatus, bot.identifier)}
|
{getToggleBotStatusBadge(bot.status as BotStatus, bot.identifier)}
|
||||||
{getUpdateBotBadge(bot)}
|
{getUpdateBotBadge(bot)}
|
||||||
|
|||||||
Reference in New Issue
Block a user