feat(trades): align TradeProposal and TradeAcceptance DTOs, update lifecycle and migrations
This commit is contained in:
@@ -14,45 +14,15 @@ namespace FinlyticTrades.Services;
|
||||
|
||||
public interface ITradeLifecycleService
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes a proposed trade.
|
||||
/// </summary>
|
||||
Task<bool> ProcessProposedTradeAsync(TradeProposalDto proposal, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Processes a manual analysis RPC response from FinlyticAnalyzer and ingests it if a trade was proposed.
|
||||
/// </summary>
|
||||
Task<bool> ProcessManualAnalysisResponseAsync(ManualAnalysisResponseDto response, string userId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Accepts a trade proposal and maps execution parameters.
|
||||
/// </summary>
|
||||
Task<TradeEntity?> AcceptTradeAsync(TradeAcceptanceDto request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an hourly update for a trade.
|
||||
/// </summary>
|
||||
Task AddHourlyUpdateAsync(TradeHourlyUpdateDto update, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of active trades filtered by optional UserId.
|
||||
/// </summary>
|
||||
Task<List<TradeEntity>> GetActiveTradesAsync(string? userId = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of trades filtered by ISIN, status, and optional UserId.
|
||||
/// </summary>
|
||||
Task<List<TradeEntity>> GetTradesAsync(string? isin, string? status, string? userId = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Closes a trade manually.
|
||||
/// </summary>
|
||||
Task<TradeEntity?> CloseTradeAsync(string tradeId, CloseTradeRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Rejects a trade proposal.
|
||||
/// </summary>
|
||||
Task<TradeEntity?> RejectTradeAsync(string tradeId, CloseTradeRequest request, CancellationToken cancellationToken = default);
|
||||
void CalculatePnL(TradeEntity trade, decimal? overridePrice = null);
|
||||
}
|
||||
|
||||
public class TradeLifecycleService : ITradeLifecycleService
|
||||
@@ -66,9 +36,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a manual analysis RPC response from FinlyticAnalyzer and ingests it if a trade was proposed.
|
||||
/// </summary>
|
||||
public async Task<bool> ProcessManualAnalysisResponseAsync(ManualAnalysisResponseDto response, string userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (response == null || !response.IsTradeProposed)
|
||||
@@ -119,9 +86,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a proposed trade.
|
||||
/// </summary>
|
||||
public async Task<bool> ProcessProposedTradeAsync(TradeProposalDto proposal, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(proposal.Symbol) && string.IsNullOrWhiteSpace(proposal.Isin))
|
||||
@@ -177,9 +141,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts a trade proposal and updates execution parameters.
|
||||
/// </summary>
|
||||
public async Task<TradeEntity?> AcceptTradeAsync(TradeAcceptanceDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string targetUserId = !string.IsNullOrWhiteSpace(request.UserId) ? request.UserId : "default_user";
|
||||
@@ -212,6 +173,7 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
if (request.TakeProfit > 0) existingTrade.TakeProfit = request.TakeProfit.Value;
|
||||
if (request.KnockoutThreshold > 0) existingTrade.KnockoutThreshold = request.KnockoutThreshold;
|
||||
if (!string.IsNullOrWhiteSpace(request.Timeframe)) existingTrade.Timeframe = request.Timeframe;
|
||||
if (!string.IsNullOrWhiteSpace(request.DerivativeIsin)) existingTrade.DerivativeIsin = request.DerivativeIsin;
|
||||
if (!string.IsNullOrWhiteSpace(request.Reasoning)) existingTrade.Reasoning = request.Reasoning;
|
||||
|
||||
existingTrade.ExecutionTimestamp = request.ExecutionTimestamp?.ToUniversalTime() ?? DateTime.UtcNow;
|
||||
@@ -241,10 +203,10 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
TradeId = targetTradeId,
|
||||
AnalysisId = proposal?.AnalysisId ?? (string.IsNullOrWhiteSpace(request.AnalysisId) ? Guid.NewGuid().ToString("N") : request.AnalysisId),
|
||||
EventId = proposal?.EventId ?? request.AnalysisId,
|
||||
Sector = proposal?.Sector ?? "General",
|
||||
Sector = proposal?.Sector ?? request.Sector ?? "General",
|
||||
Symbol = proposal?.Symbol ?? request.Symbol ?? request.Isin,
|
||||
Isin = proposal?.Isin ?? request.Isin,
|
||||
CompanyName = proposal?.CompanyName ?? request.Symbol ?? request.Isin,
|
||||
CompanyName = proposal?.CompanyName ?? request.CompanyName ?? request.Symbol ?? request.Isin,
|
||||
Status = TradeStatus.Active,
|
||||
IsGlobalProposal = false,
|
||||
UserId = targetUserId,
|
||||
@@ -256,6 +218,7 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
RiskTolerance = proposal?.RiskTolerance ?? "Moderate",
|
||||
Timeframe = proposal?.Timeframe ?? request.Timeframe ?? "1D",
|
||||
InstrumentType = proposal?.InstrumentType ?? request.InstrumentType ?? "Stock",
|
||||
DerivativeIsin = request.DerivativeIsin ?? proposal?.DerivativeIsin,
|
||||
WinRate = proposal?.WinRate ?? 50,
|
||||
VixRegime = proposal?.VixRegime ?? FinlyticCore.Models.Analyzer.VixMarketRegime.Normal,
|
||||
VixValue = proposal?.VixValue ?? 15,
|
||||
@@ -294,9 +257,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
return newTrade;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an hourly update for a trade.
|
||||
/// </summary>
|
||||
public async Task AddHourlyUpdateAsync(TradeHourlyUpdateDto update, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var trade = await _dbContext.Trades
|
||||
@@ -352,9 +312,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
"TradesChannel", update.TradeId, update.Recommendation, update.CurrentPrice);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of active trades filtered by optional UserId.
|
||||
/// </summary>
|
||||
public async Task<List<TradeEntity>> GetActiveTradesAsync(string? userId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _dbContext.Trades.AsNoTracking().Include(t => t.HourlyUpdates).AsQueryable();
|
||||
@@ -370,9 +327,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of trades filtered by ISIN, status, and optional UserId.
|
||||
/// </summary>
|
||||
public async Task<List<TradeEntity>> GetTradesAsync(string? isin, string? status, string? userId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _dbContext.Trades.AsNoTracking().Include(t => t.HourlyUpdates).AsQueryable();
|
||||
@@ -395,9 +349,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
return await query.OrderByDescending(t => t.CreatedAt).ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a trade manually.
|
||||
/// </summary>
|
||||
public async Task<TradeEntity?> CloseTradeAsync(string tradeId, CloseTradeRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var trade = await _dbContext.Trades
|
||||
@@ -420,9 +371,6 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
return trade;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rejects a trade proposal.
|
||||
/// </summary>
|
||||
public async Task<TradeEntity?> RejectTradeAsync(string tradeId, CloseTradeRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var trade = await _dbContext.Trades
|
||||
@@ -458,6 +406,7 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
entity.RiskTolerance = dto.RiskTolerance;
|
||||
entity.Timeframe = dto.Timeframe;
|
||||
entity.InstrumentType = dto.InstrumentType;
|
||||
if (!string.IsNullOrWhiteSpace(dto.DerivativeIsin)) entity.DerivativeIsin = dto.DerivativeIsin;
|
||||
entity.WinRate = dto.WinRate;
|
||||
entity.VixRegime = dto.VixRegime;
|
||||
entity.VixValue = dto.VixValue;
|
||||
@@ -484,11 +433,12 @@ public class TradeLifecycleService : ITradeLifecycleService
|
||||
entity.IsRecurring = dto.IsRecurring;
|
||||
}
|
||||
|
||||
private static void CalculatePnL(TradeEntity trade)
|
||||
public void CalculatePnL(TradeEntity trade, decimal? overridePrice = null)
|
||||
{
|
||||
if (!trade.UserExitPrice.HasValue) return;
|
||||
decimal? evalPrice = overridePrice ?? trade.UserExitPrice ?? trade.HourlyUpdates?.LastOrDefault()?.CurrentPrice;
|
||||
if (!evalPrice.HasValue || evalPrice.Value <= 0m) return;
|
||||
|
||||
decimal exitPrice = trade.UserExitPrice.Value;
|
||||
decimal exitPrice = evalPrice.Value;
|
||||
decimal entryPrice = trade.ActualEntryPrice.HasValue && trade.ActualEntryPrice.Value > 0m
|
||||
? trade.ActualEntryPrice.Value
|
||||
: trade.EntryPrice;
|
||||
|
||||
Reference in New Issue
Block a user