Fix ROI
This commit is contained in:
@@ -11,7 +11,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;
|
||||||
@@ -455,19 +454,14 @@ public class DataController : ControllerBase
|
|||||||
// Get all strategies for the specified user
|
// Get all strategies for the specified user
|
||||||
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
|
var userStrategies = await _mediator.Send(new GetUserStrategiesCommand(agentName));
|
||||||
|
|
||||||
// Get all positions for all strategies in a single database call to avoid DbContext concurrency issues
|
|
||||||
var strategyIdentifiers = userStrategies.Select(s => s.Identifier).ToList();
|
|
||||||
var allPositions = await _tradingService.GetPositionsByInitiatorIdentifiersAsync(strategyIdentifiers);
|
|
||||||
var positionsByIdentifier = allPositions.GroupBy(p => p.InitiatorIdentifier)
|
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
|
||||||
|
|
||||||
// Get agent balance history for the last 30 days
|
// Get agent balance history for the last 30 days
|
||||||
var startDate = DateTime.UtcNow.AddDays(-30);
|
var startDate = DateTime.UtcNow.AddDays(-30);
|
||||||
var endDate = DateTime.UtcNow;
|
var endDate = DateTime.UtcNow;
|
||||||
var agentBalanceHistory = await _agentService.GetAgentBalances(agentName, startDate, endDate);
|
var agentBalanceHistory = await _agentService.GetAgentBalances(agentName, startDate, endDate);
|
||||||
|
|
||||||
// Convert to detailed view model with additional information
|
// Convert to detailed view model with additional information
|
||||||
var result = userStrategies.Select(strategy => MapStrategyToViewModel(strategy, positionsByIdentifier, agentBalanceHistory))
|
var result = userStrategies
|
||||||
|
.Select(strategy => MapStrategyToViewModelAsync(strategy, agentBalanceHistory))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
@@ -511,78 +505,15 @@ public class DataController : ControllerBase
|
|||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps a trading bot to a strategy view model with detailed statistics using pre-fetched positions
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="strategy">The trading bot to map</param>
|
|
||||||
/// <param name="positionsByIdentifier">Pre-fetched positions grouped by initiator identifier</param>
|
|
||||||
/// <param name="agentBalanceHistory">Agent balance history data</param>
|
|
||||||
/// <returns>A view model with detailed strategy information</returns>
|
|
||||||
private UserStrategyDetailsViewModel MapStrategyToViewModel(Bot strategy,
|
|
||||||
Dictionary<Guid, List<Position>> positionsByIdentifier, AgentBalanceHistory agentBalanceHistory)
|
|
||||||
{
|
|
||||||
// Calculate ROI percentage based on PnL relative to account value
|
|
||||||
decimal pnl = strategy.Pnl;
|
|
||||||
|
|
||||||
// If we had initial investment amount, we could calculate ROI like:
|
|
||||||
decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account
|
|
||||||
decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0;
|
|
||||||
|
|
||||||
// Calculate volume statistics
|
|
||||||
decimal totalVolume = strategy.Volume;
|
|
||||||
decimal volumeLast24h = strategy.Volume;
|
|
||||||
|
|
||||||
// Calculate win/loss statistics
|
|
||||||
(int wins, int losses) = (strategy.TradeWins, strategy.TradeLosses);
|
|
||||||
|
|
||||||
int winRate = wins + losses > 0 ? (wins * 100) / (wins + losses) : 0;
|
|
||||||
// Calculate ROI for last 24h
|
|
||||||
decimal roiLast24h = strategy.Roi;
|
|
||||||
|
|
||||||
// Get positions for this strategy from pre-fetched data
|
|
||||||
var positions = positionsByIdentifier.TryGetValue(strategy.Identifier, out var strategyPositions)
|
|
||||||
? strategyPositions
|
|
||||||
: new List<Position>();
|
|
||||||
|
|
||||||
// Convert agent balance history to wallet balances dictionary
|
|
||||||
var walletBalances = agentBalanceHistory?.AgentBalances?
|
|
||||||
.ToDictionary(b => b.Time, b => b.TotalValue) ?? new Dictionary<DateTime, decimal>();
|
|
||||||
|
|
||||||
return new UserStrategyDetailsViewModel
|
|
||||||
{
|
|
||||||
Name = strategy.Name,
|
|
||||||
State = strategy.Status,
|
|
||||||
PnL = pnl,
|
|
||||||
ROIPercentage = roi,
|
|
||||||
ROILast24H = roiLast24h,
|
|
||||||
Runtime = strategy.StartupTime,
|
|
||||||
WinRate = winRate,
|
|
||||||
TotalVolumeTraded = totalVolume,
|
|
||||||
VolumeLast24H = volumeLast24h,
|
|
||||||
Wins = wins,
|
|
||||||
Losses = losses,
|
|
||||||
Positions = positions,
|
|
||||||
Identifier = strategy.Identifier,
|
|
||||||
WalletBalances = walletBalances,
|
|
||||||
Ticker = strategy.Ticker
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a trading bot to a strategy view model with detailed statistics
|
/// Maps a trading bot to a strategy view model with detailed statistics
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="strategy">The trading bot to map</param>
|
/// <param name="strategy">The trading bot to map</param>
|
||||||
/// <param name="agentBalanceHistory">Agent balance history data</param>
|
/// <param name="agentBalanceHistory">Agent balance history data</param>
|
||||||
/// <returns>A view model with detailed strategy information</returns>
|
/// <returns>A view model with detailed strategy information</returns>
|
||||||
private async Task<UserStrategyDetailsViewModel> MapStrategyToViewModelAsync(Bot strategy, AgentBalanceHistory agentBalanceHistory)
|
private async Task<UserStrategyDetailsViewModel> MapStrategyToViewModelAsync(Bot strategy,
|
||||||
|
AgentBalanceHistory agentBalanceHistory)
|
||||||
{
|
{
|
||||||
// Calculate ROI percentage based on PnL relative to account value
|
|
||||||
decimal pnl = strategy.Pnl;
|
|
||||||
|
|
||||||
// If we had initial investment amount, we could calculate ROI like:
|
|
||||||
decimal initialInvestment = 1000; // Example placeholder, ideally should come from the account
|
|
||||||
decimal roi = pnl != 0 ? (pnl / initialInvestment) * 100 : 0;
|
|
||||||
|
|
||||||
// Calculate volume statistics
|
// Calculate volume statistics
|
||||||
decimal totalVolume = strategy.Volume;
|
decimal totalVolume = strategy.Volume;
|
||||||
decimal volumeLast24h = strategy.Volume;
|
decimal volumeLast24h = strategy.Volume;
|
||||||
@@ -591,8 +522,6 @@ public class DataController : ControllerBase
|
|||||||
(int wins, int losses) = (strategy.TradeWins, strategy.TradeLosses);
|
(int wins, int losses) = (strategy.TradeWins, strategy.TradeLosses);
|
||||||
|
|
||||||
int winRate = wins + losses > 0 ? (wins * 100) / (wins + losses) : 0;
|
int winRate = wins + losses > 0 ? (wins * 100) / (wins + losses) : 0;
|
||||||
// Calculate ROI for last 24h
|
|
||||||
decimal roiLast24h = strategy.Roi;
|
|
||||||
|
|
||||||
// Fetch positions associated with this bot
|
// Fetch positions associated with this bot
|
||||||
var positions = await _tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier);
|
var positions = await _tradingService.GetPositionsByInitiatorIdentifierAsync(strategy.Identifier);
|
||||||
@@ -605,9 +534,8 @@ public class DataController : ControllerBase
|
|||||||
{
|
{
|
||||||
Name = strategy.Name,
|
Name = strategy.Name,
|
||||||
State = strategy.Status,
|
State = strategy.Status,
|
||||||
PnL = pnl,
|
PnL = strategy.Pnl,
|
||||||
ROIPercentage = roi,
|
ROIPercentage = strategy.Roi,
|
||||||
ROILast24H = roiLast24h,
|
|
||||||
Runtime = strategy.StartupTime,
|
Runtime = strategy.StartupTime,
|
||||||
WinRate = winRate,
|
WinRate = winRate,
|
||||||
TotalVolumeTraded = totalVolume,
|
TotalVolumeTraded = totalVolume,
|
||||||
|
|||||||
@@ -28,11 +28,6 @@ namespace Managing.Api.Models.Responses
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal ROIPercentage { get; set; }
|
public decimal ROIPercentage { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return on investment percentage in the last 24 hours
|
|
||||||
/// </summary>
|
|
||||||
public decimal ROILast24H { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Date and time when the strategy was started
|
/// Date and time when the strategy was started
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -14,52 +14,54 @@ public interface IPlatformSummaryGrain : IGrainWithStringKey
|
|||||||
/// Gets the current platform summary data
|
/// Gets the current platform summary data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<PlatformSummaryViewModel> GetPlatformSummaryAsync();
|
Task<PlatformSummaryViewModel> GetPlatformSummaryAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Forces a refresh of all platform data
|
/// Forces a refresh of all platform data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task RefreshDataAsync();
|
Task RefreshDataAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total volume traded across all strategies
|
/// Gets the total volume traded across all strategies
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<decimal> GetTotalVolumeAsync();
|
Task<decimal> GetTotalVolumeAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total PnL across all strategies
|
/// Gets the total PnL across all strategies
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<decimal> GetTotalPnLAsync();
|
Task<decimal> GetTotalPnLAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total open interest across all positions
|
/// Gets the total open interest across all positions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<decimal> GetTotalOpenInterest();
|
Task<decimal> GetTotalOpenInterest();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total number of open positions
|
/// Gets the total number of open positions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<int> GetTotalPositionCountAsync();
|
Task<int> GetTotalPositionCountAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the total platform fees
|
/// Gets the total platform fees
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<decimal> GetTotalFeesAsync();
|
Task<decimal> GetTotalFeesAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the daily volume history for the last 30 days for chart visualization
|
/// Gets the daily volume history for the last 30 days for chart visualization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<List<VolumeHistoryPoint>> GetVolumeHistoryAsync();
|
Task<List<VolumeHistoryPoint>> GetVolumeHistoryAsync();
|
||||||
|
|
||||||
// Event handlers for immediate updates
|
// Event handlers for immediate updates
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the active strategy count
|
/// Updates the active strategy count
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[OneWay]
|
[OneWay]
|
||||||
Task UpdateActiveStrategyCountAsync(int newActiveCount);
|
Task UpdateActiveStrategyCountAsync(int newActiveCount);
|
||||||
|
|
||||||
[OneWay]
|
[OneWay]
|
||||||
Task OnPositionClosedAsync(PositionClosedEvent evt);
|
Task OnPositionClosedAsync(PositionClosedEvent evt);
|
||||||
|
|
||||||
[OneWay]
|
[OneWay]
|
||||||
Task OnTradeExecutedAsync(TradeExecutedEvent evt);
|
Task OnPositionOpenAsync(PositionOpenEvent evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -68,59 +70,36 @@ public interface IPlatformSummaryGrain : IGrainWithStringKey
|
|||||||
[GenerateSerializer]
|
[GenerateSerializer]
|
||||||
public abstract class PlatformMetricsEvent
|
public abstract class PlatformMetricsEvent
|
||||||
{
|
{
|
||||||
[Id(0)]
|
[Id(0)] public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when a position is closed
|
/// Event fired when a position is closed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GenerateSerializer]
|
[GenerateSerializer]
|
||||||
public class PositionClosedEvent : PlatformMetricsEvent
|
public class PositionClosedEvent : PlatformMetricsEvent
|
||||||
{
|
{
|
||||||
[Id(1)]
|
[Id(1)] public Guid PositionIdentifier { get; set; }
|
||||||
public Guid PositionId { get; set; }
|
|
||||||
|
[Id(2)] public Ticker Ticker { get; set; }
|
||||||
[Id(2)]
|
|
||||||
public Ticker Ticker { get; set; }
|
[Id(3)] public decimal RealizedPnL { get; set; }
|
||||||
|
|
||||||
[Id(3)]
|
[Id(4)] public decimal Volume { get; set; }
|
||||||
public decimal RealizedPnL { get; set; }
|
|
||||||
|
|
||||||
[Id(4)]
|
|
||||||
public decimal Volume { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event fired when a trade is executed
|
/// Event fired when a trade is executed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[GenerateSerializer]
|
[GenerateSerializer]
|
||||||
public class TradeExecutedEvent : PlatformMetricsEvent
|
public class PositionOpenEvent : PlatformMetricsEvent
|
||||||
{
|
{
|
||||||
[Id(1)]
|
[Id(1)] public Ticker Ticker { get; set; }
|
||||||
public Guid TradeId { get; set; }
|
|
||||||
|
[Id(2)] public decimal Volume { get; set; }
|
||||||
[Id(2)]
|
|
||||||
public Guid PositionId { get; set; }
|
[Id(3)] public decimal Fee { get; set; }
|
||||||
|
|
||||||
[Id(3)]
|
[Id(4)] public TradeDirection Direction { get; set; }
|
||||||
public Guid StrategyId { get; set; }
|
[Id(5)] public Guid PositionIdentifier { get; set; }
|
||||||
|
}
|
||||||
[Id(4)]
|
|
||||||
public Ticker Ticker { get; set; }
|
|
||||||
|
|
||||||
[Id(5)]
|
|
||||||
public decimal Volume { get; set; }
|
|
||||||
|
|
||||||
[Id(6)]
|
|
||||||
public decimal PnL { get; set; }
|
|
||||||
|
|
||||||
[Id(7)]
|
|
||||||
public decimal Fee { get; set; }
|
|
||||||
|
|
||||||
[Id(8)]
|
|
||||||
public TradeDirection Direction { get; set; }
|
|
||||||
}
|
|
||||||
@@ -9,75 +9,53 @@ namespace Managing.Application.Abstractions.Grains;
|
|||||||
[GenerateSerializer]
|
[GenerateSerializer]
|
||||||
public class PlatformSummaryGrainState
|
public class PlatformSummaryGrainState
|
||||||
{
|
{
|
||||||
[Id(0)]
|
[Id(0)] public DateTime LastUpdated { get; set; }
|
||||||
public DateTime LastUpdated { get; set; }
|
|
||||||
|
|
||||||
[Id(1)]
|
|
||||||
public DateTime LastSnapshot { get; set; }
|
|
||||||
|
|
||||||
[Id(2)]
|
|
||||||
public bool HasPendingChanges { get; set; }
|
|
||||||
|
|
||||||
// Current metrics
|
|
||||||
[Id(3)]
|
|
||||||
public int TotalAgents { get; set; }
|
|
||||||
|
|
||||||
[Id(4)]
|
|
||||||
public int TotalActiveStrategies { get; set; }
|
|
||||||
|
|
||||||
[Id(5)]
|
|
||||||
public decimal TotalPlatformPnL { get; set; }
|
|
||||||
|
|
||||||
[Id(6)]
|
|
||||||
public decimal TotalPlatformVolume { get; set; }
|
|
||||||
|
|
||||||
[Id(7)]
|
|
||||||
public decimal TotalOpenInterest { get; set; }
|
|
||||||
|
|
||||||
[Id(8)]
|
|
||||||
public int TotalPositionCount { get; set; }
|
|
||||||
|
|
||||||
[Id(20)]
|
|
||||||
public decimal TotalPlatformFees { get; set; }
|
|
||||||
|
|
||||||
// 24-hour ago values (for comparison)
|
|
||||||
[Id(9)]
|
|
||||||
public int TotalAgents24hAgo { get; set; }
|
|
||||||
|
|
||||||
[Id(10)]
|
|
||||||
public int TotalActiveStrategies24hAgo { get; set; }
|
|
||||||
|
|
||||||
[Id(11)]
|
|
||||||
public decimal TotalPlatformPnL24hAgo { get; set; }
|
|
||||||
|
|
||||||
[Id(12)]
|
|
||||||
public decimal TotalPlatformVolume24hAgo { get; set; }
|
|
||||||
|
|
||||||
[Id(13)]
|
|
||||||
public decimal TotalOpenInterest24hAgo { get; set; }
|
|
||||||
|
|
||||||
[Id(14)]
|
|
||||||
public int TotalPositionCount24hAgo { get; set; }
|
|
||||||
|
|
||||||
[Id(21)]
|
|
||||||
public decimal TotalPlatformFees24hAgo { get; set; }
|
|
||||||
|
|
||||||
// Historical snapshots
|
|
||||||
[Id(15)]
|
|
||||||
public List<DailySnapshot> DailySnapshots { get; set; } = new();
|
|
||||||
|
|
||||||
// Volume breakdown by asset
|
|
||||||
[Id(16)]
|
|
||||||
public Dictionary<Ticker, decimal> VolumeByAsset { get; set; } = new();
|
|
||||||
|
|
||||||
// Position count breakdown
|
|
||||||
[Id(17)]
|
|
||||||
public Dictionary<Ticker, int> PositionCountByAsset { get; set; } = new();
|
|
||||||
|
|
||||||
[Id(18)]
|
|
||||||
public Dictionary<TradeDirection, int> PositionCountByDirection { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[Id(1)] public DateTime LastSnapshot { get; set; }
|
||||||
|
|
||||||
|
[Id(2)] public bool HasPendingChanges { get; set; }
|
||||||
|
|
||||||
|
// Current metrics
|
||||||
|
[Id(3)] public int TotalAgents { get; set; }
|
||||||
|
|
||||||
|
[Id(4)] public int TotalActiveStrategies { get; set; }
|
||||||
|
|
||||||
|
[Id(5)] public decimal TotalPlatformPnL { get; set; }
|
||||||
|
|
||||||
|
[Id(6)] public decimal TotalPlatformVolume { get; set; }
|
||||||
|
|
||||||
|
[Id(7)] public decimal OpenInterest { get; set; }
|
||||||
|
|
||||||
|
[Id(8)] public int TotalPositionCount { get; set; }
|
||||||
|
|
||||||
|
[Id(20)] public decimal TotalPlatformFees { get; set; }
|
||||||
|
|
||||||
|
// 24-hour ago values (for comparison)
|
||||||
|
[Id(9)] public int TotalAgents24hAgo { get; set; }
|
||||||
|
|
||||||
|
[Id(10)] public int TotalActiveStrategies24hAgo { get; set; }
|
||||||
|
|
||||||
|
[Id(11)] public decimal TotalPlatformPnL24hAgo { get; set; }
|
||||||
|
|
||||||
|
[Id(12)] public decimal TotalPlatformVolume24hAgo { get; set; }
|
||||||
|
|
||||||
|
[Id(13)] public decimal TotalOpenInterest24hAgo { get; set; }
|
||||||
|
|
||||||
|
[Id(14)] public int TotalPositionCount24hAgo { get; set; }
|
||||||
|
|
||||||
|
[Id(21)] public decimal TotalPlatformFees24hAgo { get; set; }
|
||||||
|
|
||||||
|
// Historical snapshots
|
||||||
|
[Id(15)] public List<DailySnapshot> DailySnapshots { get; set; } = new();
|
||||||
|
|
||||||
|
// Volume breakdown by asset
|
||||||
|
[Id(16)] public Dictionary<Ticker, decimal> VolumeByAsset { get; set; } = new();
|
||||||
|
|
||||||
|
// Position count breakdown
|
||||||
|
[Id(17)] public Dictionary<Ticker, int> PositionCountByAsset { get; set; } = new();
|
||||||
|
|
||||||
|
[Id(18)] public Dictionary<TradeDirection, int> PositionCountByDirection { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Daily snapshot of platform metrics
|
/// Daily snapshot of platform metrics
|
||||||
@@ -85,29 +63,19 @@ public class PlatformSummaryGrainState
|
|||||||
[GenerateSerializer]
|
[GenerateSerializer]
|
||||||
public class DailySnapshot
|
public class DailySnapshot
|
||||||
{
|
{
|
||||||
[Id(0)]
|
[Id(0)] public DateTime Date { get; set; }
|
||||||
public DateTime Date { get; set; }
|
|
||||||
|
|
||||||
[Id(1)]
|
|
||||||
public int TotalAgents { get; set; }
|
|
||||||
|
|
||||||
[Id(2)]
|
|
||||||
public int TotalStrategies { get; set; }
|
|
||||||
|
|
||||||
[Id(3)]
|
|
||||||
public decimal TotalVolume { get; set; }
|
|
||||||
|
|
||||||
[Id(4)]
|
|
||||||
public decimal TotalPnL { get; set; }
|
|
||||||
|
|
||||||
[Id(5)]
|
|
||||||
public decimal TotalOpenInterest { get; set; }
|
|
||||||
|
|
||||||
[Id(6)]
|
|
||||||
public int TotalPositionCount { get; set; }
|
|
||||||
|
|
||||||
[Id(7)]
|
|
||||||
public decimal TotalFees { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[Id(1)] public int TotalAgents { get; set; }
|
||||||
|
|
||||||
|
[Id(2)] public int TotalStrategies { get; set; }
|
||||||
|
|
||||||
|
[Id(3)] public decimal TotalVolume { get; set; }
|
||||||
|
|
||||||
|
[Id(4)] public decimal TotalPnL { get; set; }
|
||||||
|
|
||||||
|
[Id(5)] public decimal TotalOpenInterest { get; set; }
|
||||||
|
|
||||||
|
[Id(6)] public int TotalPositionCount { get; set; }
|
||||||
|
|
||||||
|
[Id(7)] public decimal TotalFees { get; set; }
|
||||||
|
}
|
||||||
@@ -9,18 +9,11 @@ namespace Managing.Application.Abstractions.Models;
|
|||||||
[GenerateSerializer]
|
[GenerateSerializer]
|
||||||
public class AgentSummaryUpdateEvent
|
public class AgentSummaryUpdateEvent
|
||||||
{
|
{
|
||||||
[Id(0)]
|
[Id(0)] public Guid BotId { get; set; }
|
||||||
public int UserId { get; set; }
|
|
||||||
|
[Id(1)] public AgentSummaryEventType EventType { get; set; }
|
||||||
[Id(1)]
|
|
||||||
public Guid BotId { get; set; }
|
[Id(2)] public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
[Id(2)]
|
[Id(3)] public string? AdditionalData { get; set; } // Optional additional context
|
||||||
public AgentSummaryEventType EventType { get; set; }
|
}
|
||||||
|
|
||||||
[Id(3)]
|
|
||||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
|
||||||
|
|
||||||
[Id(4)]
|
|
||||||
public string? AdditionalData { get; set; } // Optional additional context
|
|
||||||
}
|
|
||||||
@@ -50,7 +50,6 @@ public class AgentGrainTests
|
|||||||
var botId = _mockState.Object.State.BotIds.First();
|
var botId = _mockState.Object.State.BotIds.First();
|
||||||
var updateEvent = new AgentSummaryUpdateEvent
|
var updateEvent = new AgentSummaryUpdateEvent
|
||||||
{
|
{
|
||||||
UserId = 1,
|
|
||||||
BotId = botId,
|
BotId = botId,
|
||||||
EventType = AgentSummaryEventType.PositionOpened,
|
EventType = AgentSummaryEventType.PositionOpened,
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
@@ -76,7 +75,6 @@ public class AgentGrainTests
|
|||||||
var agentGrain = CreateAgentGrain();
|
var agentGrain = CreateAgentGrain();
|
||||||
var updateEvent = new AgentSummaryUpdateEvent
|
var updateEvent = new AgentSummaryUpdateEvent
|
||||||
{
|
{
|
||||||
UserId = 1,
|
|
||||||
BotId = Guid.NewGuid(), // Different bot ID
|
BotId = Guid.NewGuid(), // Different bot ID
|
||||||
EventType = AgentSummaryEventType.PositionOpened,
|
EventType = AgentSummaryEventType.PositionOpened,
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
|
|||||||
@@ -350,17 +350,20 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var agentGrain = GrainFactory.GetGrain<IAgentGrain>(_state.State.User.Id);
|
var agentGrain = GrainFactory.GetGrain<IAgentGrain>(_state.State.User.Id);
|
||||||
var balanceCheckResult = await agentGrain.CheckAndEnsureEthBalanceAsync(_state.State.Identifier, _tradingBot.Account.Name);
|
var balanceCheckResult =
|
||||||
|
await agentGrain.CheckAndEnsureEthBalanceAsync(_state.State.Identifier, _tradingBot.Account.Name);
|
||||||
|
|
||||||
if (!balanceCheckResult.IsSuccessful)
|
if (!balanceCheckResult.IsSuccessful)
|
||||||
{
|
{
|
||||||
// Log the specific reason for the failure
|
// Log the specific reason for the failure
|
||||||
await _tradingBot.LogWarning($"Balance check failed: {balanceCheckResult.Message} (Reason: {balanceCheckResult.FailureReason})");
|
await _tradingBot.LogWarning(
|
||||||
|
$"Balance check failed: {balanceCheckResult.Message} (Reason: {balanceCheckResult.FailureReason})");
|
||||||
|
|
||||||
// Check if the bot should stop due to this failure
|
// Check if the bot should stop due to this failure
|
||||||
if (balanceCheckResult.ShouldStopBot)
|
if (balanceCheckResult.ShouldStopBot)
|
||||||
{
|
{
|
||||||
await _tradingBot.LogWarning($"Stopping bot due to balance check failure: {balanceCheckResult.Message}");
|
await _tradingBot.LogWarning(
|
||||||
|
$"Stopping bot due to balance check failure: {balanceCheckResult.Message}");
|
||||||
await StopAsync();
|
await StopAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -371,10 +374,6 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
await _tradingBot.LogInformation($"Balance check successful: {balanceCheckResult.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,9 +99,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Notify AgentGrain about bot startup
|
// Notify AgentGrain about bot startup
|
||||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.BotStarted,
|
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.BotStarted,
|
||||||
$"Bot: {Config.Name}, Ticker: {Config.Ticker}");
|
$"Bot: {Config.Name}, Ticker: {Config.Ticker}");
|
||||||
|
|
||||||
// Notify platform summary about active strategy count change
|
|
||||||
await NotifyPlatformSummaryAboutStrategyCount();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BotStatus.Running:
|
case BotStatus.Running:
|
||||||
@@ -390,11 +387,9 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
brokerPositions = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(_scopeFactory,
|
brokerPositions = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Position>>(
|
||||||
async exchangeService =>
|
_scopeFactory,
|
||||||
{
|
async exchangeService => { return [.. await exchangeService.GetBrokerPositions(Account)]; });
|
||||||
return [.. await exchangeService.GetBrokerPositions(Account)];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -421,7 +416,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
if (!internalPosition.Status.Equals(PositionStatus.New))
|
if (!internalPosition.Status.Equals(PositionStatus.New))
|
||||||
{
|
{
|
||||||
internalPosition.Status = PositionStatus.Filled;
|
internalPosition.Status = PositionStatus.Filled;
|
||||||
|
|
||||||
// Update Open trade status when position becomes Filled
|
// Update Open trade status when position becomes Filled
|
||||||
if (internalPosition.Open != null)
|
if (internalPosition.Open != null)
|
||||||
{
|
{
|
||||||
@@ -434,8 +429,11 @@ public class TradingBotBase : ITradingBot
|
|||||||
if (internalPosition.Status == PositionStatus.New)
|
if (internalPosition.Status == PositionStatus.New)
|
||||||
{
|
{
|
||||||
var orders = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Trade>>(_scopeFactory,
|
var orders = await ServiceScopeHelpers.WithScopedService<IExchangeService, List<Trade>>(_scopeFactory,
|
||||||
async exchangeService => { return [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)]; });
|
async exchangeService =>
|
||||||
|
{
|
||||||
|
return [.. await exchangeService.GetOpenOrders(Account, Config.Ticker)];
|
||||||
|
});
|
||||||
|
|
||||||
if (orders.Any())
|
if (orders.Any())
|
||||||
{
|
{
|
||||||
var ordersCount = orders.Count();
|
var ordersCount = orders.Count();
|
||||||
@@ -480,25 +478,27 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Check if position is already open on broker with 2 orders
|
// Check if position is already open on broker with 2 orders
|
||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"🔍 **Checking Broker Position**\nPosition has exactly `{orders.Count()}` open orders\nChecking if position is already open on broker...");
|
$"🔍 **Checking Broker Position**\nPosition has exactly `{orders.Count()}` open orders\nChecking if position is already open on broker...");
|
||||||
|
|
||||||
Position brokerPosition = null;
|
Position brokerPosition = null;
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
{
|
async exchangeService =>
|
||||||
var brokerPositions = await exchangeService.GetBrokerPositions(Account);
|
{
|
||||||
brokerPosition = brokerPositions.FirstOrDefault(p => p.Ticker == Config.Ticker);
|
var brokerPositions = await exchangeService.GetBrokerPositions(Account);
|
||||||
});
|
brokerPosition = brokerPositions.FirstOrDefault(p => p.Ticker == Config.Ticker);
|
||||||
|
});
|
||||||
|
|
||||||
if (brokerPosition != null)
|
if (brokerPosition != null)
|
||||||
{
|
{
|
||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"✅ **Position Found on Broker**\nPosition is already open on broker\nUpdating position status to Filled");
|
$"✅ **Position Found on Broker**\nPosition is already open on broker\nUpdating position status to Filled");
|
||||||
|
|
||||||
UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);
|
UpdatePositionPnl(positionForSignal.Identifier, brokerPosition.ProfitAndLoss.Realized);
|
||||||
await SetPositionStatus(signal.Identifier, PositionStatus.Filled);
|
await SetPositionStatus(signal.Identifier, PositionStatus.Filled);
|
||||||
|
|
||||||
// Notify platform summary about the executed trade
|
// Notify platform summary about the executed trade
|
||||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionOpened,
|
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionOpened,
|
||||||
$"Position found on broker with 2 orders: {internalPosition.Identifier}", internalPosition);
|
$"Position found on broker with 2 orders: {internalPosition.Identifier}",
|
||||||
|
internalPosition);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -519,11 +519,13 @@ public class TradingBotBase : ITradingBot
|
|||||||
await HandleClosedPosition(positionForSignal);
|
await HandleClosedPosition(positionForSignal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (internalPosition.Status == PositionStatus.Finished || internalPosition.Status == PositionStatus.Flipped)
|
else if (internalPosition.Status == PositionStatus.Finished ||
|
||||||
|
internalPosition.Status == PositionStatus.Flipped)
|
||||||
{
|
{
|
||||||
await HandleClosedPosition(positionForSignal);
|
await HandleClosedPosition(positionForSignal);
|
||||||
}
|
}
|
||||||
else if (internalPosition.Status == PositionStatus.Filled || internalPosition.Status == PositionStatus.PartiallyFilled)
|
else if (internalPosition.Status == PositionStatus.Filled ||
|
||||||
|
internalPosition.Status == PositionStatus.PartiallyFilled)
|
||||||
{
|
{
|
||||||
Candle lastCandle = null;
|
Candle lastCandle = null;
|
||||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory, async exchangeService =>
|
||||||
@@ -619,7 +621,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (internalPosition.Status == PositionStatus.Rejected || internalPosition.Status == PositionStatus.Canceled)
|
else if (internalPosition.Status == PositionStatus.Rejected ||
|
||||||
|
internalPosition.Status == PositionStatus.Canceled)
|
||||||
{
|
{
|
||||||
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
|
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
|
||||||
if (signal.Status == SignalStatus.PositionOpen)
|
if (signal.Status == SignalStatus.PositionOpen)
|
||||||
@@ -664,18 +667,18 @@ public class TradingBotBase : ITradingBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Config.IsForBacktest){
|
if (!Config.IsForBacktest)
|
||||||
// Update position in database with broker data
|
{
|
||||||
|
// Update position in database with broker data
|
||||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
||||||
{
|
{
|
||||||
// Update the internal position with broker data
|
// Update the internal position with broker data
|
||||||
internalPosition.Status = PositionStatus.Filled;
|
internalPosition.Status = PositionStatus.Filled;
|
||||||
internalPosition.ProfitAndLoss = internalPosition.ProfitAndLoss;
|
internalPosition.ProfitAndLoss = internalPosition.ProfitAndLoss;
|
||||||
|
|
||||||
// Save updated position to database
|
// Save updated position to database
|
||||||
await tradingService.UpdatePositionAsync(internalPosition);
|
await tradingService.UpdatePositionAsync(internalPosition);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -807,9 +810,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionOpened,
|
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionOpened,
|
||||||
$"Signal: {signal.Identifier}", position);
|
$"Signal: {signal.Identifier}", position);
|
||||||
|
|
||||||
// Publish TradeExecutedEvent for the opening trade (this handles both position opening and trade execution)
|
|
||||||
await PublishTradeExecutedEventAsync(position.Open, position, signal.Identifier, 0);
|
|
||||||
|
|
||||||
Logger.LogInformation($"Position requested");
|
Logger.LogInformation($"Position requested");
|
||||||
return position; // Return the created position without adding to list
|
return position; // Return the created position without adding to list
|
||||||
}
|
}
|
||||||
@@ -828,10 +828,11 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Handle insufficient funds errors with user-friendly messaging
|
// Handle insufficient funds errors with user-friendly messaging
|
||||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
await LogWarning(ex.UserMessage);
|
await LogWarning(ex.UserMessage);
|
||||||
|
|
||||||
// Log the technical details for debugging
|
// Log the technical details for debugging
|
||||||
Logger.LogError(ex, "Insufficient funds error for signal {SignalId}: {ErrorMessage}", signal.Identifier, ex.Message);
|
Logger.LogError(ex, "Insufficient funds error for signal {SignalId}: {ErrorMessage}", signal.Identifier,
|
||||||
|
ex.Message);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -1066,7 +1067,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
if (currentCandle != null)
|
if (currentCandle != null)
|
||||||
{
|
{
|
||||||
List<Candle> recentCandles = null;
|
List<Candle> recentCandles = null;
|
||||||
|
|
||||||
if (Config.IsForBacktest)
|
if (Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
recentCandles = LastCandle != null ? new List<Candle>() { LastCandle } : new List<Candle>();
|
recentCandles = LastCandle != null ? new List<Candle>() { LastCandle } : new List<Candle>();
|
||||||
@@ -1076,16 +1077,18 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Use CandleStoreGrain to get recent candles instead of calling exchange service directly
|
// Use CandleStoreGrain to get recent candles instead of calling exchange service directly
|
||||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
||||||
{
|
{
|
||||||
var grainKey = CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe);
|
var grainKey =
|
||||||
|
CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe);
|
||||||
var grain = grainFactory.GetGrain<ICandleStoreGrain>(grainKey);
|
var grain = grainFactory.GetGrain<ICandleStoreGrain>(grainKey);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
recentCandles = await grain.GetLastCandle(5);
|
recentCandles = await grain.GetLastCandle(5);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error retrieving recent candles from CandleStoreGrain for {GrainKey}", grainKey);
|
Logger.LogError(ex, "Error retrieving recent candles from CandleStoreGrain for {GrainKey}",
|
||||||
|
grainKey);
|
||||||
recentCandles = new List<Candle>();
|
recentCandles = new List<Candle>();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1218,10 +1221,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
// Update position in database with all trade changes
|
// Update position in database with all trade changes
|
||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory, async tradingService =>
|
await ServiceScopeHelpers.WithScopedService<ITradingService>(_scopeFactory,
|
||||||
{
|
async tradingService => { await tradingService.UpdatePositionAsync(position); });
|
||||||
await tradingService.UpdatePositionAsync(position);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the last position closing time for cooldown period tracking
|
// Update the last position closing time for cooldown period tracking
|
||||||
@@ -1235,7 +1236,8 @@ public class TradingBotBase : ITradingBot
|
|||||||
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
|
Config.BotTradingBalance += position.ProfitAndLoss.Realized;
|
||||||
|
|
||||||
Logger.LogInformation(
|
Logger.LogInformation(
|
||||||
string.Format("💰 **Balance Updated**\nNew bot trading balance: `${0:F2}`", Config.BotTradingBalance));
|
string.Format("💰 **Balance Updated**\nNew bot trading balance: `${0:F2}`",
|
||||||
|
Config.BotTradingBalance));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify AgentGrain about position closing
|
// Notify AgentGrain about position closing
|
||||||
@@ -1244,13 +1246,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
: "PnL: Unknown";
|
: "PnL: Unknown";
|
||||||
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionClosed,
|
await NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType.PositionClosed,
|
||||||
string.Format("Signal: {0}, {1}", position.SignalIdentifier, pnlInfo), position);
|
string.Format("Signal: {0}, {1}", position.SignalIdentifier, pnlInfo), position);
|
||||||
|
|
||||||
// Publish TradeExecutedEvent for the closing trade
|
|
||||||
var closingTrade = GetClosingTrade(position);
|
|
||||||
if (closingTrade != null)
|
|
||||||
{
|
|
||||||
await PublishTradeExecutedEventAsync(closingTrade, position, position.SignalIdentifier, position.ProfitAndLoss?.Realized ?? 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1260,8 +1255,11 @@ public class TradingBotBase : ITradingBot
|
|||||||
if (!Config.IsForBacktest)
|
if (!Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
|
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
|
||||||
messengerService => { messengerService.SendClosingPosition(position);
|
messengerService =>
|
||||||
return Task.CompletedTask; });
|
{
|
||||||
|
messengerService.SendClosingPosition(position);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await CancelAllOrders();
|
await CancelAllOrders();
|
||||||
@@ -1330,7 +1328,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
Positions.Values.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
Positions.Values.First(p => p.SignalIdentifier == signalIdentifier).Status = positionStatus;
|
||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"📊 **Position Status Change**\nPosition: `{signalIdentifier}`\nStatus: `{position.Status}` → `{positionStatus}`");
|
$"📊 **Position Status Change**\nPosition: `{signalIdentifier}`\nStatus: `{position.Status}` → `{positionStatus}`");
|
||||||
|
|
||||||
// Update Open trade status when position becomes Filled
|
// Update Open trade status when position becomes Filled
|
||||||
if (positionStatus == PositionStatus.Filled && position.Open != null)
|
if (positionStatus == PositionStatus.Filled && position.Open != null)
|
||||||
{
|
{
|
||||||
@@ -1444,7 +1442,7 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
// Network Fee: $0.50 for opening position only
|
// Network Fee: $0.50 for opening position only
|
||||||
// Closing is handled by oracle, so no network fee for closing
|
// Closing is handled by oracle, so no network fee for closing
|
||||||
var networkFeeForOpening = 0.50m;
|
var networkFeeForOpening = 0.15m;
|
||||||
fees += networkFeeForOpening;
|
fees += networkFeeForOpening;
|
||||||
|
|
||||||
return fees;
|
return fees;
|
||||||
@@ -1456,9 +1454,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
Config.IsForWatchingOnly = !Config.IsForWatchingOnly;
|
Config.IsForWatchingOnly = !Config.IsForWatchingOnly;
|
||||||
await LogInformation(
|
await LogInformation(
|
||||||
$"🔄 **Watch Mode Toggle**\nBot: `{Config.Name}`\nWatch Only: `{(Config.IsForWatchingOnly ? "ON" : "OFF")}`");
|
$"🔄 **Watch Mode Toggle**\nBot: `{Config.Name}`\nWatch Only: `{(Config.IsForWatchingOnly ? "ON" : "OFF")}`");
|
||||||
|
|
||||||
// Notify platform summary about strategy count change
|
|
||||||
await NotifyPlatformSummaryAboutStrategyCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1467,9 +1462,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
public async Task StopBot()
|
public async Task StopBot()
|
||||||
{
|
{
|
||||||
await LogInformation($"🛑 **Bot Stopped**\nBot: `{Config.Name}`\nTicker: `{Config.Ticker}`");
|
await LogInformation($"🛑 **Bot Stopped**\nBot: `{Config.Name}`\nTicker: `{Config.Ticker}`");
|
||||||
|
|
||||||
// Notify platform summary about strategy count change
|
|
||||||
await NotifyPlatformSummaryAboutStrategyCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogInformation(string message)
|
public async Task LogInformation(string message)
|
||||||
@@ -1953,46 +1945,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
return isInCooldown;
|
return isInCooldown;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Publishes a TradeExecutedEvent to the platform summary grain
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trade">The trade that was executed</param>
|
|
||||||
/// <param name="position">The position this trade belongs to</param>
|
|
||||||
/// <param name="signalIdentifier">The signal identifier</param>
|
|
||||||
/// <param name="pnl">The PnL for this trade</param>
|
|
||||||
private async Task PublishTradeExecutedEventAsync(Trade trade, Position position, string signalIdentifier, decimal pnl)
|
|
||||||
{
|
|
||||||
if (Config.IsForBacktest)
|
|
||||||
{
|
|
||||||
return; // Skip notifications for backtest
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
|
||||||
{
|
|
||||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
|
||||||
var tradeExecutedEvent = new TradeExecutedEvent
|
|
||||||
{
|
|
||||||
TradeId = Guid.NewGuid(), // Generate new ID for the event
|
|
||||||
PositionId = position.Identifier,
|
|
||||||
StrategyId = position.InitiatorIdentifier,
|
|
||||||
Ticker = position.Ticker,
|
|
||||||
Volume = trade.Price * trade.Quantity * trade.Leverage,
|
|
||||||
PnL = pnl,
|
|
||||||
Fee = trade.Fee,
|
|
||||||
Direction = trade.Direction
|
|
||||||
};
|
|
||||||
await platformGrain.OnTradeExecutedAsync(tradeExecutedEvent);
|
|
||||||
Logger.LogDebug("Published TradeExecutedEvent for trade {TradeId} in position {PositionId}", tradeExecutedEvent.TradeId, position.Identifier);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Failed to publish TradeExecutedEvent for position {PositionId}", position.Identifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the trade that was used to close the position
|
/// Gets the trade that was used to close the position
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2013,12 +1965,14 @@ public class TradingBotBase : ITradingBot
|
|||||||
{
|
{
|
||||||
return position.TakeProfit2;
|
return position.TakeProfit2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no specific closing trade is found, create a synthetic one based on the position
|
// If no specific closing trade is found, create a synthetic one based on the position
|
||||||
// This handles cases where the position was closed manually or by the exchange
|
// This handles cases where the position was closed manually or by the exchange
|
||||||
if (position.ProfitAndLoss?.Realized != null)
|
if (position.ProfitAndLoss?.Realized != null)
|
||||||
{
|
{
|
||||||
var closeDirection = position.OriginDirection == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long;
|
var closeDirection = position.OriginDirection == TradeDirection.Long
|
||||||
|
? TradeDirection.Short
|
||||||
|
: TradeDirection.Long;
|
||||||
return new Trade(
|
return new Trade(
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
closeDirection,
|
closeDirection,
|
||||||
@@ -2032,49 +1986,18 @@ public class TradingBotBase : ITradingBot
|
|||||||
"Position closed"
|
"Position closed"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Notifies the platform summary grain about active strategy count changes
|
|
||||||
/// </summary>
|
|
||||||
private async Task NotifyPlatformSummaryAboutStrategyCount()
|
|
||||||
{
|
|
||||||
if (Config.IsForBacktest)
|
|
||||||
{
|
|
||||||
return; // Skip notifications for backtest
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await ServiceScopeHelpers.WithScopedService<IGrainFactory>(_scopeFactory, async grainFactory =>
|
|
||||||
{
|
|
||||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
|
||||||
|
|
||||||
// Get current active strategy count from the platform
|
|
||||||
var currentSummary = await platformGrain.GetPlatformSummaryAsync();
|
|
||||||
var currentActiveCount = currentSummary.TotalActiveStrategies;
|
|
||||||
|
|
||||||
// Update the count (this will trigger a refresh if needed)
|
|
||||||
await platformGrain.UpdateActiveStrategyCountAsync(currentActiveCount);
|
|
||||||
|
|
||||||
Logger.LogDebug("Notified platform summary about strategy count: {Count}", currentActiveCount);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Failed to notify platform summary about strategy count");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Notifies both AgentGrain and PlatformSummaryGrain about bot events
|
/// Notifies both AgentGrain and PlatformSummaryGrain about bot events
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="eventType">The type of event (e.g., PositionOpened, PositionClosed)</param>
|
/// <param name="eventType">The type of event (e.g., PositionOpened, PositionClosed)</param>
|
||||||
/// <param name="additionalData">Optional additional context data</param>
|
/// <param name="additionalData">Optional additional context data</param>
|
||||||
/// <param name="position">Optional position data for platform summary events</param>
|
/// <param name="position">Optional position data for platform summary events</param>
|
||||||
private async Task NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType eventType, string additionalData = null, Position position = null)
|
private async Task NotifyAgentAndPlatformGrainAsync(AgentSummaryEventType eventType, string additionalData = null,
|
||||||
|
Position position = null)
|
||||||
{
|
{
|
||||||
if (Config.IsForBacktest)
|
if (Config.IsForBacktest)
|
||||||
{
|
{
|
||||||
@@ -2092,7 +2015,6 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
var updateEvent = new AgentSummaryUpdateEvent
|
var updateEvent = new AgentSummaryUpdateEvent
|
||||||
{
|
{
|
||||||
UserId = Account.User.Id,
|
|
||||||
BotId = Identifier,
|
BotId = Identifier,
|
||||||
EventType = eventType,
|
EventType = eventType,
|
||||||
Timestamp = DateTime.UtcNow,
|
Timestamp = DateTime.UtcNow,
|
||||||
@@ -2105,24 +2027,35 @@ public class TradingBotBase : ITradingBot
|
|||||||
|
|
||||||
// Notify PlatformSummaryGrain (platform-wide metrics)
|
// Notify PlatformSummaryGrain (platform-wide metrics)
|
||||||
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
var platformGrain = grainFactory.GetGrain<IPlatformSummaryGrain>("platform-summary");
|
||||||
|
|
||||||
switch (eventType)
|
switch (eventType)
|
||||||
{
|
{
|
||||||
case AgentSummaryEventType.PositionOpened when position != null:
|
case AgentSummaryEventType.PositionOpened when position != null:
|
||||||
// Position opening is now handled by TradeExecutedEvent in PublishTradeExecutedEventAsync
|
// Position opening is now handled by TradeExecutedEvent in PublishTradeExecutedEventAsync
|
||||||
Logger.LogDebug("Position opened notification sent via TradeExecutedEvent for position {PositionId}", position.Identifier);
|
Logger.LogDebug(
|
||||||
|
"Position opened notification sent via TradeExecutedEvent for position {PositionId}",
|
||||||
|
position.Identifier);
|
||||||
|
var positionOpenEvent = new PositionOpenEvent
|
||||||
|
{
|
||||||
|
PositionIdentifier = position.Identifier,
|
||||||
|
Ticker = position.Ticker,
|
||||||
|
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage,
|
||||||
|
Fee = position.Open.Fee
|
||||||
|
};
|
||||||
|
await platformGrain.OnPositionOpenAsync(positionOpenEvent);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AgentSummaryEventType.PositionClosed when position != null:
|
case AgentSummaryEventType.PositionClosed when position != null:
|
||||||
var positionClosedEvent = new PositionClosedEvent
|
var positionClosedEvent = new PositionClosedEvent
|
||||||
{
|
{
|
||||||
PositionId = position.Identifier,
|
PositionIdentifier = position.Identifier,
|
||||||
Ticker = position.Ticker,
|
Ticker = position.Ticker,
|
||||||
RealizedPnL = position.ProfitAndLoss?.Realized ?? 0,
|
RealizedPnL = position.ProfitAndLoss?.Realized ?? 0,
|
||||||
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage,
|
Volume = position.Open.Price * position.Open.Quantity * position.Open.Leverage,
|
||||||
};
|
};
|
||||||
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
await platformGrain.OnPositionClosedAsync(positionClosedEvent);
|
||||||
Logger.LogDebug("Sent platform position closed notification for position {PositionId}", position.Identifier);
|
Logger.LogDebug("Sent platform position closed notification for position {PositionId}",
|
||||||
|
position.Identifier);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2132,5 +2065,4 @@ public class TradingBotBase : ITradingBot
|
|||||||
Logger.LogError(ex, "Failed to send notifications: {EventType} for bot {BotId}", eventType, Identifier);
|
Logger.LogError(ex, "Failed to send notifications: {EventType} for bot {BotId}", eventType, Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
|
|
||||||
// Set up reminder for daily snapshots using precise timing
|
// Set up reminder for daily snapshots using precise timing
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
// Daily reminder - runs at midnight (00:00 UTC)
|
// Daily reminder - runs at midnight (00:00 UTC)
|
||||||
var nextDailyTime = CandleHelpers.GetNextExpectedCandleTime(Timeframe.OneDay, now);
|
var nextDailyTime = CandleHelpers.GetNextExpectedCandleTime(Timeframe.OneDay, now);
|
||||||
var timeUntilNextDay = nextDailyTime - now;
|
var timeUntilNextDay = nextDailyTime - now;
|
||||||
@@ -98,7 +98,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
_state.State.TotalActiveStrategies = totalActiveStrategies;
|
_state.State.TotalActiveStrategies = totalActiveStrategies;
|
||||||
_state.State.TotalPlatformVolume = totalVolume;
|
_state.State.TotalPlatformVolume = totalVolume;
|
||||||
_state.State.TotalPlatformPnL = totalPnL;
|
_state.State.TotalPlatformPnL = totalPnL;
|
||||||
_state.State.TotalOpenInterest = totalOpenInterest;
|
_state.State.OpenInterest = totalOpenInterest;
|
||||||
_state.State.TotalPositionCount = totalPositionCount;
|
_state.State.TotalPositionCount = totalPositionCount;
|
||||||
_state.State.LastUpdated = DateTime.UtcNow;
|
_state.State.LastUpdated = DateTime.UtcNow;
|
||||||
_state.State.HasPendingChanges = false;
|
_state.State.HasPendingChanges = false;
|
||||||
@@ -225,7 +225,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
|
|
||||||
public Task<decimal> GetTotalOpenInterest()
|
public Task<decimal> GetTotalOpenInterest()
|
||||||
{
|
{
|
||||||
return Task.FromResult(_state.State.TotalOpenInterest);
|
return Task.FromResult(_state.State.OpenInterest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<int> GetTotalPositionCountAsync()
|
public Task<int> GetTotalPositionCountAsync()
|
||||||
@@ -282,21 +282,15 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Position closed: {PositionId} for {Ticker} with PnL: {PnL}",
|
_logger.LogInformation("Position closed: {PositionId} for {Ticker} with PnL: {PnL}",
|
||||||
evt.PositionId, evt.Ticker, evt.RealizedPnL);
|
evt.PositionIdentifier, evt.Ticker, evt.RealizedPnL);
|
||||||
|
|
||||||
// Validate event data
|
// Validate event data
|
||||||
if (evt == null || evt.PositionId == Guid.Empty || evt.Ticker == Ticker.Unknown)
|
if (evt == null || evt.PositionIdentifier == Guid.Empty || evt.Ticker == Ticker.Unknown)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Invalid PositionClosedEvent received: {Event}", evt);
|
_logger.LogWarning("Invalid PositionClosedEvent received: {Event}", evt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure position count doesn't go negative
|
|
||||||
if (_state.State.TotalPositionCount > 0)
|
|
||||||
{
|
|
||||||
_state.State.TotalPositionCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
_state.State.TotalPlatformVolume += evt.Volume;
|
_state.State.TotalPlatformVolume += evt.Volume;
|
||||||
_state.State.TotalPlatformPnL += evt.RealizedPnL;
|
_state.State.TotalPlatformPnL += evt.RealizedPnL;
|
||||||
|
|
||||||
@@ -308,9 +302,9 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_state.State.VolumeByAsset[asset] += evt.Volume;
|
_state.State.VolumeByAsset[asset] += evt.Volume;
|
||||||
|
|
||||||
// Update open interest (subtract the closed position's volume)
|
// Update open interest (subtract the closed position's volume)
|
||||||
_state.State.TotalOpenInterest = Math.Max(0, _state.State.TotalOpenInterest - evt.Volume);
|
_state.State.OpenInterest = Math.Max(0, _state.State.OpenInterest - evt.Volume);
|
||||||
|
|
||||||
_state.State.HasPendingChanges = true;
|
_state.State.HasPendingChanges = true;
|
||||||
await _state.WriteStateAsync();
|
await _state.WriteStateAsync();
|
||||||
@@ -321,17 +315,17 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnTradeExecutedAsync(TradeExecutedEvent evt)
|
public async Task OnPositionOpenAsync(PositionOpenEvent evt)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Trade executed: {TradeId} for {Ticker} with volume: {Volume}",
|
_logger.LogInformation("Position opened: {PositionIdentifier} for {Ticker} with volume: {Volume}",
|
||||||
evt.TradeId, evt.Ticker, evt.Volume);
|
evt.PositionIdentifier, evt.Ticker, evt.Volume);
|
||||||
|
|
||||||
// Validate event data
|
// Validate event data
|
||||||
if (evt == null || evt.Ticker == Ticker.Unknown || evt.Volume <= 0)
|
if (evt == null || evt.Ticker == Ticker.Unknown || evt.Volume <= 0)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Invalid TradeExecutedEvent received: {Event}", evt);
|
_logger.LogWarning("Invalid PositionOpenEvent received: {Event}", evt);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,18 +338,20 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
{
|
{
|
||||||
_state.State.VolumeByAsset[asset] = 0;
|
_state.State.VolumeByAsset[asset] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_state.State.VolumeByAsset[asset] += evt.Volume;
|
_state.State.VolumeByAsset[asset] += evt.Volume;
|
||||||
|
|
||||||
// Update open interest and position count
|
// Update open interest and position count
|
||||||
// Since this is called only when position is fully open on broker, we always increase counts
|
// Since this is called only when position is fully open on broker, we always increase counts
|
||||||
_state.State.TotalPositionCount++;
|
_state.State.TotalPositionCount++;
|
||||||
_state.State.TotalOpenInterest += evt.Volume;
|
_state.State.OpenInterest += evt.Volume;
|
||||||
|
|
||||||
// Update position count by asset
|
// Update position count by asset
|
||||||
if (!_state.State.PositionCountByAsset.ContainsKey(asset))
|
if (!_state.State.PositionCountByAsset.ContainsKey(asset))
|
||||||
{
|
{
|
||||||
_state.State.PositionCountByAsset[asset] = 0;
|
_state.State.PositionCountByAsset[asset] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_state.State.PositionCountByAsset[asset]++;
|
_state.State.PositionCountByAsset[asset]++;
|
||||||
|
|
||||||
// Update position count by direction
|
// Update position count by direction
|
||||||
@@ -363,13 +359,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
{
|
{
|
||||||
_state.State.PositionCountByDirection[evt.Direction] = 0;
|
_state.State.PositionCountByDirection[evt.Direction] = 0;
|
||||||
}
|
}
|
||||||
_state.State.PositionCountByDirection[evt.Direction]++;
|
|
||||||
|
|
||||||
// Update PnL if provided
|
_state.State.PositionCountByDirection[evt.Direction]++;
|
||||||
if (evt.PnL != 0)
|
|
||||||
{
|
|
||||||
_state.State.TotalPlatformPnL += evt.PnL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update fees if provided
|
// Update fees if provided
|
||||||
if (evt.Fee > 0)
|
if (evt.Fee > 0)
|
||||||
@@ -409,7 +400,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
_state.State.TotalActiveStrategies24hAgo = _state.State.TotalActiveStrategies;
|
_state.State.TotalActiveStrategies24hAgo = _state.State.TotalActiveStrategies;
|
||||||
_state.State.TotalPlatformPnL24hAgo = _state.State.TotalPlatformPnL;
|
_state.State.TotalPlatformPnL24hAgo = _state.State.TotalPlatformPnL;
|
||||||
_state.State.TotalPlatformVolume24hAgo = _state.State.TotalPlatformVolume;
|
_state.State.TotalPlatformVolume24hAgo = _state.State.TotalPlatformVolume;
|
||||||
_state.State.TotalOpenInterest24hAgo = _state.State.TotalOpenInterest;
|
_state.State.TotalOpenInterest24hAgo = _state.State.OpenInterest;
|
||||||
_state.State.TotalPositionCount24hAgo = _state.State.TotalPositionCount;
|
_state.State.TotalPositionCount24hAgo = _state.State.TotalPositionCount;
|
||||||
_state.State.TotalPlatformFees24hAgo = _state.State.TotalPlatformFees;
|
_state.State.TotalPlatformFees24hAgo = _state.State.TotalPlatformFees;
|
||||||
|
|
||||||
@@ -421,7 +412,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
TotalStrategies = _state.State.TotalActiveStrategies,
|
TotalStrategies = _state.State.TotalActiveStrategies,
|
||||||
TotalVolume = _state.State.TotalPlatformVolume,
|
TotalVolume = _state.State.TotalPlatformVolume,
|
||||||
TotalPnL = _state.State.TotalPlatformPnL,
|
TotalPnL = _state.State.TotalPlatformPnL,
|
||||||
TotalOpenInterest = _state.State.TotalOpenInterest,
|
TotalOpenInterest = _state.State.OpenInterest,
|
||||||
TotalPositionCount = _state.State.TotalPositionCount,
|
TotalPositionCount = _state.State.TotalPositionCount,
|
||||||
TotalFees = _state.State.TotalPlatformFees
|
TotalFees = _state.State.TotalPlatformFees
|
||||||
};
|
};
|
||||||
@@ -462,7 +453,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
TotalPlatformPnL = state.TotalPlatformPnL,
|
TotalPlatformPnL = state.TotalPlatformPnL,
|
||||||
TotalPlatformVolume = state.TotalPlatformVolume,
|
TotalPlatformVolume = state.TotalPlatformVolume,
|
||||||
TotalPlatformVolumeLast24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo,
|
TotalPlatformVolumeLast24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo,
|
||||||
TotalOpenInterest = state.TotalOpenInterest,
|
TotalOpenInterest = state.OpenInterest,
|
||||||
TotalPositionCount = state.TotalPositionCount,
|
TotalPositionCount = state.TotalPositionCount,
|
||||||
TotalPlatformFees = state.TotalPlatformFees,
|
TotalPlatformFees = state.TotalPlatformFees,
|
||||||
|
|
||||||
@@ -471,7 +462,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
|||||||
StrategiesChange24h = state.TotalActiveStrategies - state.TotalActiveStrategies24hAgo,
|
StrategiesChange24h = state.TotalActiveStrategies - state.TotalActiveStrategies24hAgo,
|
||||||
PnLChange24h = state.TotalPlatformPnL - state.TotalPlatformPnL24hAgo,
|
PnLChange24h = state.TotalPlatformPnL - state.TotalPlatformPnL24hAgo,
|
||||||
VolumeChange24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo,
|
VolumeChange24h = state.TotalPlatformVolume - state.TotalPlatformVolume24hAgo,
|
||||||
OpenInterestChange24h = state.TotalOpenInterest - state.TotalOpenInterest24hAgo,
|
OpenInterestChange24h = state.OpenInterest - state.TotalOpenInterest24hAgo,
|
||||||
PositionCountChange24h = state.TotalPositionCount - state.TotalPositionCount24hAgo,
|
PositionCountChange24h = state.TotalPositionCount - state.TotalPositionCount24hAgo,
|
||||||
FeesChange24h = state.TotalPlatformFees - state.TotalPlatformFees24hAgo,
|
FeesChange24h = state.TotalPlatformFees - state.TotalPlatformFees24hAgo,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user