Add user to position + fix few things
This commit is contained in:
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
@@ -151,17 +151,17 @@ builder.Services.AddSwaggerGen(options =>
|
||||
|
||||
builder.WebHost.SetupDiscordBot();
|
||||
// builder.Services.AddHostedService<FeeWorker>();
|
||||
// builder.Services.AddHostedService<PositionManagerWorker>();
|
||||
builder.Services.AddHostedService<PositionManagerWorker>();
|
||||
// builder.Services.AddHostedService<PositionFetcher>();
|
||||
// builder.Services.AddHostedService<PricesFiveMinutesWorker>();
|
||||
builder.Services.AddHostedService<PricesFifteenMinutesWorker>();
|
||||
builder.Services.AddHostedService<PricesOneHourWorker>();
|
||||
builder.Services.AddHostedService<PricesFourHoursWorker>();
|
||||
builder.Services.AddHostedService<PricesOneDayWorker>();
|
||||
// builder.Services.AddHostedService<SpotlightWorker>();
|
||||
// builder.Services.AddHostedService<TraderWatcher>();
|
||||
builder.Services.AddHostedService<LeaderboardWorker>();
|
||||
builder.Services.AddHostedService<FundingRatesWatcher>();
|
||||
// builder.Services.AddHostedService<PricesFifteenMinutesWorker>();
|
||||
// builder.Services.AddHostedService<PricesOneHourWorker>();
|
||||
// builder.Services.AddHostedService<PricesFourHoursWorker>();
|
||||
// builder.Services.AddHostedService<PricesOneDayWorker>();
|
||||
// // builder.Services.AddHostedService<SpotlightWorker>();
|
||||
// // builder.Services.AddHostedService<TraderWatcher>();
|
||||
// builder.Services.AddHostedService<LeaderboardWorker>();
|
||||
// builder.Services.AddHostedService<FundingRatesWatcher>();
|
||||
|
||||
// App
|
||||
var app = builder.Build();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ public class TradingController : BaseController
|
||||
nameof(moneyManagementName));
|
||||
}
|
||||
|
||||
var user = await GetUser();
|
||||
if (moneyManagement != null)
|
||||
{
|
||||
var user = await GetUser();
|
||||
moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ public class TradingController : BaseController
|
||||
ticker,
|
||||
PositionInitiator.User,
|
||||
DateTime.UtcNow,
|
||||
user,
|
||||
isForPaperTrading: isForPaperTrading,
|
||||
price: openPrice);
|
||||
var result = await _openTradeCommandHandler.Handle(command);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Application.Trading;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using static Managing.Common.Enums;
|
||||
@@ -23,7 +24,9 @@ public class PositionTests : BaseTests
|
||||
Ticker.BTC,
|
||||
PositionInitiator.User,
|
||||
DateTime.UtcNow,
|
||||
isForPaperTrading: false);
|
||||
_account.User,
|
||||
isForPaperTrading: false,
|
||||
signalIdentifier: new Guid().ToString());
|
||||
var handler = new OpenPositionCommandHandler(
|
||||
_exchangeService,
|
||||
_accountService.Object,
|
||||
@@ -41,8 +44,8 @@ public class PositionTests : BaseTests
|
||||
// _ = new GetAccountPositioqwnInfoListOutputDTO().DecodeOutput(hexPositions).d
|
||||
//
|
||||
var openTrade = await _exchangeService.GetTrade(_account, "", Ticker.BTC);
|
||||
var position = new Position("", TradeDirection.Long, Ticker.BTC, MoneyManagement, PositionInitiator.User,
|
||||
DateTime.UtcNow)
|
||||
var position = new Position("", "", TradeDirection.Long, Ticker.BTC, MoneyManagement, PositionInitiator.User,
|
||||
DateTime.UtcNow, new User())
|
||||
{
|
||||
Open = openTrade
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using Xunit;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -9,7 +10,8 @@ namespace Managing.Application.Tests
|
||||
[Theory]
|
||||
[InlineData(1, 100, 110, 10)]
|
||||
[InlineData(2, 100, 110, 20)]
|
||||
public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice, decimal expectedResult)
|
||||
public void Should_Return_Correct_ProfitAndLoss_Amount(decimal quantity, decimal price, decimal exitPrice,
|
||||
decimal expectedResult)
|
||||
{
|
||||
// Arrange
|
||||
var init = new List<Tuple<decimal, decimal>>();
|
||||
@@ -42,7 +44,8 @@ namespace Managing.Application.Tests
|
||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||
|
||||
// Trigger Stop Loss
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||
position.OriginDirection);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
||||
@@ -65,7 +68,8 @@ namespace Managing.Application.Tests
|
||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||
|
||||
// Trigger Stop Loss
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, position.OriginDirection);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||
position.OriginDirection);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3.97005582759999752M, position.ProfitAndLoss.Realized);
|
||||
@@ -127,8 +131,10 @@ namespace Managing.Application.Tests
|
||||
|
||||
// Act
|
||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price, TradeDirection.Short);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Short);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit1.Quantity, position.TakeProfit1.Price,
|
||||
TradeDirection.Short);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||
TradeDirection.Short);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
||||
@@ -151,7 +157,8 @@ namespace Managing.Application.Tests
|
||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||
|
||||
// Trigger Stop Loss
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Short);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price,
|
||||
TradeDirection.Short);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(120, position.ProfitAndLoss.Realized);
|
||||
@@ -174,7 +181,8 @@ namespace Managing.Application.Tests
|
||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||
|
||||
// Trigger Stop Loss
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price, TradeDirection.Long);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.StopLoss.Price,
|
||||
TradeDirection.Long);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(20, position.ProfitAndLoss.Realized);
|
||||
@@ -197,7 +205,8 @@ namespace Managing.Application.Tests
|
||||
position.ProfitAndLoss = new ProfitAndLoss(orders, position.OriginDirection);
|
||||
|
||||
// Trigger Stop Loss
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price, TradeDirection.Long);
|
||||
position.ProfitAndLoss.AddFill(-position.TakeProfit2.Quantity, position.TakeProfit2.Price,
|
||||
TradeDirection.Long);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(120, position.ProfitAndLoss.Realized);
|
||||
@@ -205,47 +214,54 @@ namespace Managing.Application.Tests
|
||||
|
||||
private static Position GetFakeShortPosition()
|
||||
{
|
||||
return new Position("FakeAccount", TradeDirection.Short, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
|
||||
return new Position("", "FakeAccount", TradeDirection.Short, Ticker.BTC, null,
|
||||
PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
|
||||
{
|
||||
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
|
||||
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
||||
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
||||
StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
|
||||
TradeType.StopMarket, Ticker.ADA, 10, 110, 1, "StopLossOrderId", ""),
|
||||
TradeType.StopMarket, Ticker.ADA, 10, 110, 1, "StopLossOrderId", ""),
|
||||
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 6, 90, 1, "TakeProfit1OrderId", ""),
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 6, 90, 1, "TakeProfit1OrderId", ""),
|
||||
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 4, 85, 1, "TakeProfit1OrderId", "")
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 4, 85, 1, "TakeProfit1OrderId", "")
|
||||
};
|
||||
}
|
||||
|
||||
private static Position GetSolanaLongPosition()
|
||||
{
|
||||
return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
|
||||
return new Position("", "FakeAccount", TradeDirection.Long, Ticker.BTC, null,
|
||||
PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
|
||||
{
|
||||
Open = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.Filled,
|
||||
TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1, "OpenOrderId", ""),
|
||||
TradeType.Market, Ticker.ADA, (decimal)6.0800904000245037980887037491, (decimal)81.6200, 1,
|
||||
"OpenOrderId", ""),
|
||||
StopLoss = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
||||
TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1, "StopLossOrderId", ""),
|
||||
TradeType.StopMarket, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)79.987600, 1,
|
||||
"StopLossOrderId", ""),
|
||||
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000, 1, "TakeProfit1OrderId", ""),
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)2.4320361600098015192354814996, (decimal)85.701000,
|
||||
1, "TakeProfit1OrderId", ""),
|
||||
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.PendingOpen,
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000, 1, "TakeProfit1OrderId", "")
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, (decimal)3.6480542400147022788532222495, (decimal)89.782000,
|
||||
1, "TakeProfit1OrderId", "")
|
||||
};
|
||||
}
|
||||
|
||||
private static Position GetFakeLongPosition()
|
||||
{
|
||||
return new Position("FakeAccount", TradeDirection.Long, Ticker.BTC, null, PositionInitiator.PaperTrading, DateTime.UtcNow)
|
||||
return new Position("", "FakeAccount", TradeDirection.Long, Ticker.BTC, null,
|
||||
PositionInitiator.PaperTrading, DateTime.UtcNow, new User())
|
||||
{
|
||||
Open = new Trade(DateTime.Now, TradeDirection.Short, TradeStatus.Filled,
|
||||
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
||||
TradeType.Market, Ticker.ADA, 10, 100, 1, "OpenOrderId", ""),
|
||||
StopLoss = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
|
||||
TradeType.StopMarket, Ticker.ADA, 10, 90, 1, "StopLossOrderId", ""),
|
||||
TradeType.StopMarket, Ticker.ADA, 10, 90, 1, "StopLossOrderId", ""),
|
||||
TakeProfit1 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 6, 110, 1, "TakeProfit1OrderId", ""),
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 6, 110, 1, "TakeProfit1OrderId", ""),
|
||||
TakeProfit2 = new Trade(DateTime.Now, TradeDirection.Long, TradeStatus.PendingOpen,
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 4, 115, 1, "TakeProfit1OrderId", "")
|
||||
TradeType.TakeProfitLimit, Ticker.ADA, 4, 115, 1, "TakeProfit1OrderId", "")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +331,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
: TradingService.GetPositionByIdentifier(positionForSignal.Identifier);
|
||||
|
||||
var positionsExchange = IsForBacktest
|
||||
? new List<Position>{position}
|
||||
? new List<Position> { position }
|
||||
: await TradingService.GetBrokerPositions(Account);
|
||||
|
||||
if (!IsForBacktest)
|
||||
@@ -344,11 +344,13 @@ public class TradingBot : Bot, ITradingBot
|
||||
var orders = await ExchangeService.GetOpenOrders(Account, Ticker);
|
||||
if (orders.Any())
|
||||
{
|
||||
await LogInformation($"Cannot update Position. Position is still waiting for opening. There is {orders.Count()} open orders.");
|
||||
await LogInformation(
|
||||
$"Cannot update Position. Position is still waiting for opening. There is {orders.Count()} open orders.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogWarning($"Cannot update Position. No position on exchange and no orders. Position {signal.Identifier} might be closed already.");
|
||||
await LogWarning(
|
||||
$"Cannot update Position. No position on exchange and no orders. Position {signal.Identifier} might be closed already.");
|
||||
await HandleClosedPosition(positionForSignal);
|
||||
}
|
||||
}
|
||||
@@ -446,7 +448,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
Logger.LogInformation($"Try to re-open position");
|
||||
await OpenPosition(signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -525,15 +527,16 @@ public class TradingBot : Bot, ITradingBot
|
||||
Ticker,
|
||||
IsForBacktest ? PositionInitiator.PaperTrading : PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
User,
|
||||
IsForBacktest,
|
||||
lastPrice,
|
||||
balance: WalletBalances.LastOrDefault().Value,
|
||||
fee: Fee);
|
||||
fee: Fee,
|
||||
signalIdentifier: signal.Identifier);
|
||||
|
||||
var position = await new OpenPositionCommandHandler(ExchangeService, AccountService, TradingService)
|
||||
.Handle(command);
|
||||
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
position.SignalIdentifier = signal.Identifier;
|
||||
@@ -691,7 +694,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
await ExchangeService.CancelOrder(Account, Ticker);
|
||||
var closePendingOrderStatus = await ExchangeService.CancelOrder(Account, Ticker);
|
||||
Logger.LogInformation($"Closing all {Ticker} orders status : {closePendingOrderStatus}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -839,7 +842,7 @@ 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,
|
||||
StrategyType.Stc, SignalType.Signal);
|
||||
StrategyType.Stc, SignalType.Signal);
|
||||
signal.Status = SignalStatus.WaitingForPosition; // Ensure status is correct
|
||||
signal.User = Account.User; // Assign user
|
||||
|
||||
@@ -854,7 +857,7 @@ public class TradingBot : Bot, ITradingBot
|
||||
if (position == null)
|
||||
{
|
||||
// Clean up the signal if position creation failed
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
throw new Exception("Failed to open position");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using MediatR;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -14,11 +15,13 @@ namespace Managing.Application.Trading.Commands
|
||||
Ticker ticker,
|
||||
PositionInitiator initiator,
|
||||
DateTime date,
|
||||
User user,
|
||||
bool isForPaperTrading = false,
|
||||
decimal? price = null,
|
||||
decimal? balance = 1000,
|
||||
decimal? fee = null,
|
||||
bool? ignoreSLTP = false)
|
||||
decimal? fee = null,
|
||||
bool? ignoreSLTP = false,
|
||||
string signalIdentifier = null)
|
||||
{
|
||||
AccountName = accountName;
|
||||
MoneyManagement = moneyManagement;
|
||||
@@ -31,8 +34,11 @@ namespace Managing.Application.Trading.Commands
|
||||
Initiator = initiator;
|
||||
Fee = fee;
|
||||
IgnoreSLTP = ignoreSLTP;
|
||||
User = user;
|
||||
SignalIdentifier = signalIdentifier;
|
||||
}
|
||||
|
||||
public string SignalIdentifier { get; set; }
|
||||
public string AccountName { get; }
|
||||
public MoneyManagement MoneyManagement { get; }
|
||||
public TradeDirection Direction { get; }
|
||||
@@ -44,5 +50,6 @@ namespace Managing.Application.Trading.Commands
|
||||
public decimal? Balance { get; }
|
||||
public DateTime Date { get; set; }
|
||||
public PositionInitiator Initiator { get; internal set; }
|
||||
public User User { get; internal set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,8 +26,15 @@ namespace Managing.Application.Trading
|
||||
}
|
||||
|
||||
var initiator = request.IsForPaperTrading ? PositionInitiator.PaperTrading : request.Initiator;
|
||||
var position = new Position(request.AccountName, request.Direction, request.Ticker, request.MoneyManagement,
|
||||
initiator, request.Date);
|
||||
var position = new Position(new Guid().ToString(), request.AccountName, request.Direction, request.Ticker,
|
||||
request.MoneyManagement,
|
||||
initiator, request.Date, request.User);
|
||||
|
||||
if (!string.IsNullOrEmpty(request.SignalIdentifier))
|
||||
{
|
||||
position.SignalIdentifier = request.SignalIdentifier;
|
||||
}
|
||||
|
||||
var balance = request.IsForPaperTrading
|
||||
? request.Balance.GetValueOrDefault()
|
||||
: exchangeService.GetBalance(account, request.IsForPaperTrading).Result;
|
||||
@@ -63,12 +70,12 @@ namespace Managing.Application.Trading
|
||||
TradeType.Limit,
|
||||
isForPaperTrading: request.IsForPaperTrading,
|
||||
currentDate: request.Date,
|
||||
stopLossPrice: stopLossPrice, // Pass determined SL price
|
||||
stopLossPrice: stopLossPrice, // Pass determined SL price
|
||||
takeProfitPrice: takeProfitPrice); // Pass determined TP price
|
||||
|
||||
//trade.Fee = TradingHelpers.GetFeeAmount(fee, openPrice * quantity, account.Exchange);
|
||||
position.Open = trade;
|
||||
|
||||
|
||||
var closeDirection = request.Direction == TradeDirection.Long
|
||||
? TradeDirection.Short
|
||||
: TradeDirection.Long;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using InfluxDB.Client.Api.Domain;
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Trading.Commands;
|
||||
using Managing.Application.Trading;
|
||||
@@ -63,13 +64,12 @@ public class OpenPosition : FlowBase
|
||||
var Positions = JsonConvert.DeserializeObject<List<Position>>(_cacheService.GetValue(POSITIONS_KEY));
|
||||
var Signals = JsonConvert.DeserializeObject<HashSet<Signal>>(_cacheService.GetValue(POSITIONS_KEY));
|
||||
|
||||
Fee = _cacheService.GetOrSave(FEE_KEY, () =>
|
||||
{
|
||||
return _tradingService.GetFee(Account, OpenPositionParameters.IsForBacktest);
|
||||
}, TimeSpan.FromDays(1));
|
||||
Fee = _cacheService.GetOrSave(FEE_KEY,
|
||||
() => { return _tradingService.GetFee(Account, OpenPositionParameters.IsForBacktest); },
|
||||
TimeSpan.FromDays(1));
|
||||
|
||||
await ExecuteOpenPosition(signal, Positions, Signals, Candles, Account);
|
||||
|
||||
|
||||
_cacheService.SaveValue(POSITIONS_KEY, JsonConvert.SerializeObject(Positions));
|
||||
_cacheService.SaveValue(SIGNALS_KEY, JsonConvert.SerializeObject(Signals));
|
||||
|
||||
@@ -82,13 +82,16 @@ public class OpenPosition : FlowBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals, HashSet<Candle> candles, Account account)
|
||||
private async Task ExecuteOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
|
||||
HashSet<Candle> candles, Account account)
|
||||
{
|
||||
// Check if a position is already open
|
||||
var openedPosition = positions.FirstOrDefault(p => p.Status == PositionStatus.Filled
|
||||
&& p.SignalIdentifier != signal.Identifier);
|
||||
&& p.SignalIdentifier != signal.Identifier);
|
||||
|
||||
var lastPrice = OpenPositionParameters.IsForBacktest ? candles.Last().Close : _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow);
|
||||
var lastPrice = OpenPositionParameters.IsForBacktest
|
||||
? candles.Last().Close
|
||||
: _exchangeService.GetPrice(account, signal.Ticker, DateTime.UtcNow);
|
||||
|
||||
// If position open
|
||||
if (openedPosition != null)
|
||||
@@ -110,7 +113,8 @@ public class OpenPosition : FlowBase
|
||||
{
|
||||
//await LogInformation("Try to flip the position because of an opposite direction signal");
|
||||
//await CloseTrade(previousSignal, openedPosition, openedPosition.Open, lastPrice, true);
|
||||
positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status = PositionStatus.Flipped;
|
||||
positions.FirstOrDefault(s => s.Identifier == previousSignal.Identifier).Status =
|
||||
PositionStatus.Flipped;
|
||||
await ExecuteOpenPosition(signal, positions, signals, candles, account);
|
||||
|
||||
//await LogInformation($"Position {previousSignal.Identifier} flipped by {signal.Identifier} at {lastPrice}$");
|
||||
@@ -134,23 +138,27 @@ public class OpenPosition : FlowBase
|
||||
|
||||
try
|
||||
{
|
||||
var moneyManagement = await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName);
|
||||
var WalletBalances = JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(_cacheService.GetValue(WALLET_BALANCES));
|
||||
var moneyManagement =
|
||||
await _settingsRepository.GetMoneyManagement(OpenPositionParameters.MoneyManagementName);
|
||||
var WalletBalances =
|
||||
JsonConvert.DeserializeObject<Dictionary<DateTime, decimal>>(
|
||||
_cacheService.GetValue(WALLET_BALANCES));
|
||||
|
||||
var command = new OpenPositionRequest(
|
||||
OpenPositionParameters.AccountName,
|
||||
moneyManagement,
|
||||
moneyManagement,
|
||||
signal.Direction,
|
||||
signal.Ticker,
|
||||
signal.Ticker,
|
||||
PositionInitiator.Bot,
|
||||
signal.Date,
|
||||
account.User,
|
||||
OpenPositionParameters.IsForBacktest,
|
||||
lastPrice,
|
||||
balance: WalletBalances.LastOrDefault().Value,
|
||||
fee: Fee);
|
||||
|
||||
var position = await new OpenPositionCommandHandler(_exchangeService, _accountService, _tradingService)
|
||||
.Handle(command);
|
||||
.Handle(command);
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
@@ -158,18 +166,21 @@ public class OpenPosition : FlowBase
|
||||
{
|
||||
position.SignalIdentifier = signal.Identifier;
|
||||
positions.Add(position);
|
||||
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.PositionOpen;
|
||||
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
|
||||
SignalStatus.PositionOpen;
|
||||
|
||||
if (!OpenPositionParameters.IsForBacktest)
|
||||
{
|
||||
await _messengerService.SendPosition(position);
|
||||
}
|
||||
|
||||
Output = JsonConvert.SerializeObject(position);
|
||||
//Logger.LogInformation($"Position requested");
|
||||
}
|
||||
else
|
||||
{
|
||||
positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = PositionStatus.Rejected;
|
||||
positions.FirstOrDefault(s => s.Identifier == signal.Identifier).Status =
|
||||
PositionStatus.Rejected;
|
||||
signals.FirstOrDefault(s => s.Identifier == signal.Identifier).Status = SignalStatus.Expired;
|
||||
}
|
||||
}
|
||||
@@ -180,18 +191,18 @@ public class OpenPosition : FlowBase
|
||||
//await LogWarning($"Cannot open trade : {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private bool CanOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals, HashSet<Candle> candles)
|
||||
private bool CanOpenPosition(Signal signal, List<Position> positions, HashSet<Signal> signals,
|
||||
HashSet<Candle> candles)
|
||||
{
|
||||
if (positions.Count == 0)
|
||||
return true;
|
||||
|
||||
var lastPosition = positions.LastOrDefault(p => p.IsFinished()
|
||||
&& p.SignalIdentifier != signal.Identifier
|
||||
&& p.ProfitAndLoss.Realized < 0
|
||||
&& p.OriginDirection == signal.Direction);
|
||||
&& p.SignalIdentifier != signal.Identifier
|
||||
&& p.ProfitAndLoss.Realized < 0
|
||||
&& p.OriginDirection == signal.Direction);
|
||||
|
||||
if (lastPosition == null)
|
||||
return true;
|
||||
@@ -224,4 +235,4 @@ public class OpenPositionParameters
|
||||
public string AccountName { get; set; }
|
||||
public bool IsForBacktest { get; set; }
|
||||
public bool FlipPosition { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,10 @@ namespace Managing.Domain.Trades
|
||||
{
|
||||
public class Position
|
||||
{
|
||||
public Position(string accountName, TradeDirection originDirection, Ticker ticker, MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date)
|
||||
public Position(string identifier, string accountName, TradeDirection originDirection, Ticker ticker,
|
||||
MoneyManagement moneyManagement, PositionInitiator positionInitiator, DateTime date, User user)
|
||||
{
|
||||
Identifier = Guid.NewGuid().ToString();
|
||||
Identifier = identifier;
|
||||
AccountName = accountName;
|
||||
OriginDirection = originDirection;
|
||||
Ticker = ticker;
|
||||
@@ -17,34 +18,24 @@ namespace Managing.Domain.Trades
|
||||
Initiator = positionInitiator;
|
||||
Date = date;
|
||||
Status = Initiator == PositionInitiator.PaperTrading ? PositionStatus.Filled : PositionStatus.New;
|
||||
User = user;
|
||||
}
|
||||
|
||||
[Required]
|
||||
public string AccountName { get; }
|
||||
[Required]
|
||||
public DateTime Date { get; set; }
|
||||
[Required]
|
||||
public TradeDirection OriginDirection { get; }
|
||||
[Required]
|
||||
public Ticker Ticker { get; }
|
||||
[Required]
|
||||
public MoneyManagement MoneyManagement { get; }
|
||||
[Required]
|
||||
public Trade Open { get; set; }
|
||||
[Required]
|
||||
public Trade StopLoss { get; set; }
|
||||
[Required]
|
||||
public Trade TakeProfit1 { get; set; }
|
||||
[Required] public string AccountName { get; }
|
||||
[Required] public DateTime Date { get; set; }
|
||||
[Required] public TradeDirection OriginDirection { get; }
|
||||
[Required] public Ticker Ticker { get; }
|
||||
[Required] public MoneyManagement MoneyManagement { get; }
|
||||
[Required] public Trade Open { get; set; }
|
||||
[Required] public Trade StopLoss { get; set; }
|
||||
[Required] public Trade TakeProfit1 { get; set; }
|
||||
public Trade TakeProfit2 { get; set; }
|
||||
public ProfitAndLoss ProfitAndLoss { get; set; }
|
||||
[Required]
|
||||
public PositionStatus Status { get; set; }
|
||||
[Required] public PositionStatus Status { get; set; }
|
||||
public string SignalIdentifier { get; set; }
|
||||
[Required]
|
||||
public string Identifier { get; set; }
|
||||
[Required]
|
||||
public PositionInitiator Initiator { get; }
|
||||
public User User { get; set; }
|
||||
[Required] public string Identifier { get; set; }
|
||||
[Required] public PositionInitiator Initiator { get; }
|
||||
[Required] public User User { get; set; }
|
||||
|
||||
public bool IsFinished()
|
||||
{
|
||||
@@ -56,4 +47,4 @@ namespace Managing.Domain.Trades
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +257,7 @@ public static class MongoMappers
|
||||
MoneyManagement = Map(position.MoneyManagement),
|
||||
Initiator = position.Initiator,
|
||||
Ticker = position.Ticker,
|
||||
User = position.User != null ? Map(position.User) : null
|
||||
User = Map(position.User)
|
||||
};
|
||||
|
||||
if (position.StopLoss != null)
|
||||
@@ -294,8 +294,8 @@ public static class MongoMappers
|
||||
|
||||
public static Position Map(PositionDto dto)
|
||||
{
|
||||
var position = new Position(dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker,
|
||||
Map(dto.MoneyManagement), dto.Initiator, dto.Date)
|
||||
var position = new Position(dto.Identifier, dto.AccountName, originDirection: dto.OriginDirection, dto.Ticker,
|
||||
Map(dto.MoneyManagement), dto.Initiator, dto.Date, Map(dto.User))
|
||||
{
|
||||
Open = new Trade(date: dto.Open.Date, direction: dto.Open.Direction, status: dto.Open.Status,
|
||||
tradeType: dto.Open.TradeType, ticker: dto.Open.Ticker, quantity: dto.Open.Quantity,
|
||||
@@ -305,7 +305,7 @@ public static class MongoMappers
|
||||
Status = dto.Status,
|
||||
SignalIdentifier = dto.SignalIdentifier,
|
||||
Identifier = dto.Identifier,
|
||||
User = dto.User != null ? Map(dto.User) : null
|
||||
User = Map(dto.User)
|
||||
};
|
||||
|
||||
if (dto.StopLoss != null)
|
||||
|
||||
@@ -7,6 +7,7 @@ using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Messengers.Discord
|
||||
@@ -73,7 +74,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
TakeProfit = takeProfit.GetValueOrDefault(),
|
||||
};
|
||||
var tradeCommand = new OpenPositionRequest(accountName, moneymanagement, direction, ticker,
|
||||
PositionInitiator.User, DateTime.UtcNow);
|
||||
PositionInitiator.User, DateTime.UtcNow, new User());
|
||||
var result = await _openTradeCommandHandler.Handle(tradeCommand);
|
||||
var builder = new ComponentBuilder().WithButton("Close Position",
|
||||
$"{Constants.DiscordButtonAction.ClosePosition}|{result.Open.ExchangeOrderId}");
|
||||
|
||||
@@ -273,6 +273,7 @@ namespace Managing.Infrastructure.Messengers.Discord
|
||||
ticker,
|
||||
initiator,
|
||||
DateTime.UtcNow,
|
||||
defaultUser,
|
||||
ignoreSLTP: ignoreSLTP);
|
||||
var position = await new OpenPositionCommandHandler(exchangeService, accountService, tradingService)
|
||||
.Handle(tradeCommand);
|
||||
|
||||
@@ -8,6 +8,7 @@ using Managing.Infrastructure.Evm.Models.Gmx.v2;
|
||||
using Managing.Infrastructure.Evm.Models.Proxy;
|
||||
using Nethereum.Web3;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Users;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Infrastructure.Evm.Services.Gmx;
|
||||
@@ -161,12 +162,13 @@ internal static class GmxV2Mappers
|
||||
{
|
||||
try
|
||||
{
|
||||
var position = new Position("",
|
||||
var position = new Position("", "",
|
||||
MiscExtensions.ParseEnum<TradeDirection>(gmxPosition.Direction),
|
||||
MiscExtensions.ParseEnum<Ticker>(gmxPosition.Ticker),
|
||||
new MoneyManagement(),
|
||||
PositionInitiator.User,
|
||||
gmxPosition.Date);
|
||||
gmxPosition.Date,
|
||||
new User());
|
||||
position.Open = Map(gmxPosition.Open);
|
||||
position.TakeProfit1 = Map(gmxPosition.TakeProfit1);
|
||||
position.StopLoss = Map(gmxPosition.StopLoss);
|
||||
@@ -174,15 +176,17 @@ internal static class GmxV2Mappers
|
||||
{
|
||||
Net = (decimal)gmxPosition.Pnl
|
||||
};
|
||||
|
||||
|
||||
position.Status = MiscExtensions.ParseEnum<PositionStatus>(gmxPosition.Status);
|
||||
positions.Add(position);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error mapping GMX position {gmxPosition?.ExchangeOrderId}: {ex.Message} \n StackTrace: {ex.StackTrace}");
|
||||
Console.WriteLine(
|
||||
$"Error mapping GMX position {gmxPosition?.ExchangeOrderId}: {ex.Message} \n StackTrace: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,17 +35,17 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
endpoint = $"/{endpoint}";
|
||||
}
|
||||
|
||||
var url = $"{_settings.BaseUrl}privy{endpoint}";
|
||||
|
||||
var url = $"{_settings.BaseUrl}/api/privy{endpoint}";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync(url, payload, _jsonOptions);
|
||||
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await HandleErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
@@ -62,7 +62,7 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
endpoint = $"/{endpoint}";
|
||||
}
|
||||
|
||||
var url = $"{_settings.BaseUrl}privy{endpoint}";
|
||||
var url = $"{_settings.BaseUrl}/api/privy{endpoint}";
|
||||
|
||||
if (payload != null)
|
||||
{
|
||||
@@ -72,12 +72,12 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await HandleErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
@@ -95,16 +95,16 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
}
|
||||
|
||||
var url = $"{_settings.BaseUrl}/api/gmx{endpoint}";
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync(url, payload, _jsonOptions);
|
||||
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await HandleErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
@@ -131,12 +131,12 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await HandleErrorResponse(response);
|
||||
}
|
||||
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Web3ProxyException))
|
||||
@@ -149,25 +149,26 @@ namespace Managing.Infrastructure.Evm.Services
|
||||
private async Task HandleErrorResponse(HttpResponseMessage response)
|
||||
{
|
||||
var statusCode = (int)response.StatusCode;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Try to parse as the Web3Proxy error format (success: false, error: string)
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var errorResponse = await response.Content.ReadFromJsonAsync<Web3ProxyResponse>(_jsonOptions);
|
||||
|
||||
|
||||
if (errorResponse != null && !errorResponse.Success && !string.IsNullOrEmpty(errorResponse.Error))
|
||||
{
|
||||
// Handle the standard Web3Proxy error format
|
||||
throw new Web3ProxyException(errorResponse.Error);
|
||||
}
|
||||
|
||||
|
||||
// Fallback for other error formats
|
||||
try
|
||||
try
|
||||
{
|
||||
// Try to parse as structured error if it doesn't match the simple format
|
||||
var structuredErrorResponse = await response.Content.ReadFromJsonAsync<Web3ProxyErrorResponse>(_jsonOptions);
|
||||
|
||||
var structuredErrorResponse =
|
||||
await response.Content.ReadFromJsonAsync<Web3ProxyErrorResponse>(_jsonOptions);
|
||||
|
||||
if (structuredErrorResponse?.ErrorDetails != null)
|
||||
{
|
||||
structuredErrorResponse.ErrorDetails.StatusCode = statusCode;
|
||||
|
||||
@@ -321,7 +321,7 @@ export async function increaseOrderHelper(
|
||||
referralCodeForTxn: params.referralCodeForTxn,
|
||||
triggerPrice: params.limitPrice,
|
||||
collateralTokenAddress: collateralToken.address,
|
||||
isLong: true,
|
||||
isLong: params.isLong,
|
||||
receiveTokenAddress: collateralTokenAddress,
|
||||
indexToken: marketInfo.indexToken,
|
||||
marketInfo,
|
||||
|
||||
Reference in New Issue
Block a user