feat(trades): add live execution cockpit, closing cockpit, calculation cards and precision trade settings
This commit is contained in:
@@ -101,12 +101,20 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
var existingTrade = await _dbContext.Trades
|
||||
.FirstOrDefaultAsync(t =>
|
||||
(!string.IsNullOrWhiteSpace(proposal.TradeId) && t.TradeId == proposal.TradeId) ||
|
||||
(!string.IsNullOrWhiteSpace(proposal.AnalysisId) && t.AnalysisId == proposal.AnalysisId),
|
||||
(!string.IsNullOrWhiteSpace(proposal.AnalysisId) && t.AnalysisId == proposal.AnalysisId) ||
|
||||
(!string.IsNullOrWhiteSpace(proposal.Isin) && t.Isin == proposal.Isin && (t.Status == TradeStatus.Proposed || t.Status == TradeStatus.Active)),
|
||||
cancellationToken);
|
||||
|
||||
if (existingTrade != null)
|
||||
{
|
||||
if (existingTrade.Status != TradeStatus.Active && existingTrade.Status != TradeStatus.Closed)
|
||||
if (existingTrade.Status == TradeStatus.Active)
|
||||
{
|
||||
_logger.LogInformation("[{Channel}] An ACTIVE trade {TradeId} already exists for {Symbol} ({Isin}). Skipping duplicate proposed trade creation.",
|
||||
"TradesChannel", existingTrade.TradeId, proposal.Symbol, proposal.Isin);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (existingTrade.Status != TradeStatus.Closed)
|
||||
{
|
||||
existingTrade.Status = targetStatus;
|
||||
}
|
||||
@@ -115,7 +123,7 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
_dbContext.Trades.Update(existingTrade);
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation("[{Channel}] Successfully UPDATED trade proposal {TradeId} for Symbol {Symbol} (ISIN: {Isin}) with status {Status}",
|
||||
_logger.LogInformation("[{Channel}] Successfully UPDATED existing trade proposal {TradeId} for Symbol {Symbol} (ISIN: {Isin}) with status {Status}",
|
||||
"TradesChannel", existingTrade.TradeId, proposal.Symbol, proposal.Isin, existingTrade.Status);
|
||||
|
||||
return true;
|
||||
@@ -297,13 +305,10 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
}
|
||||
else
|
||||
{
|
||||
trade.Status = TradeStatus.Closed;
|
||||
trade.UserExitPrice = update.CurrentPrice;
|
||||
trade.UserExitTimestamp = DateTime.UtcNow;
|
||||
trade.CloseReason = "AiRecommendationClose";
|
||||
trade.ClosedAt = DateTime.UtcNow;
|
||||
|
||||
CalculatePnL(trade);
|
||||
// NO AUTO CLOSE for active user trades!
|
||||
// Trade remains Active, alert is stored in HourlyUpdates and surfaced in UI for manual confirmation.
|
||||
_logger.LogInformation("[{Channel}] Active trade {TradeId} received Close recommendation ({Reasoning}). Trade kept Active for user action.",
|
||||
"TradesChannel", trade.TradeId, update.Reasoning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,6 +364,10 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
trade.Status = TradeStatus.Closed;
|
||||
trade.UserExitPrice = request.UserExitPrice;
|
||||
trade.UserExitTimestamp = request.UserExitTimestamp?.ToUniversalTime() ?? DateTime.UtcNow;
|
||||
if (request.ExitFee > 0m)
|
||||
{
|
||||
trade.ExitFee = request.ExitFee;
|
||||
}
|
||||
trade.CloseReason = request.CloseReason;
|
||||
trade.ClosedAt = DateTime.UtcNow;
|
||||
|
||||
@@ -406,6 +415,9 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
entity.RiskTolerance = dto.RiskTolerance;
|
||||
entity.Timeframe = dto.Timeframe;
|
||||
entity.InstrumentType = dto.InstrumentType;
|
||||
if (!string.IsNullOrWhiteSpace(dto.AssetType)) entity.AssetType = dto.AssetType;
|
||||
entity.HasCfd = dto.HasCfd;
|
||||
if (dto.DerivativeProductCategories.Count > 0) entity.DerivativeProductCategories = dto.DerivativeProductCategories;
|
||||
if (!string.IsNullOrWhiteSpace(dto.DerivativeIsin)) entity.DerivativeIsin = dto.DerivativeIsin;
|
||||
entity.WinRate = dto.WinRate;
|
||||
entity.VixRegime = dto.VixRegime;
|
||||
|
||||
Reference in New Issue
Block a user