Add user to position + fix few things
This commit is contained in:
@@ -3,6 +3,7 @@ using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Workers;
|
||||
using Managing.Application.Workers.Abstractions;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Trades;
|
||||
using Newtonsoft.Json;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -58,51 +59,84 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
||||
position.Status = PositionStatus.Updating;
|
||||
_tradingService.UpdatePosition(position);
|
||||
|
||||
_logger.LogDebug("Processing risk orders for {Direction} position opened at {OpenDate}",
|
||||
_logger.LogDebug("Verifying position on exchange for {Direction} position opened at {OpenDate}",
|
||||
position.OriginDirection, position.Date.ToString("o"));
|
||||
|
||||
var account = await _accountService.GetAccount(position.AccountName, false, false);
|
||||
var success = true;
|
||||
|
||||
// Process and update trades
|
||||
var updatedSl = await ProcessTrade(account, position.StopLoss, "SL", async () =>
|
||||
await _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection,
|
||||
position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow));
|
||||
|
||||
if (updatedSl != null)
|
||||
{
|
||||
position.StopLoss = updatedSl;
|
||||
success &= updatedSl.Status.IsActive();
|
||||
}
|
||||
|
||||
var updatedTp1 = await ProcessTrade(account, position.TakeProfit1, "TP1", async () =>
|
||||
await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
||||
position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow));
|
||||
// Get positions directly from broker
|
||||
var brokerPositions = await _exchangeService.GetBrokerPositions(account);
|
||||
var exchangePosition = brokerPositions.FirstOrDefault(p =>
|
||||
p.Ticker == position.Ticker &&
|
||||
p.OriginDirection == position.OriginDirection);
|
||||
|
||||
if (updatedTp1 != null)
|
||||
if (exchangePosition == null)
|
||||
{
|
||||
position.TakeProfit1 = updatedTp1;
|
||||
success &= updatedTp1.Status.IsActive();
|
||||
_logger.LogWarning("Position not found on exchange - marking as canceled");
|
||||
position.Status = PositionStatus.Canceled;
|
||||
_tradingService.UpdatePosition(position);
|
||||
continue;
|
||||
}
|
||||
|
||||
Trade? updatedTp2 = null;
|
||||
if (position.TakeProfit2 != null)
|
||||
|
||||
// Update with exchange data if available
|
||||
if (exchangePosition.StopLoss != null)
|
||||
{
|
||||
updatedTp2 = await ProcessTrade(account, position.TakeProfit2, "TP2", async () =>
|
||||
await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
||||
position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow));
|
||||
_logger.LogInformation("Stop Loss found on exchange - ID: {OrderId}", exchangePosition.StopLoss.ExchangeOrderId);
|
||||
position.StopLoss = exchangePosition.StopLoss;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Stop Loss not found on exchange - creating new SL order");
|
||||
var updatedSl = await _exchangeService.OpenStopLoss(account, position.Ticker, position.OriginDirection,
|
||||
position.StopLoss.Price, position.StopLoss.Quantity, false, DateTime.UtcNow);
|
||||
|
||||
if (updatedTp2 != null)
|
||||
if (updatedSl != null)
|
||||
{
|
||||
position.TakeProfit2 = updatedTp2;
|
||||
success &= updatedTp2.Status.IsActive() || updatedTp2.Status == TradeStatus.Cancelled;
|
||||
position.StopLoss = updatedSl;
|
||||
}
|
||||
}
|
||||
|
||||
// Update position status based on trade states
|
||||
position.Status = success && AllTradesActive(position)
|
||||
? PositionStatus.Filled
|
||||
: PositionStatus.PartiallyFilled;
|
||||
if (exchangePosition.TakeProfit1 != null)
|
||||
{
|
||||
_logger.LogInformation("Take Profit found on exchange - ID: {OrderId}", exchangePosition.TakeProfit1.ExchangeOrderId);
|
||||
position.TakeProfit1 = exchangePosition.TakeProfit1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Take Profit not found on exchange - creating new TP order");
|
||||
var updatedTp1 = await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
||||
position.TakeProfit1.Price, position.TakeProfit1.Quantity, false, DateTime.UtcNow);
|
||||
|
||||
if (updatedTp1 != null)
|
||||
{
|
||||
position.TakeProfit1 = updatedTp1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle TP2 if it exists
|
||||
if (position.TakeProfit2 != null)
|
||||
{
|
||||
if (exchangePosition.TakeProfit2 != null)
|
||||
{
|
||||
_logger.LogInformation("Take Profit 2 found on exchange - ID: {OrderId}", exchangePosition.TakeProfit2.ExchangeOrderId);
|
||||
position.TakeProfit2 = exchangePosition.TakeProfit2;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Take Profit 2 not found on exchange - creating new TP2 order");
|
||||
var updatedTp2 = await _exchangeService.OpenTakeProfit(account, position.Ticker, position.OriginDirection,
|
||||
position.TakeProfit2.Price, position.TakeProfit2.Quantity, false, DateTime.UtcNow);
|
||||
|
||||
if (updatedTp2 != null)
|
||||
{
|
||||
position.TakeProfit2 = updatedTp2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update position status based on verification results
|
||||
var success = AllTradesActive(position);
|
||||
position.Status = success ? PositionStatus.Filled : PositionStatus.PartiallyFilled;
|
||||
|
||||
_logger.LogInformation("Final position status: {Status}", position.Status);
|
||||
}
|
||||
@@ -119,52 +153,13 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Trade?> ProcessTrade(Account account, Trade trade, string tradeType, Func<Task<Trade>> createTrade)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Check existing status on exchange
|
||||
var exchangeTrade = await _exchangeService.GetTrade(account, trade.ExchangeOrderId, trade.Ticker);
|
||||
if (exchangeTrade != null && exchangeTrade.Status.IsActive())
|
||||
{
|
||||
_logger.LogInformation("{TradeType} already exists on exchange - Status: {Status}",
|
||||
tradeType, exchangeTrade.Status);
|
||||
return exchangeTrade;
|
||||
}
|
||||
|
||||
// 2. Only create new order if in pending state
|
||||
if (trade.Status != TradeStatus.PendingOpen)
|
||||
{
|
||||
_logger.LogWarning("{TradeType} creation skipped - Invalid status: {Status}",
|
||||
tradeType, trade.Status);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. Create new order
|
||||
var newTrade = await createTrade();
|
||||
if (newTrade?.Status == TradeStatus.Requested)
|
||||
{
|
||||
_logger.LogInformation("{TradeType} successfully created - ExchangeID: {ExchangeOrderId}",
|
||||
tradeType, newTrade.ExchangeOrderId);
|
||||
return newTrade;
|
||||
}
|
||||
|
||||
_logger.LogError("{TradeType} creation failed - Null response or invalid status", tradeType);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{TradeType} processing failed", tradeType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllTradesActive(Position position)
|
||||
{
|
||||
return position.StopLoss.Status.IsActive() &&
|
||||
position.TakeProfit1.Status.IsActive() &&
|
||||
(position.TakeProfit2?.Status.IsActive() ?? true);
|
||||
}
|
||||
|
||||
private async Task ManageFilledPositions()
|
||||
{
|
||||
var positions = GetPositions(PositionStatus.Filled);
|
||||
@@ -181,22 +176,98 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
||||
position.Status = PositionStatus.Updating;
|
||||
_tradingService.UpdatePosition(position);
|
||||
|
||||
_logger.LogInformation("Managing filled position - Direction: {Direction}, Open Since: {OpenDate}",
|
||||
_logger.LogInformation("Checking position state on exchange - Direction: {Direction}, Open Since: {OpenDate}",
|
||||
position.OriginDirection, position.Date.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
var account = await GetAccount(position.AccountName);
|
||||
|
||||
// Perform position management
|
||||
var updatedPosition = await _tradingService.ManagePosition(account, position);
|
||||
// Check if position still exists on broker
|
||||
var brokerPositions = await _exchangeService.GetBrokerPositions(account);
|
||||
var exchangePosition = brokerPositions.FirstOrDefault(p =>
|
||||
p.Ticker == position.Ticker &&
|
||||
p.OriginDirection == position.OriginDirection);
|
||||
|
||||
// Log status changes if they occurred
|
||||
if (updatedPosition.Status != position.Status)
|
||||
if (exchangePosition == null)
|
||||
{
|
||||
_logger.LogInformation("Position status updated: {OldStatus} → {NewStatus}",
|
||||
position.Status, updatedPosition.Status);
|
||||
// Position no longer on exchange - it has been closed
|
||||
_logger.LogInformation("Position no longer on exchange - marking as finished");
|
||||
position.Status = PositionStatus.Finished;
|
||||
|
||||
// Determine if SL or TP was hit by checking which one is missing
|
||||
if (exchangePosition?.StopLoss == null && position.StopLoss.Status != TradeStatus.Filled)
|
||||
{
|
||||
_logger.LogInformation("Stop loss appears to have been hit");
|
||||
position.StopLoss.SetStatus(TradeStatus.Filled);
|
||||
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||
position,
|
||||
position.StopLoss.Quantity,
|
||||
position.StopLoss.Price,
|
||||
position.Open.Leverage);
|
||||
}
|
||||
else if (exchangePosition?.TakeProfit1 == null && position.TakeProfit1.Status != TradeStatus.Filled)
|
||||
{
|
||||
_logger.LogInformation("Take profit 1 appears to have been hit");
|
||||
position.TakeProfit1.SetStatus(TradeStatus.Filled);
|
||||
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||
position,
|
||||
position.TakeProfit1.Quantity,
|
||||
position.TakeProfit1.Price,
|
||||
position.Open.Leverage);
|
||||
}
|
||||
else if (exchangePosition?.TakeProfit2 == null && position.TakeProfit2?.Status != TradeStatus.Filled)
|
||||
{
|
||||
_logger.LogInformation("Take profit 2 appears to have been hit");
|
||||
position.TakeProfit2.SetStatus(TradeStatus.Filled);
|
||||
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||
position,
|
||||
position.TakeProfit2.Quantity,
|
||||
position.TakeProfit2.Price,
|
||||
position.Open.Leverage);
|
||||
}
|
||||
|
||||
// Cancel any remaining orders
|
||||
await _exchangeService.CancelOrder(account, position.Ticker);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position still exists - update with exchange data
|
||||
_logger.LogInformation("Position still active on exchange with quantity {Quantity}",
|
||||
exchangePosition.Open?.Quantity ?? 0);
|
||||
|
||||
// Update our position with broker data
|
||||
if (exchangePosition.Open != null)
|
||||
position.Open = exchangePosition.Open;
|
||||
|
||||
if (exchangePosition.StopLoss != null)
|
||||
position.StopLoss = exchangePosition.StopLoss;
|
||||
|
||||
if (exchangePosition.TakeProfit1 != null)
|
||||
position.TakeProfit1 = exchangePosition.TakeProfit1;
|
||||
|
||||
if (exchangePosition.TakeProfit2 != null)
|
||||
position.TakeProfit2 = exchangePosition.TakeProfit2;
|
||||
|
||||
if (exchangePosition.ProfitAndLoss != null)
|
||||
position.ProfitAndLoss = exchangePosition.ProfitAndLoss;
|
||||
else
|
||||
{
|
||||
// Calculate PNL if not provided
|
||||
var lastPrice = _exchangeService.GetPrice(account, position.Ticker, DateTime.UtcNow);
|
||||
position.ProfitAndLoss = TradingBox.GetProfitAndLoss(
|
||||
position,
|
||||
position.Open.Quantity,
|
||||
lastPrice,
|
||||
position.Open.Leverage);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Updated position from exchange - PNL: {PNL}",
|
||||
position.ProfitAndLoss?.Net ?? 0);
|
||||
|
||||
// Keep status as Filled
|
||||
position.Status = PositionStatus.Filled;
|
||||
}
|
||||
|
||||
_tradingService.UpdatePosition(updatedPosition);
|
||||
_tradingService.UpdatePosition(position);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -243,45 +314,47 @@ public class PositionManagerWorker : BaseWorker<PositionManagerWorker>
|
||||
_tradingService.UpdatePosition(position);
|
||||
|
||||
var account = await GetAccount(position.AccountName);
|
||||
var trade = await _exchangeService.GetTrade(account.Key, position.Open.ExchangeOrderId, position.Ticker);
|
||||
var openTrade = position.Open;
|
||||
|
||||
if (trade.Status == TradeStatus.PendingOpen || trade.Status == TradeStatus.Requested)
|
||||
|
||||
// Check if position exists on broker
|
||||
var brokerPositions = await _exchangeService.GetBrokerPositions(account);
|
||||
var exchangePosition = brokerPositions.FirstOrDefault(p =>
|
||||
p.Ticker == position.Ticker &&
|
||||
p.OriginDirection == position.OriginDirection);
|
||||
|
||||
if (exchangePosition != null)
|
||||
{
|
||||
// Position staleness check
|
||||
if (position.Date < DateTime.UtcNow.AddDays(-1))
|
||||
{
|
||||
position.Status = PositionStatus.Canceled;
|
||||
_tradingService.UpdatePosition(position);
|
||||
_logger.LogWarning("[{Identifier}] Position canceled - stale since {PositionAge} days",
|
||||
position.Identifier,
|
||||
(DateTime.UtcNow - position.Date).TotalDays);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset status for retry
|
||||
position.Status = PositionStatus.New;
|
||||
_tradingService.UpdatePosition(position);
|
||||
_logger.LogInformation("[{Identifier}] Awaiting order fill - {Ticker} (0/{ExpectedQuantity})",
|
||||
position.Identifier,
|
||||
position.Ticker, openTrade.Quantity);
|
||||
}
|
||||
// Position is confirmed on exchange
|
||||
position.Status = PositionStatus.PartiallyFilled;
|
||||
position.Open = exchangePosition.Open; // Use the exchange data
|
||||
_tradingService.UpdatePosition(position);
|
||||
|
||||
_logger.LogInformation("[{Identifier}] Position found on exchange - moving to partially filled status",
|
||||
position.Identifier);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Position not found on exchange - check for staleness
|
||||
if (position.Date < DateTime.UtcNow.AddDays(-1))
|
||||
{
|
||||
position.Status = PositionStatus.Canceled;
|
||||
_tradingService.UpdatePosition(position);
|
||||
_logger.LogWarning("[{Identifier}] Position canceled - stale since {PositionAge} days",
|
||||
position.Identifier,
|
||||
(DateTime.UtcNow - position.Date).TotalDays);
|
||||
}
|
||||
else
|
||||
{
|
||||
position.Status = PositionStatus.PartiallyFilled;
|
||||
position.Open = openTrade;
|
||||
// Position is now open, now waiting to open SLTP
|
||||
// Reset status to try again
|
||||
position.Status = PositionStatus.New;
|
||||
_tradingService.UpdatePosition(position);
|
||||
|
||||
_logger.LogInformation("[{Identifier}] Position now open ",
|
||||
_logger.LogInformation("[{Identifier}] Position not yet found on exchange - awaiting fill",
|
||||
position.Identifier);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing position {Identifier}", position.Identifier);
|
||||
// Consider resetting to New status for retry if needed
|
||||
// Reset to New status for retry
|
||||
position.Status = PositionStatus.New;
|
||||
_tradingService.UpdatePosition(position);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user