Update the position count and initiator
This commit is contained in:
@@ -12,7 +12,6 @@ using Managing.Domain.Scenarios;
|
|||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Strategies;
|
using Managing.Domain.Strategies;
|
||||||
using Managing.Domain.Strategies.Base;
|
using Managing.Domain.Strategies.Base;
|
||||||
using Managing.Domain.Trades;
|
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -411,9 +410,10 @@ public class DataController : ControllerBase
|
|||||||
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
|
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
|
||||||
|
|
||||||
// Convert to detailed view model with additional information
|
// Convert to detailed view model with additional information
|
||||||
var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy)).ToList();
|
var tasks = userStrategies.Select(strategy => MapStrategyToViewModelAsync(strategy));
|
||||||
|
var result = await Task.WhenAll(tasks);
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -434,7 +434,7 @@ public class DataController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map the strategy to a view model using the shared method
|
// Map the strategy to a view model using the shared method
|
||||||
var result = MapStrategyToViewModel(strategy);
|
var result = await MapStrategyToViewModelAsync(strategy);
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
@@ -444,7 +444,7 @@ public class DataController : ControllerBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="strategy">The trading bot to map</param>
|
/// <param name="strategy">The trading bot to map</param>
|
||||||
/// <returns>A view model with detailed strategy information</returns>
|
/// <returns>A view model with detailed strategy information</returns>
|
||||||
private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy)
|
private async Task<UserStrategyDetailsViewModel> MapStrategyToViewModelAsync(Bot strategy)
|
||||||
{
|
{
|
||||||
// Calculate ROI percentage based on PnL relative to account value
|
// Calculate ROI percentage based on PnL relative to account value
|
||||||
decimal pnl = strategy.Pnl;
|
decimal pnl = strategy.Pnl;
|
||||||
@@ -464,6 +464,9 @@ public class DataController : ControllerBase
|
|||||||
// Calculate ROI for last 24h
|
// Calculate ROI for last 24h
|
||||||
decimal roiLast24h = strategy.Roi;
|
decimal roiLast24h = strategy.Roi;
|
||||||
|
|
||||||
|
// Fetch positions associated with this bot
|
||||||
|
var positions = await _tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier);
|
||||||
|
|
||||||
return new UserStrategyDetailsViewModel
|
return new UserStrategyDetailsViewModel
|
||||||
{
|
{
|
||||||
Name = strategy.Name,
|
Name = strategy.Name,
|
||||||
@@ -477,7 +480,7 @@ public class DataController : ControllerBase
|
|||||||
VolumeLast24H = volumeLast24h,
|
VolumeLast24H = volumeLast24h,
|
||||||
Wins = wins,
|
Wins = wins,
|
||||||
Losses = losses,
|
Losses = losses,
|
||||||
Positions = new List<Position>(),
|
Positions = positions.ToList(),
|
||||||
Identifier = strategy.Identifier,
|
Identifier = strategy.Identifier,
|
||||||
WalletBalances = new Dictionary<DateTime, decimal>(),
|
WalletBalances = new Dictionary<DateTime, decimal>(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public interface ITradingRepository
|
|||||||
Task<Position> GetPositionByIdentifierAsync(Guid identifier);
|
Task<Position> GetPositionByIdentifierAsync(Guid identifier);
|
||||||
Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator);
|
Task<IEnumerable<Position>> GetPositionsAsync(PositionInitiator positionInitiator);
|
||||||
Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus);
|
Task<IEnumerable<Position>> GetPositionsByStatusAsync(PositionStatus positionStatus);
|
||||||
|
Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier);
|
||||||
Task<IEnumerable<Position>> GetAllPositionsAsync();
|
Task<IEnumerable<Position>> GetAllPositionsAsync();
|
||||||
|
|
||||||
Task UpdateScenarioAsync(Scenario scenario);
|
Task UpdateScenarioAsync(Scenario scenario);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public interface ITradingService
|
|||||||
Task UpdateIndicatorAsync(IndicatorBase indicatorBase);
|
Task UpdateIndicatorAsync(IndicatorBase indicatorBase);
|
||||||
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
|
Task<IEnumerable<Position>> GetBrokerPositions(Account account);
|
||||||
Task<IEnumerable<Position>> GetAllDatabasePositionsAsync();
|
Task<IEnumerable<Position>> GetAllDatabasePositionsAsync();
|
||||||
|
Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier);
|
||||||
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
|
Task<PrivyInitAddressResponse> InitPrivyWallet(string publicAddress);
|
||||||
|
|
||||||
// Synth API integration methods
|
// Synth API integration methods
|
||||||
|
|||||||
@@ -713,7 +713,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
Config.BotTradingBalance,
|
Config.BotTradingBalance,
|
||||||
Config.IsForBacktest,
|
Config.IsForBacktest,
|
||||||
lastPrice,
|
lastPrice,
|
||||||
signalIdentifier: signal.Identifier);
|
signalIdentifier: signal.Identifier,
|
||||||
|
initiatorIdentifier: Identifier);
|
||||||
|
|
||||||
var position = await ServiceScopeHelpers
|
var position = await ServiceScopeHelpers
|
||||||
.WithScopedServices<IExchangeService, IAccountService, ITradingService, Position>(
|
.WithScopedServices<IExchangeService, IAccountService, ITradingService, Position>(
|
||||||
@@ -978,7 +979,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
_scopeFactory, async (exchangeService, accountService, tradingService) =>
|
_scopeFactory, async (exchangeService, accountService, tradingService) =>
|
||||||
{
|
{
|
||||||
closedPosition =
|
closedPosition =
|
||||||
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService)
|
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService, _scopeFactory)
|
||||||
.Handle(command);
|
.Handle(command);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ namespace Managing.Application.Trading.Commands
|
|||||||
decimal amountToTrade,
|
decimal amountToTrade,
|
||||||
bool isForPaperTrading = false,
|
bool isForPaperTrading = false,
|
||||||
decimal? price = null,
|
decimal? price = null,
|
||||||
string signalIdentifier = null)
|
string signalIdentifier = null,
|
||||||
|
Guid? initiatorIdentifier = null)
|
||||||
{
|
{
|
||||||
AccountName = accountName;
|
AccountName = accountName;
|
||||||
MoneyManagement = moneyManagement;
|
MoneyManagement = moneyManagement;
|
||||||
@@ -38,6 +39,7 @@ namespace Managing.Application.Trading.Commands
|
|||||||
IsForPaperTrading = isForPaperTrading;
|
IsForPaperTrading = isForPaperTrading;
|
||||||
Price = price;
|
Price = price;
|
||||||
SignalIdentifier = signalIdentifier;
|
SignalIdentifier = signalIdentifier;
|
||||||
|
InitiatorIdentifier = initiatorIdentifier ?? throw new ArgumentNullException(nameof(initiatorIdentifier), "InitiatorIdentifier is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SignalIdentifier { get; set; }
|
public string SignalIdentifier { get; set; }
|
||||||
@@ -51,5 +53,6 @@ namespace Managing.Application.Trading.Commands
|
|||||||
public DateTime Date { get; }
|
public DateTime Date { get; }
|
||||||
public PositionInitiator Initiator { get; }
|
public PositionInitiator Initiator { get; }
|
||||||
public User User { get; }
|
public User User { get; }
|
||||||
|
public Guid InitiatorIdentifier { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
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;
|
||||||
|
using Managing.Core;
|
||||||
using Managing.Domain.Shared.Helpers;
|
using Managing.Domain.Shared.Helpers;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ public class ClosePositionCommandHandler(
|
|||||||
IExchangeService exchangeService,
|
IExchangeService exchangeService,
|
||||||
IAccountService accountService,
|
IAccountService accountService,
|
||||||
ITradingService tradingService,
|
ITradingService tradingService,
|
||||||
IGrainFactory? grainFactory = null,
|
IServiceScopeFactory scopeFactory,
|
||||||
ILogger<ClosePositionCommandHandler> logger = null)
|
ILogger<ClosePositionCommandHandler> logger = null)
|
||||||
: ICommandHandler<ClosePositionCommand, Position>
|
: ICommandHandler<ClosePositionCommand, Position>
|
||||||
{
|
{
|
||||||
@@ -70,29 +72,35 @@ public class ClosePositionCommandHandler(
|
|||||||
|
|
||||||
if (!request.IsForBacktest)
|
if (!request.IsForBacktest)
|
||||||
await tradingService.UpdatePositionAsync(request.Position);
|
await tradingService.UpdatePositionAsync(request.Position);
|
||||||
|
|
||||||
// Notify platform summary about the closed position
|
// Notify platform summary about the closed position
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var platformGrain = grainFactory?.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(scopeFactory, async grainFactory =>
|
||||||
if (platformGrain != null)
|
|
||||||
{
|
{
|
||||||
var positionClosedEvent = new PositionClosedEvent
|
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
||||||
|
if (platformGrain != null)
|
||||||
{
|
{
|
||||||
PositionId = request.Position.Identifier,
|
var positionClosedEvent = new PositionClosedEvent
|
||||||
Ticker = request.Position.Ticker,
|
{
|
||||||
RealizedPnL = request.Position.ProfitAndLoss?.Realized ?? 0,
|
PositionId = request.Position.Identifier,
|
||||||
Volume = closedPosition.Quantity * lastPrice * request.Position.Open.Leverage,
|
Ticker = request.Position.Ticker,
|
||||||
InitialVolume = request.Position.Open.Quantity * request.Position.Open.Price * request.Position.Open.Leverage
|
RealizedPnL = request.Position.ProfitAndLoss?.Realized ?? 0,
|
||||||
};
|
Volume = closedPosition.Quantity * lastPrice * request.Position.Open.Leverage,
|
||||||
|
InitialVolume = request.Position.Open.Quantity * request.Position.Open.Price *
|
||||||
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
request.Position.Open.Leverage
|
||||||
}
|
};
|
||||||
|
|
||||||
|
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
SentrySdk.CaptureException(ex);
|
SentrySdk.CaptureException(ex);
|
||||||
logger?.LogError(ex, "Failed to notify platform summary about position closure for position {PositionId}", request.Position.Identifier);
|
logger?.LogError(ex,
|
||||||
|
"Failed to notify platform summary about position closure for position {PositionId}",
|
||||||
|
request.Position.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ namespace Managing.Application.Trading.Handlers
|
|||||||
position.SignalIdentifier = request.SignalIdentifier;
|
position.SignalIdentifier = request.SignalIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
position.InitiatorIdentifier = request.InitiatorIdentifier;
|
||||||
|
|
||||||
// Always use BotTradingBalance directly as the balance to risk
|
// Always use BotTradingBalance directly as the balance to risk
|
||||||
decimal balanceToRisk = request.AmountToTrade;
|
decimal balanceToRisk = request.AmountToTrade;
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,11 @@ public class TradingService : ITradingService
|
|||||||
return await _tradingRepository.GetAllPositionsAsync();
|
return await _tradingRepository.GetAllPositionsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier)
|
||||||
|
{
|
||||||
|
return await _tradingRepository.GetPositionsByInitiatorIdentifierAsync(initiatorIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
|
private async Task ManageTrader(TraderFollowup a, List<Ticker> tickers)
|
||||||
{
|
{
|
||||||
var shortAddress = a.Account.Address.Substring(0, 6);
|
var shortAddress = a.Account.Address.Substring(0, 6);
|
||||||
|
|||||||
@@ -66,6 +66,11 @@ namespace Managing.Domain.Trades
|
|||||||
|
|
||||||
[Id(14)] [Required] public User User { get; set; }
|
[Id(14)] [Required] public User User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of the bot or entity that initiated this position
|
||||||
|
/// </summary>
|
||||||
|
[Id(15)] [Required] public Guid InitiatorIdentifier { get; set; }
|
||||||
|
|
||||||
public bool IsFinished()
|
public bool IsFinished()
|
||||||
{
|
{
|
||||||
return Status switch
|
return Status switch
|
||||||
|
|||||||
1431
src/Managing.Infrastructure.Database/Migrations/20250815011248_AddInitiatorIdentifierToPositions.Designer.cs
generated
Normal file
1431
src/Managing.Infrastructure.Database/Migrations/20250815011248_AddInitiatorIdentifierToPositions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Databases.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddInitiatorIdentifierToPositions : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Positions_Date",
|
||||||
|
table: "Positions");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "InitiatorIdentifier",
|
||||||
|
table: "Positions",
|
||||||
|
type: "uuid",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Positions_InitiatorIdentifier",
|
||||||
|
table: "Positions",
|
||||||
|
column: "InitiatorIdentifier");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Positions_InitiatorIdentifier",
|
||||||
|
table: "Positions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "InitiatorIdentifier",
|
||||||
|
table: "Positions");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Positions_Date",
|
||||||
|
table: "Positions",
|
||||||
|
column: "Date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -231,6 +231,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
.HasPrecision(18, 8)
|
.HasPrecision(18, 8)
|
||||||
.HasColumnType("numeric(18,8)");
|
.HasColumnType("numeric(18,8)");
|
||||||
|
|
||||||
|
b.Property<int>("LongPositionCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(255)
|
.HasMaxLength(255)
|
||||||
@@ -244,6 +247,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
.HasPrecision(18, 8)
|
.HasPrecision(18, 8)
|
||||||
.HasColumnType("numeric(18,8)");
|
.HasColumnType("numeric(18,8)");
|
||||||
|
|
||||||
|
b.Property<int>("ShortPositionCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<DateTime>("StartupTime")
|
b.Property<DateTime>("StartupTime")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
@@ -668,6 +674,9 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("InitiatorIdentifier")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
b.Property<string>("MoneyManagementJson")
|
b.Property<string>("MoneyManagementJson")
|
||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
@@ -711,11 +720,11 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
|
|
||||||
b.HasKey("Identifier");
|
b.HasKey("Identifier");
|
||||||
|
|
||||||
b.HasIndex("Date");
|
|
||||||
|
|
||||||
b.HasIndex("Identifier")
|
b.HasIndex("Identifier")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("InitiatorIdentifier");
|
||||||
|
|
||||||
b.HasIndex("OpenTradeId");
|
b.HasIndex("OpenTradeId");
|
||||||
|
|
||||||
b.HasIndex("Status");
|
b.HasIndex("Status");
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ public class PositionEntity
|
|||||||
[MaxLength(255)] public string AccountName { get; set; }
|
[MaxLength(255)] public string AccountName { get; set; }
|
||||||
|
|
||||||
public int? UserId { get; set; }
|
public int? UserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier of the bot or entity that initiated this position
|
||||||
|
/// </summary>
|
||||||
|
[Required] public Guid InitiatorIdentifier { get; set; }
|
||||||
|
|
||||||
// Foreign keys to trades
|
// Foreign keys to trades
|
||||||
public int? OpenTradeId { get; set; }
|
public int? OpenTradeId { get; set; }
|
||||||
|
|||||||
@@ -300,6 +300,7 @@ public class ManagingDbContext : DbContext
|
|||||||
entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255);
|
entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255);
|
||||||
entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255);
|
entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255);
|
||||||
entity.Property(e => e.UserId);
|
entity.Property(e => e.UserId);
|
||||||
|
entity.Property(e => e.InitiatorIdentifier).IsRequired();
|
||||||
entity.Property(e => e.MoneyManagementJson).HasColumnType("text");
|
entity.Property(e => e.MoneyManagementJson).HasColumnType("text");
|
||||||
|
|
||||||
// Configure relationship with User
|
// Configure relationship with User
|
||||||
@@ -333,7 +334,7 @@ public class ManagingDbContext : DbContext
|
|||||||
entity.HasIndex(e => e.Identifier).IsUnique();
|
entity.HasIndex(e => e.Identifier).IsUnique();
|
||||||
entity.HasIndex(e => e.UserId);
|
entity.HasIndex(e => e.UserId);
|
||||||
entity.HasIndex(e => e.Status);
|
entity.HasIndex(e => e.Status);
|
||||||
entity.HasIndex(e => e.Date);
|
entity.HasIndex(e => e.InitiatorIdentifier);
|
||||||
|
|
||||||
// Composite indexes
|
// Composite indexes
|
||||||
entity.HasIndex(e => new { e.UserId, e.Identifier });
|
entity.HasIndex(e => new { e.UserId, e.Identifier });
|
||||||
|
|||||||
@@ -561,7 +561,8 @@ public static class PostgreSqlMappers
|
|||||||
entity.User != null ? Map(entity.User) : null)
|
entity.User != null ? Map(entity.User) : null)
|
||||||
{
|
{
|
||||||
Status = entity.Status,
|
Status = entity.Status,
|
||||||
SignalIdentifier = entity.SignalIdentifier
|
SignalIdentifier = entity.SignalIdentifier,
|
||||||
|
InitiatorIdentifier = entity.InitiatorIdentifier
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set ProfitAndLoss with proper type
|
// Set ProfitAndLoss with proper type
|
||||||
@@ -596,6 +597,7 @@ public static class PostgreSqlMappers
|
|||||||
SignalIdentifier = position.SignalIdentifier,
|
SignalIdentifier = position.SignalIdentifier,
|
||||||
AccountName = position.AccountName,
|
AccountName = position.AccountName,
|
||||||
UserId = position.User?.Id ?? 0,
|
UserId = position.User?.Id ?? 0,
|
||||||
|
InitiatorIdentifier = position.InitiatorIdentifier,
|
||||||
MoneyManagementJson = position.MoneyManagement != null
|
MoneyManagementJson = position.MoneyManagement != null
|
||||||
? JsonConvert.SerializeObject(position.MoneyManagement)
|
? JsonConvert.SerializeObject(position.MoneyManagement)
|
||||||
: null
|
: null
|
||||||
|
|||||||
@@ -397,6 +397,22 @@ public class PostgreSqlTradingRepository : ITradingRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Position>> GetPositionsByInitiatorIdentifierAsync(Guid initiatorIdentifier)
|
||||||
|
{
|
||||||
|
var positions = await _context.Positions
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(p => p.User)
|
||||||
|
.Include(p => p.OpenTrade)
|
||||||
|
.Include(p => p.StopLossTrade)
|
||||||
|
.Include(p => p.TakeProfit1Trade)
|
||||||
|
.Include(p => p.TakeProfit2Trade)
|
||||||
|
.Where(p => p.InitiatorIdentifier == initiatorIdentifier)
|
||||||
|
.ToListAsync()
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return PostgreSqlMappers.Map(positions);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Position>> GetAllPositionsAsync()
|
public async Task<IEnumerable<Position>> GetAllPositionsAsync()
|
||||||
{
|
{
|
||||||
var positions = await _context.Positions
|
var positions = await _context.Positions
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using Managing.Domain.MoneyManagements;
|
|||||||
using Managing.Domain.Statistics;
|
using Managing.Domain.Statistics;
|
||||||
using Managing.Domain.Trades;
|
using Managing.Domain.Trades;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -300,12 +301,14 @@ namespace Managing.Infrastructure.Messengers.Discord
|
|||||||
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
var exchangeService = (IExchangeService)_services.GetService(typeof(IExchangeService));
|
||||||
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
var accountService = (IAccountService)_services.GetService(typeof(IAccountService));
|
||||||
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
var tradingService = (ITradingService)_services.GetService(typeof(ITradingService));
|
||||||
|
var scopeFactory = (IServiceScopeFactory)_services.GetService(typeof(IServiceScopeFactory));
|
||||||
|
|
||||||
await component.RespondAsync("Alright, let met few seconds to close this position");
|
await component.RespondAsync("Alright, let met few seconds to close this position");
|
||||||
var position = await tradingService.GetPositionByIdentifierAsync(Guid.Parse(parameters[1]));
|
var position = await tradingService.GetPositionByIdentifierAsync(Guid.Parse(parameters[1]));
|
||||||
var command = new ClosePositionCommand(position);
|
var command = new ClosePositionCommand(position);
|
||||||
var result =
|
var result =
|
||||||
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService).Handle(command);
|
await new ClosePositionCommandHandler(exchangeService, accountService, tradingService, scopeFactory)
|
||||||
|
.Handle(command);
|
||||||
var fields = new List<EmbedFieldBuilder>()
|
var fields = new List<EmbedFieldBuilder>()
|
||||||
{
|
{
|
||||||
new EmbedFieldBuilder
|
new EmbedFieldBuilder
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
|||||||
// Use BotClient instead of fetch
|
// Use BotClient instead of fetch
|
||||||
const botClient = new BotClient({}, apiUrl)
|
const botClient = new BotClient({}, apiUrl)
|
||||||
const request: ClosePositionRequest = {
|
const request: ClosePositionRequest = {
|
||||||
identifier: strategyName,
|
identifier: position.initiatorIdentifier,
|
||||||
positionId: position.identifier
|
positionId: position.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4011,6 +4011,7 @@ export interface Position {
|
|||||||
identifier: string;
|
identifier: string;
|
||||||
initiator: PositionInitiator;
|
initiator: PositionInitiator;
|
||||||
user: User;
|
user: User;
|
||||||
|
initiatorIdentifier: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TradeDirection {
|
export enum TradeDirection {
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export interface Position {
|
|||||||
identifier: string;
|
identifier: string;
|
||||||
initiator: PositionInitiator;
|
initiator: PositionInitiator;
|
||||||
user: User;
|
user: User;
|
||||||
|
initiatorIdentifier: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TradeDirection {
|
export enum TradeDirection {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useQuery} from '@tanstack/react-query'
|
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
import useApiUrlStore from '../../app/store/apiStore'
|
import useApiUrlStore from '../../app/store/apiStore'
|
||||||
import {fetchPlatformData} from '../../services/platformService'
|
import {fetchPlatformData} from '../../services/platformService'
|
||||||
|
|
||||||
function PlatformSummary({index}: { index: number }) {
|
function PlatformSummary({index}: { index: number }) {
|
||||||
const {apiUrl} = useApiUrlStore()
|
const {apiUrl} = useApiUrlStore()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -26,11 +27,11 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
|
|
||||||
const formatCurrency = (value: number) => {
|
const formatCurrency = (value: number) => {
|
||||||
if (value >= 1000000) {
|
if (value >= 1000000) {
|
||||||
return `$${(value / 1000000).toFixed(1)}M`
|
return `$${(value / 1000000).toFixed(2)}M`
|
||||||
} else if (value >= 1000) {
|
} else if (value >= 1000) {
|
||||||
return `$${(value / 1000).toFixed(1)}K`
|
return `$${(value / 1000).toFixed(2)}K`
|
||||||
}
|
}
|
||||||
return `$${value.toFixed(0)}`
|
return `$${value.toFixed(2)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatNumber = (value: number) => {
|
const formatNumber = (value: number) => {
|
||||||
@@ -67,6 +68,12 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
return `${percentage >= 0 ? '+' : ''}${percentage.toFixed(1)}%`
|
return `${percentage >= 0 ? '+' : ''}${percentage.toFixed(1)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleForceRefresh = async () => {
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ['platformData', apiUrl]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Show loading spinner only on initial load
|
// Show loading spinner only on initial load
|
||||||
if (isLoading && !data) {
|
if (isLoading && !data) {
|
||||||
return (
|
return (
|
||||||
@@ -366,14 +373,36 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
{/* Data Freshness Indicator */}
|
{/* Data Freshness Indicator */}
|
||||||
<div className="bg-base-200 rounded-lg p-4">
|
<div className="bg-base-200 rounded-lg p-4">
|
||||||
<div className="flex items-center justify-between text-sm text-gray-400">
|
<div className="flex items-center justify-between text-sm text-gray-400">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-4">
|
||||||
<span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span>
|
<div className="flex items-center gap-2">
|
||||||
{isFetching && (
|
<span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span>
|
||||||
<div className="flex items-center gap-1 text-blue-400">
|
{isFetching && (
|
||||||
<div className="loading loading-spinner loading-xs"></div>
|
<div className="flex items-center gap-1 text-blue-400">
|
||||||
<span>Refreshing...</span>
|
<div className="loading loading-spinner loading-xs"></div>
|
||||||
</div>
|
<span>Refreshing...</span>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleForceRefresh}
|
||||||
|
disabled={isFetching}
|
||||||
|
className="btn btn-xs btn-outline btn-primary disabled:loading"
|
||||||
|
title="Force refresh data"
|
||||||
|
>
|
||||||
|
{isFetching ? (
|
||||||
|
<>
|
||||||
|
<span className="loading loading-spinner loading-xs"></span>
|
||||||
|
Refreshing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-3 h-3">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||||
|
</svg>
|
||||||
|
Refresh
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<span>24h snapshot: {platformData?.last24HourSnapshot ? new Date(platformData.last24HourSnapshot).toLocaleString() : 'Unknown'}</span>
|
<span>24h snapshot: {platformData?.last24HourSnapshot ? new Date(platformData.last24HourSnapshot).toLocaleString() : 'Unknown'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user