Fix config update + remove messages + Summary fix for not open position
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Managing.Application.Bots.Models;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.Trades;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
@@ -19,7 +20,7 @@ public interface IBotService
|
||||
Task<IEnumerable<Bot>> GetBotsByIdsAsync(IEnumerable<Guid> botIds);
|
||||
Task<Bot> GetBotByName(string name);
|
||||
Task<Bot> GetBotByIdentifier(Guid identifier);
|
||||
Task<Position> OpenPositionManuallyAsync(Guid identifier, TradeDirection direction);
|
||||
Task<LightSignal> CreateManualSignalAsync(Guid identifier, TradeDirection direction);
|
||||
Task<Position> ClosePositionAsync(Guid identifier, Guid positionId);
|
||||
Task<TradingBotConfig> GetBotConfig(Guid identifier);
|
||||
Task<IEnumerable<TradingBotConfig>> GetBotConfigsByIdsAsync(IEnumerable<Guid> botIds);
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Managing.Application.Abstractions
|
||||
decimal GetTotalFees();
|
||||
Task LoadAccount();
|
||||
Task LoadLastCandle();
|
||||
Task<Position> OpenPositionManually(TradeDirection direction);
|
||||
Task<LightSignal> CreateManualSignal(TradeDirection direction);
|
||||
|
||||
Task CloseTrade(LightSignal signal, Position position, Trade tradeToClose, decimal lastPrice,
|
||||
bool tradeClosingPosition = false);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Managing.Application.Shared;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Trades;
|
||||
using Managing.Domain.Users;
|
||||
@@ -277,7 +278,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
var hasOpenPositions = await HasOpenPositionsInDatabaseAsync();
|
||||
if (hasOpenPositions)
|
||||
{
|
||||
_logger.LogWarning("Stopping bot {Name} while it still has open positions in database. Trading loop will stop but positions remain managed by system.",
|
||||
_logger.LogWarning(
|
||||
"Stopping bot {Name} while it still has open positions in database. Trading loop will stop but positions remain managed by system.",
|
||||
_tradingBot?.Config.Name);
|
||||
throw new InvalidOperationException(
|
||||
"Cannot stop bot while it has open positions. Please close all positions first.");
|
||||
@@ -442,7 +444,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
}
|
||||
|
||||
|
||||
public async Task<Position> OpenPositionManuallyAsync(TradeDirection direction)
|
||||
public async Task<LightSignal> CreateManualSignalAsync(TradeDirection direction)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -462,7 +464,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
||||
await _state.WriteStateAsync();
|
||||
}
|
||||
|
||||
return await _tradingBot.OpenPositionManually(direction);
|
||||
return await _tradingBot.CreateManualSignal(direction);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -339,11 +339,6 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
Positions[newlyCreatedPosition.Identifier] = newlyCreatedPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
await LogWarning(
|
||||
$"⚠️ Position Creation Failed\nSignal: `{signal.Identifier}`\nPosition creation returned null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +364,7 @@ public class TradingBotBase : ITradingBot
|
||||
try
|
||||
{
|
||||
// Skip processing if position is already canceled or rejected (never filled)
|
||||
if (positionForSignal.Status == PositionStatus.Canceled ||
|
||||
if (positionForSignal.Status == PositionStatus.Canceled ||
|
||||
positionForSignal.Status == PositionStatus.Rejected)
|
||||
{
|
||||
Logger.LogDebug(
|
||||
@@ -486,7 +481,7 @@ public class TradingBotBase : ITradingBot
|
||||
if (timeSinceRequest.TotalMinutes >= waitTimeMinutes)
|
||||
{
|
||||
await LogWarning(
|
||||
$"⚠️ Order Cleanup\nToo many open orders: `{orders.Count()}`\nPosition: `{positionForSignal.Identifier}`\nTime elapsed: `{waitTimeMinutes}min`\nCanceling all orders...");
|
||||
$"⚠️ Orders Cleanup\nTime elapsed: {waitTimeMinutes}min\nCanceling all orders...");
|
||||
try
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||
@@ -495,7 +490,7 @@ public class TradingBotBase : ITradingBot
|
||||
await exchangeService.CancelOrder(Account, Config.Ticker);
|
||||
});
|
||||
await LogInformation(
|
||||
$"✅ Orders Canceled\nSuccessfully canceled all orders for: `{Config.Ticker}`");
|
||||
$"✅ Orders for {internalPosition.OriginDirection} {Config.Ticker} successfully canceled");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -504,6 +499,13 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Canceled);
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
|
||||
positionForSignal.Status = PositionStatus.Canceled;
|
||||
positionForSignal.Open.SetStatus(TradeStatus.Cancelled);
|
||||
positionForSignal.StopLoss.SetStatus(TradeStatus.Cancelled);
|
||||
positionForSignal.TakeProfit1.SetStatus(TradeStatus.Cancelled);
|
||||
|
||||
await UpdatePositionDatabase(positionForSignal);
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -577,7 +579,7 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
await LogWarning(
|
||||
$"❌ Position Never Filled\nNo position on exchange and no orders\nSignal: `{signal.Identifier}`\nPosition was never filled and will be marked as canceled.");
|
||||
|
||||
|
||||
// Position was never filled (still in New status), so just mark it as canceled
|
||||
// Don't call HandleClosedPosition as that would incorrectly add volume/PnL
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Canceled);
|
||||
@@ -762,16 +764,16 @@ public class TradingBotBase : ITradingBot
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (internalPosition.Status == PositionStatus.Rejected ||
|
||||
internalPosition.Status == PositionStatus.Canceled)
|
||||
{
|
||||
await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
|
||||
if (signal.Status == SignalStatus.PositionOpen)
|
||||
{
|
||||
Logger.LogInformation($"Try to re-open position");
|
||||
await OpenPosition(signal);
|
||||
}
|
||||
}
|
||||
// else if (internalPosition.Status == PositionStatus.Rejected ||
|
||||
// internalPosition.Status == PositionStatus.Canceled)
|
||||
// {
|
||||
// await LogWarning($"Open position trade is rejected for signal {signal.Identifier}");
|
||||
// if (signal.Status == SignalStatus.PositionOpen)
|
||||
// {
|
||||
// Logger.LogInformation($"Try to re-open position");
|
||||
// await OpenPosition(signal);
|
||||
// }
|
||||
// }
|
||||
|
||||
if (Config.UseSynthApi && !Config.IsForBacktest &&
|
||||
positionForSignal.Status == PositionStatus.Filled)
|
||||
@@ -926,24 +928,26 @@ public class TradingBotBase : ITradingBot
|
||||
|
||||
if (position != null)
|
||||
{
|
||||
if (position.Open.Status != TradeStatus.Cancelled)
|
||||
if (position.Open.Status != TradeStatus.Cancelled && position.Status != PositionStatus.Rejected)
|
||||
{
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.PositionOpen);
|
||||
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
|
||||
async messengerService => { await messengerService.SendClosedPosition(position, Account.User); });
|
||||
async messengerService => { await messengerService.SendPosition(position); });
|
||||
}
|
||||
|
||||
Logger.LogInformation($"Position requested");
|
||||
return position; // Return the created position without adding to list
|
||||
return position;
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetPositionStatus(signal.Identifier, PositionStatus.Rejected);
|
||||
position.Status = PositionStatus.Rejected;
|
||||
await UpdatePositionDatabase(position);
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
return null;
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,7 +968,7 @@ public class TradingBotBase : ITradingBot
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
await LogWarning($"Cannot open trade : {ex.Message}, stackTrace : {ex.StackTrace}");
|
||||
SentrySdk.CaptureException(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1247,7 +1251,7 @@ public class TradingBotBase : ITradingBot
|
||||
// We use this for reconciliation with the bot's own calculations
|
||||
var totalBotFees = position.GasFees + position.UiFees;
|
||||
var gmxNetPnl = gmxPosition.ProfitAndLoss.Realized; // This is already after GMX fees
|
||||
|
||||
|
||||
position.ProfitAndLoss = new ProfitAndLoss
|
||||
{
|
||||
// GMX's realized PnL is already after their fees
|
||||
@@ -1530,7 +1534,7 @@ public class TradingBotBase : ITradingBot
|
||||
// No need to subtract fees from PnL as they're tracked separately
|
||||
}
|
||||
|
||||
SkipCandleBasedCalculation:
|
||||
SkipCandleBasedCalculation:
|
||||
await SetPositionStatus(position.SignalIdentifier, PositionStatus.Finished);
|
||||
|
||||
// Update position in database with all trade changes
|
||||
@@ -1538,13 +1542,13 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
position.Status = PositionStatus.Finished;
|
||||
await UpdatePositionDatabase(position);
|
||||
|
||||
|
||||
// Only send PositionClosed notification if the position was actually filled
|
||||
// Check if Open trade was filled (means position was opened on the broker)
|
||||
if (position.Open?.Status == TradeStatus.Filled)
|
||||
{
|
||||
await NotifyAgentAndPlatformGrainAsync(NotificationEventType.PositionClosed, position);
|
||||
|
||||
|
||||
// Update the last position closing time for cooldown period tracking
|
||||
// Only update if position was actually filled
|
||||
LastPositionClosingTime = Config.IsForBacktest ? currentCandle.Date : DateTime.UtcNow;
|
||||
@@ -1586,10 +1590,7 @@ public class TradingBotBase : ITradingBot
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedService<IMessengerService>(_scopeFactory,
|
||||
async messengerService =>
|
||||
{
|
||||
await messengerService.SendClosedPosition(position, Account.User);
|
||||
});
|
||||
async messengerService => { await messengerService.SendClosedPosition(position, Account.User); });
|
||||
}
|
||||
|
||||
await CancelAllOrders();
|
||||
@@ -1810,7 +1811,7 @@ public class TradingBotBase : ITradingBot
|
||||
/// <param name="direction">The direction of the trade (Long/Short).</param>
|
||||
/// <returns>The created Position object.</returns>
|
||||
/// <exception cref="Exception">Throws if no candles are available or position opening fails.</exception>
|
||||
public async Task<Position> OpenPositionManually(TradeDirection direction)
|
||||
public async Task<LightSignal> CreateManualSignal(TradeDirection direction)
|
||||
{
|
||||
if (LastCandle == null)
|
||||
{
|
||||
@@ -1828,22 +1829,7 @@ public class TradingBotBase : ITradingBot
|
||||
// Add the signal to our collection
|
||||
await AddSignal(signal);
|
||||
|
||||
// Open the position using the generated signal (SL/TP handled by MoneyManagement)
|
||||
var position = await OpenPosition(signal);
|
||||
|
||||
if (position == null)
|
||||
{
|
||||
// Clean up the signal if position creation failed
|
||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||
throw new Exception("Failed to open position");
|
||||
}
|
||||
|
||||
// Add the position to the list after successful creation
|
||||
Positions[position.Identifier] = position;
|
||||
|
||||
Logger.LogInformation(
|
||||
$"👤 Manual Position Opened\nPosition: `{position.Identifier}`\nSignal: `{signal.Identifier}`\nAdded to positions list");
|
||||
return position;
|
||||
return signal;
|
||||
}
|
||||
|
||||
public async Task AddSignal(LightSignal signal)
|
||||
@@ -1866,7 +1852,7 @@ public class TradingBotBase : ITradingBot
|
||||
$"🆔 Signal ID: `{signal.Identifier}`";
|
||||
|
||||
// Apply Synth-based signal filtering if enabled
|
||||
if ((Config.UseSynthApi || !Config.IsForBacktest) && ExecutionCount > 0)
|
||||
if (Config.UseSynthApi && !Config.IsForBacktest && ExecutionCount > 0)
|
||||
{
|
||||
await ServiceScopeHelpers.WithScopedServices<ITradingService, IExchangeService>(_scopeFactory,
|
||||
async (tradingService, exchangeService) =>
|
||||
@@ -2052,11 +2038,26 @@ public class TradingBotBase : ITradingBot
|
||||
changes.Add($"👀 Watch Only: {oldWatch} → {newWatch}");
|
||||
}
|
||||
|
||||
if (Config.MoneyManagement?.GetType().Name != newConfig.MoneyManagement?.GetType().Name)
|
||||
// Check for changes in individual MoneyManagement properties
|
||||
if (Config.MoneyManagement?.StopLoss != newConfig.MoneyManagement?.StopLoss)
|
||||
{
|
||||
var oldMM = Config.MoneyManagement?.GetType().Name ?? "None";
|
||||
var newMM = newConfig.MoneyManagement?.GetType().Name ?? "None";
|
||||
changes.Add($"💰 Money Management: {oldMM} → {newMM}");
|
||||
var oldStopLoss = Config.MoneyManagement?.StopLoss.ToString("P2") ?? "None";
|
||||
var newStopLoss = newConfig.MoneyManagement?.StopLoss.ToString("P2") ?? "None";
|
||||
changes.Add($"🛑 Stop Loss: {oldStopLoss} → {newStopLoss}");
|
||||
}
|
||||
|
||||
if (Config.MoneyManagement?.TakeProfit != newConfig.MoneyManagement?.TakeProfit)
|
||||
{
|
||||
var oldTakeProfit = Config.MoneyManagement?.TakeProfit.ToString("P2") ?? "None";
|
||||
var newTakeProfit = newConfig.MoneyManagement?.TakeProfit.ToString("P2") ?? "None";
|
||||
changes.Add($"🎯 Take Profit: {oldTakeProfit} → {newTakeProfit}");
|
||||
}
|
||||
|
||||
if (Config.MoneyManagement?.Leverage != newConfig.MoneyManagement?.Leverage)
|
||||
{
|
||||
var oldLeverage = Config.MoneyManagement?.Leverage.ToString("F1") + "x" ?? "None";
|
||||
var newLeverage = newConfig.MoneyManagement?.Leverage.ToString("F1") + "x" ?? "None";
|
||||
changes.Add($"⚡ Leverage: {oldLeverage} → {newLeverage}");
|
||||
}
|
||||
|
||||
if (Config.RiskManagement != newConfig.RiskManagement)
|
||||
@@ -2260,13 +2261,13 @@ public class TradingBotBase : ITradingBot
|
||||
{
|
||||
var grainKey = CandleHelpers.GetCandleStoreGrainKey(Account.Exchange, Config.Ticker, Config.Timeframe);
|
||||
var grain = grainFactory.GetGrain<ICandleStoreGrain>(grainKey);
|
||||
|
||||
|
||||
var lastCandles = await grain.GetLastCandle(1);
|
||||
LastCandle = lastCandles.FirstOrDefault();
|
||||
|
||||
|
||||
if (LastCandle != null)
|
||||
{
|
||||
Logger.LogDebug("Successfully refreshed last candle for {Ticker} at {Date}",
|
||||
Logger.LogDebug("Successfully refreshed last candle for {Ticker} at {Date}",
|
||||
Config.Ticker, LastCandle.Date);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -50,18 +50,18 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
// Daily reminder - runs at midnight (00:00 UTC)
|
||||
var nextDailyTime = CandleHelpers.GetNextExpectedCandleTime(Timeframe.OneDay, now);
|
||||
var timeUntilNextDay = nextDailyTime - now;
|
||||
|
||||
|
||||
// Ensure dueTime is never negative - if it is, schedule for next day
|
||||
if (timeUntilNextDay <= TimeSpan.Zero)
|
||||
{
|
||||
timeUntilNextDay = TimeSpan.FromDays(1).Add(TimeSpan.FromMinutes(3));
|
||||
_logger.LogWarning("Due time was negative or zero, scheduling reminder for next day instead");
|
||||
}
|
||||
|
||||
|
||||
await this.RegisterOrUpdateReminder(_dailySnapshotReminder,
|
||||
timeUntilNextDay, TimeSpan.FromDays(1).Add(TimeSpan.FromMinutes(3)));
|
||||
|
||||
_logger.LogInformation("Daily reminder scheduled - Next daily: {NextDaily}, Due time: {DueTime}",
|
||||
_logger.LogInformation("Daily reminder scheduled - Next daily: {NextDaily}, Due time: {DueTime}",
|
||||
nextDailyTime, timeUntilNextDay);
|
||||
|
||||
// Wipe daily snapshots except for the first day
|
||||
@@ -136,12 +136,14 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
|
||||
foreach (var position in positions)
|
||||
{
|
||||
if (!position.IsValidForMetrics()) continue;
|
||||
|
||||
// Calculate volume using the dedicated method
|
||||
var positionVolume = TradingHelpers.GetVolumeForPosition(position);
|
||||
totalVolume += positionVolume;
|
||||
|
||||
// Add to open interest for active positions only (only opening volume)
|
||||
if (!position.IsFinished())
|
||||
if (position.Status.Equals(PositionStatus.Filled))
|
||||
{
|
||||
var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
|
||||
totalOpenInterest += openingVolume;
|
||||
@@ -175,7 +177,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
_state.State.PositionCountByAsset[ticker]++;
|
||||
|
||||
// Position count breakdown by direction - only count finished positions
|
||||
if (!position.IsFinished())
|
||||
if (position.IsValidForMetrics())
|
||||
{
|
||||
if (!_state.State.PositionCountByDirection.ContainsKey(direction))
|
||||
{
|
||||
@@ -201,6 +203,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
_state.State.PositionCountByDirection.GetValueOrDefault(TradeDirection.Short, 0));
|
||||
|
||||
_state.State.LastUpdated = DateTime.UtcNow;
|
||||
await RefreshAgentCountAsync();
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
_logger.LogInformation("Platform summary data refreshed successfully");
|
||||
@@ -344,7 +347,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
}
|
||||
|
||||
var originalCount = _state.State.DailySnapshots.Count;
|
||||
|
||||
|
||||
// Keep only the first day snapshot
|
||||
var firstSnapshot = _state.State.DailySnapshots.OrderBy(s => s.Date).First();
|
||||
_state.State.DailySnapshots.Clear();
|
||||
@@ -354,8 +357,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
_state.State.LastSnapshot = firstSnapshot.Date;
|
||||
|
||||
await _state.WriteStateAsync();
|
||||
|
||||
_logger.LogInformation("Wiped {WipedCount} daily snapshots, kept first snapshot from {FirstDate}",
|
||||
|
||||
_logger.LogInformation("Wiped {WipedCount} daily snapshots, kept first snapshot from {FirstDate}",
|
||||
originalCount - 1, firstSnapshot.Date);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -375,7 +378,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
|
||||
// Get all positions to calculate missing snapshots
|
||||
var positions = await _tradingService.GetAllDatabasePositionsAsync();
|
||||
|
||||
|
||||
if (!positions.Any())
|
||||
{
|
||||
_logger.LogInformation("No positions found, skipping gap filling");
|
||||
@@ -388,7 +391,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
var today = DateTime.UtcNow.Date;
|
||||
|
||||
// Determine the start date for gap filling
|
||||
var startDate = _state.State.DailySnapshots.Any()
|
||||
var startDate = _state.State.DailySnapshots.Any()
|
||||
? _state.State.DailySnapshots.Max(s => s.Date).AddDays(1)
|
||||
: earliestPositionDate;
|
||||
|
||||
@@ -419,8 +422,9 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
{
|
||||
var snapshot = await CalculateDailySnapshotFromPositionsAsync(positions.ToList(), missingDate);
|
||||
_state.State.DailySnapshots.Add(snapshot);
|
||||
|
||||
_logger.LogInformation("Created missing daily snapshot for {Date}: Volume={Volume}, PnL={PnL}, Positions={Positions}",
|
||||
|
||||
_logger.LogInformation(
|
||||
"Created missing daily snapshot for {Date}: Volume={Volume}, PnL={PnL}, Positions={Positions}",
|
||||
missingDate, snapshot.TotalVolume, snapshot.TotalPnL, snapshot.TotalLifetimePositionCount);
|
||||
}
|
||||
|
||||
@@ -448,7 +452,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
/// <param name="positions">All positions to analyze</param>
|
||||
/// <param name="targetDate">The date to calculate the snapshot up to</param>
|
||||
/// <returns>A cumulative daily snapshot for the specified date</returns>
|
||||
private async Task<DailySnapshot> CalculateDailySnapshotFromPositionsAsync(List<Position> positions, DateTime targetDate)
|
||||
private async Task<DailySnapshot> CalculateDailySnapshotFromPositionsAsync(List<Position> positions,
|
||||
DateTime targetDate)
|
||||
{
|
||||
var dayStart = targetDate;
|
||||
var dayEnd = targetDate.AddDays(1);
|
||||
@@ -468,7 +473,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
|
||||
// Calculate open interest at different points during the day to find the maximum
|
||||
var hourlyOpenInterest = new List<decimal>();
|
||||
|
||||
|
||||
// Check open interest at each hour of the day (0-23)
|
||||
for (int hour = 0; hour < 24; hour++)
|
||||
{
|
||||
@@ -478,11 +483,15 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
foreach (var position in positions)
|
||||
{
|
||||
// Check if position was active at this hour
|
||||
var wasActiveAtThisHour = position.Date <= hourDateTime &&
|
||||
(!position.IsFinished() ||
|
||||
(position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date > hourDateTime) ||
|
||||
(position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date > hourDateTime) ||
|
||||
(position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date > hourDateTime));
|
||||
var wasActiveAtThisHour = position.Date <= hourDateTime &&
|
||||
(!position.IsFinished() ||
|
||||
(position.StopLoss.Status == TradeStatus.Filled &&
|
||||
position.StopLoss.Date > hourDateTime) ||
|
||||
(position.TakeProfit1.Status == TradeStatus.Filled &&
|
||||
position.TakeProfit1.Date > hourDateTime) ||
|
||||
(position.TakeProfit2 != null &&
|
||||
position.TakeProfit2.Status == TradeStatus.Filled &&
|
||||
position.TakeProfit2.Date > hourDateTime));
|
||||
|
||||
if (wasActiveAtThisHour)
|
||||
{
|
||||
@@ -491,7 +500,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
hourlyOI += openingVolume;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
hourlyOpenInterest.Add(hourlyOI);
|
||||
}
|
||||
|
||||
@@ -502,16 +511,18 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
{
|
||||
// Calculate CUMULATIVE volume up to this point in time
|
||||
// Include all positions that were opened on or before the target date
|
||||
_logger.LogDebug("Checking position {PositionId}: Position.Date={PositionDate}, TargetDate={TargetDate}, Position.Date.Date={PositionDateOnly}",
|
||||
_logger.LogDebug(
|
||||
"Checking position {PositionId}: Position.Date={PositionDate}, TargetDate={TargetDate}, Position.Date.Date={PositionDateOnly}",
|
||||
position.Identifier, position.Date, targetDate, position.Date.Date);
|
||||
|
||||
|
||||
// Add opening volume if position was opened on or before this day
|
||||
// Use more flexible date comparison to handle timezone differences
|
||||
if (position.Date.Date <= targetDate)
|
||||
{
|
||||
var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
|
||||
totalVolume += openingVolume;
|
||||
_logger.LogDebug("Position {PositionId} opened on/before {TargetDate}: Opening volume = {OpeningVolume}",
|
||||
_logger.LogDebug(
|
||||
"Position {PositionId} opened on/before {TargetDate}: Opening volume = {OpeningVolume}",
|
||||
position.Identifier, targetDate, openingVolume);
|
||||
}
|
||||
|
||||
@@ -520,21 +531,29 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
{
|
||||
if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate)
|
||||
{
|
||||
var closingVolume = position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
|
||||
var closingVolume = position.StopLoss.Price * position.StopLoss.Quantity *
|
||||
position.StopLoss.Leverage;
|
||||
totalVolume += closingVolume;
|
||||
_logger.LogDebug("Position {PositionId} closed on/before {TargetDate} via StopLoss: Closing volume = {ClosingVolume}",
|
||||
_logger.LogDebug(
|
||||
"Position {PositionId} closed on/before {TargetDate} via StopLoss: Closing volume = {ClosingVolume}",
|
||||
position.Identifier, targetDate, closingVolume);
|
||||
}
|
||||
|
||||
if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate)
|
||||
{
|
||||
var closingVolume = position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
|
||||
var closingVolume = position.TakeProfit1.Price * position.TakeProfit1.Quantity *
|
||||
position.TakeProfit1.Leverage;
|
||||
totalVolume += closingVolume;
|
||||
_logger.LogDebug("Position {PositionId} closed on/before {TargetDate} via TakeProfit1: Closing volume = {ClosingVolume}",
|
||||
_logger.LogDebug(
|
||||
"Position {PositionId} closed on/before {TargetDate} via TakeProfit1: Closing volume = {ClosingVolume}",
|
||||
position.Identifier, targetDate, closingVolume);
|
||||
}
|
||||
if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
|
||||
|
||||
if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
|
||||
position.TakeProfit2.Date.Date <= targetDate)
|
||||
{
|
||||
var closingVolume = position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
|
||||
var closingVolume = position.TakeProfit2.Price * position.TakeProfit2.Quantity *
|
||||
position.TakeProfit2.Leverage;
|
||||
totalVolume += closingVolume;
|
||||
}
|
||||
}
|
||||
@@ -543,7 +562,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
var wasClosedOnOrBeforeThisDay = position.IsFinished() && (
|
||||
(position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate) ||
|
||||
(position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate) ||
|
||||
(position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
|
||||
(position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled &&
|
||||
position.TakeProfit2.Date.Date <= targetDate)
|
||||
);
|
||||
|
||||
if (wasClosedOnOrBeforeThisDay)
|
||||
@@ -563,7 +583,8 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
|
||||
var totalAgents = await _agentService.GetTotalAgentCount();
|
||||
var totalStrategies = _state.State.TotalActiveStrategies;
|
||||
|
||||
_logger.LogInformation("Calculated CUMULATIVE daily snapshot for {TargetDate}: CumVolume={TotalVolume}, MaxOpenInterest={MaxOpenInterest}, CumPositionCount={TotalPositionCount}",
|
||||
_logger.LogInformation(
|
||||
"Calculated CUMULATIVE daily snapshot for {TargetDate}: CumVolume={TotalVolume}, MaxOpenInterest={MaxOpenInterest}, CumPositionCount={TotalPositionCount}",
|
||||
targetDate, totalVolume, maxOpenInterest, totalPositionCount);
|
||||
|
||||
return new DailySnapshot
|
||||
|
||||
@@ -8,6 +8,7 @@ using Managing.Common;
|
||||
using Managing.Core;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Bots;
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Trades;
|
||||
@@ -264,10 +265,10 @@ namespace Managing.Application.ManageBot
|
||||
return await _botRepository.GetBotByIdentifierAsync(identifier);
|
||||
}
|
||||
|
||||
public async Task<Position> OpenPositionManuallyAsync(Guid identifier, TradeDirection direction)
|
||||
public async Task<LightSignal> CreateManualSignalAsync(Guid identifier, TradeDirection direction)
|
||||
{
|
||||
var grain = _grainFactory.GetGrain<ILiveTradingBotGrain>(identifier);
|
||||
return await grain.OpenPositionManuallyAsync(direction);
|
||||
return await grain.CreateManualSignalAsync(direction);
|
||||
}
|
||||
|
||||
public async Task<Position> ClosePositionAsync(Guid identifier, Guid positionId)
|
||||
|
||||
Reference in New Issue
Block a user