Update closing trade date on SL or TP

This commit is contained in:
2025-10-04 19:36:27 +07:00
parent 343b85dada
commit 5468b1e7f7
5 changed files with 54 additions and 66 deletions

View File

@@ -450,6 +450,8 @@ public class TradingBotBase : ITradingBot
if (internalPosition.Status.Equals(PositionStatus.Filled)) if (internalPosition.Status.Equals(PositionStatus.Filled))
{ {
internalPosition.Status = PositionStatus.Finished; internalPosition.Status = PositionStatus.Finished;
// Call HandleClosedPosition to ensure trade dates are properly updated
await HandleClosedPosition(internalPosition);
} }
} }
} }
@@ -1161,6 +1163,8 @@ public class TradingBotBase : ITradingBot
{ {
// Trade close on exchange => Should close trade manually // Trade close on exchange => Should close trade manually
await SetPositionStatus(signal.Identifier, PositionStatus.Finished); await SetPositionStatus(signal.Identifier, PositionStatus.Finished);
// Ensure trade dates are properly updated even for canceled/rejected positions
await HandleClosedPosition(position);
} }
} }
} }

View File

@@ -443,20 +443,21 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
} }
/// <summary> /// <summary>
/// Calculates a daily snapshot from positions for a specific date /// Calculates a CUMULATIVE daily snapshot from positions up to a specific date
/// </summary> /// </summary>
/// <param name="positions">All positions to analyze</param> /// <param name="positions">All positions to analyze</param>
/// <param name="targetDate">The date to calculate the snapshot for</param> /// <param name="targetDate">The date to calculate the snapshot up to</param>
/// <returns>A daily snapshot for the specified date</returns> /// <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 dayStart = targetDate;
var dayEnd = targetDate.AddDays(1); var dayEnd = targetDate.AddDays(1);
// For daily snapshots, we need to consider ALL positions to calculate: // For CUMULATIVE daily snapshots, we need to consider ALL positions to calculate:
// 1. Volume from trades that occurred on this specific day // 1. TOTAL volume from all trades that occurred on or before this day
// 2. Open interest from positions that were active during this day // 2. Open interest from positions that were active during this day
// So we'll process all positions and filter the relevant data // 3. Cumulative position count up to this date
// So we'll process all positions and include relevant data cumulatively
// Calculate metrics for this specific day // Calculate metrics for this specific day
var totalVolume = 0m; var totalVolume = 0m;
@@ -499,84 +500,60 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
foreach (var position in positions) foreach (var position in positions)
{ {
// Calculate volume for trades that occurred on this specific day // Calculate CUMULATIVE volume up to this point in time
var dayVolume = 0m; // 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); position.Identifier, position.Date, targetDate, position.Date.Date);
// Add opening volume if position was opened on this day // Add opening volume if position was opened on or before this day
// Use more flexible date comparison to handle timezone differences // Use more flexible date comparison to handle timezone differences
if (position.Date.Date == targetDate || if (position.Date.Date <= targetDate)
(position.Date >= targetDate && position.Date < targetDate.AddDays(1)))
{ {
var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage; var openingVolume = position.Open.Price * position.Open.Quantity * position.Open.Leverage;
dayVolume += openingVolume; totalVolume += openingVolume;
_logger.LogDebug("Position {PositionId} opened on {TargetDate}: Opening volume = {OpeningVolume}", _logger.LogDebug("Position {PositionId} opened on/before {TargetDate}: Opening volume = {OpeningVolume}",
position.Identifier, targetDate, openingVolume); position.Identifier, targetDate, openingVolume);
} }
// Add closing volume if position was closed on this day // Add closing volume if position was closed on or before this day
if (position.IsFinished()) if (position.IsFinished())
{ {
if (position.StopLoss.Status == TradeStatus.Filled && if (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate)
(position.StopLoss.Date.Date == targetDate ||
(position.StopLoss.Date >= targetDate && position.StopLoss.Date < targetDate.AddDays(1))))
{ {
var closingVolume = position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage; var closingVolume = position.StopLoss.Price * position.StopLoss.Quantity * position.StopLoss.Leverage;
dayVolume += closingVolume; totalVolume += closingVolume;
_logger.LogDebug("Position {PositionId} closed on {TargetDate} via StopLoss: Closing volume = {ClosingVolume}", _logger.LogDebug("Position {PositionId} closed on/before {TargetDate} via StopLoss: Closing volume = {ClosingVolume}",
position.Identifier, targetDate, closingVolume); position.Identifier, targetDate, closingVolume);
} }
if (position.TakeProfit1.Status == TradeStatus.Filled && if (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate)
(position.TakeProfit1.Date.Date == targetDate ||
(position.TakeProfit1.Date >= targetDate && position.TakeProfit1.Date < targetDate.AddDays(1))))
{ {
var closingVolume = position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage; var closingVolume = position.TakeProfit1.Price * position.TakeProfit1.Quantity * position.TakeProfit1.Leverage;
dayVolume += closingVolume; totalVolume += closingVolume;
_logger.LogDebug("Position {PositionId} closed on {TargetDate} via TakeProfit1: Closing volume = {ClosingVolume}", _logger.LogDebug("Position {PositionId} closed on/before {TargetDate} via TakeProfit1: Closing volume = {ClosingVolume}",
position.Identifier, targetDate, closingVolume); position.Identifier, targetDate, closingVolume);
} }
if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && if (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
(position.TakeProfit2.Date.Date == targetDate ||
(position.TakeProfit2.Date >= targetDate && position.TakeProfit2.Date < targetDate.AddDays(1))))
{ {
var closingVolume = position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage; var closingVolume = position.TakeProfit2.Price * position.TakeProfit2.Quantity * position.TakeProfit2.Leverage;
dayVolume += closingVolume; totalVolume += closingVolume;
_logger.LogDebug("Position {PositionId} closed on {TargetDate} via TakeProfit2: Closing volume = {ClosingVolume}",
position.Identifier, targetDate, closingVolume);
} }
} }
if (dayVolume > 0) // Calculate CUMULATIVE fees and PnL for positions closed on or before this day
{ var wasClosedOnOrBeforeThisDay = position.IsFinished() && (
_logger.LogDebug("Position {PositionId} contributed {DayVolume} to {TargetDate} total volume", (position.StopLoss.Status == TradeStatus.Filled && position.StopLoss.Date.Date <= targetDate) ||
position.Identifier, dayVolume, targetDate); (position.TakeProfit1.Status == TradeStatus.Filled && position.TakeProfit1.Date.Date <= targetDate) ||
} (position.TakeProfit2 != null && position.TakeProfit2.Status == TradeStatus.Filled && position.TakeProfit2.Date.Date <= targetDate)
totalVolume += dayVolume;
// Calculate fees and PnL for positions closed on this day
var wasClosedOnThisDay = 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)
); );
if (wasClosedOnThisDay) if (wasClosedOnOrBeforeThisDay)
{ {
totalFees += position.CalculateTotalFees(); totalFees += position.CalculateTotalFees();
totalPnL += position.ProfitAndLoss?.Realized ?? 0; totalPnL += position.ProfitAndLoss?.Realized ?? 0;
} }
// Count positions that were active on this day (opened on or before, closed on or after) // Count positions that were created on or before this day (CUMULATIVE position count)
var wasActiveOnThisDay = position.Date.Date <= targetDate && if (position.Date.Date <= targetDate)
(!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));
if (wasActiveOnThisDay)
{ {
totalPositionCount++; totalPositionCount++;
} }
@@ -586,7 +563,7 @@ public class PlatformSummaryGrain : Grain, IPlatformSummaryGrain, IRemindable
var totalAgents = await _agentService.GetTotalAgentCount(); var totalAgents = await _agentService.GetTotalAgentCount();
var totalStrategies = _state.State.TotalActiveStrategies; var totalStrategies = _state.State.TotalActiveStrategies;
_logger.LogInformation("Calculated daily snapshot for {TargetDate}: TotalVolume={TotalVolume}, MaxOpenInterest={MaxOpenInterest}, TotalPositionCount={TotalPositionCount}", _logger.LogInformation("Calculated CUMULATIVE daily snapshot for {TargetDate}: CumVolume={TotalVolume}, MaxOpenInterest={MaxOpenInterest}, CumPositionCount={TotalPositionCount}",
targetDate, totalVolume, maxOpenInterest, totalPositionCount); targetDate, totalVolume, maxOpenInterest, totalPositionCount);
return new DailySnapshot return new DailySnapshot

View File

@@ -438,13 +438,13 @@ public class PostgreSqlTradingRepository : ITradingRepository
/// <summary> /// <summary>
/// Updates a trade entity with data from a domain trade object /// Updates a trade entity with data from a domain trade object
/// Only updates the date if the trade status is changing from Requested to Filled /// Always updates the date when it changes to ensure accurate execution dates
/// </summary> /// </summary>
private void UpdateTradeEntity(TradeEntity entity, Trade trade) private void UpdateTradeEntity(TradeEntity entity, Trade trade)
{ {
// Only update the date if the trade status is changing from Requested to Filled // Only update the date if the trade status is changing from Requested to Filled
// This prevents overwriting dates for trades that are already filled // This prevents overwriting dates for trades that are already filled
if (entity.Status != TradeStatus.Filled && trade.Status == TradeStatus.Filled) if (entity.Status != trade.Status)
{ {
entity.Date = trade.Date; entity.Date = trade.Date;
} }

View File

@@ -1,5 +1,9 @@
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import type {ClosePositionRequest, Position, UserStrategyDetailsViewModel} from '../../../generated/ManagingApi' import type {
ClosePositionRequest,
PositionViewModel,
UserStrategyDetailsViewModel
} from '../../../generated/ManagingApi'
import {BotClient, DataClient} from '../../../generated/ManagingApi' import {BotClient, DataClient} from '../../../generated/ManagingApi'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import Modal from '../Modal/Modal' import Modal from '../Modal/Modal'
@@ -9,6 +13,7 @@ interface TradesModalProps {
showModal: boolean showModal: boolean
agentName: string | null agentName: string | null
strategyName: string | null strategyName: string | null
botIdentifier: string | undefined
onClose: () => void onClose: () => void
} }
@@ -16,6 +21,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
showModal, showModal,
strategyName, strategyName,
agentName, agentName,
botIdentifier,
onClose, onClose,
}) => { }) => {
const { apiUrl } = useApiUrlStore() const { apiUrl } = useApiUrlStore()
@@ -49,7 +55,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
} }
} }
const closePosition = async (position: Position) => { const closePosition = async (position: PositionViewModel) => {
if (!agentName) return if (!agentName) return
try { try {
@@ -60,7 +66,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: position.initiatorIdentifier, identifier: botIdentifier,
positionId: position.identifier positionId: position.identifier
} }
@@ -115,7 +121,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
</thead> </thead>
<tbody> <tbody>
{strategyData.positions && strategyData.positions.length > 0 ? ( {strategyData.positions && strategyData.positions.length > 0 ? (
strategyData.positions.map((position: Position) => ( strategyData.positions.map((position: PositionViewModel) => (
<tr key={position.identifier}> <tr key={position.identifier}>
<td>{new Date(position.date).toLocaleString()}</td> <td>{new Date(position.date).toLocaleString()}</td>
<td className={position.originDirection === 'Long' ? 'text-success' : 'text-error'}> <td className={position.originDirection === 'Long' ? 'text-success' : 'text-error'}>

View File

@@ -72,7 +72,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
const [showManualPositionModal, setShowManualPositionModal] = useState(false) const [showManualPositionModal, setShowManualPositionModal] = useState(false)
const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState<string | null>(null) const [selectedBotForManualPosition, setSelectedBotForManualPosition] = useState<string | null>(null)
const [showTradesModal, setShowTradesModal] = useState(false) const [showTradesModal, setShowTradesModal] = useState(false)
const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ name: string; agentName: string } | null>(null) const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ name: string; agentName: string; identifier: string } | null>(null)
const [showBotConfigModal, setShowBotConfigModal] = useState(false) const [showBotConfigModal, setShowBotConfigModal] = useState(false)
const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{ const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{
identifier: string identifier: string
@@ -144,10 +144,10 @@ const BotList: React.FC<IBotList> = ({ list }) => {
) )
} }
function getTradesBadge(name: string, agentName: string) { function getTradesBadge(name: string, agentName: string, botIdentifier: string) {
const classes = baseBadgeClass() + ' bg-secondary' const classes = baseBadgeClass() + ' bg-secondary'
return ( return (
<button className={classes} onClick={() => openTradesModal(name, agentName)}> <button className={classes} onClick={() => openTradesModal(name, agentName, botIdentifier)}>
<p className="text-primary-content flex"> <p className="text-primary-content flex">
<ChartBarIcon width={15}></ChartBarIcon> <ChartBarIcon width={15}></ChartBarIcon>
</p> </p>
@@ -160,8 +160,8 @@ const BotList: React.FC<IBotList> = ({ list }) => {
setShowManualPositionModal(true) setShowManualPositionModal(true)
} }
function openTradesModal(name: string, agentName: string) { function openTradesModal(name: string, agentName: string, botIdentifier: string) {
setSelectedBotForTrades({ name: name, agentName }) setSelectedBotForTrades({ name: name, agentName, identifier: botIdentifier })
setShowTradesModal(true) setShowTradesModal(true)
} }
@@ -311,7 +311,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
<div className={baseBadgeClass(true)}> <div className={baseBadgeClass(true)}>
PNL {bot.profitAndLoss.toFixed(2).toString()} $ PNL {bot.profitAndLoss.toFixed(2).toString()} $
</div> </div>
{getTradesBadge(bot.name, bot.agentName)} {getTradesBadge(bot.name, bot.agentName, bot.identifier)}
</div> </div>
</div> </div>
</div> </div>
@@ -337,6 +337,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
showModal={showTradesModal} showModal={showTradesModal}
strategyName={selectedBotForTrades?.name ?? null} strategyName={selectedBotForTrades?.name ?? null}
agentName={selectedBotForTrades?.agentName ?? null} agentName={selectedBotForTrades?.agentName ?? null}
botIdentifier={selectedBotForTrades?.identifier ?? undefined}
onClose={() => { onClose={() => {
setShowTradesModal(false) setShowTradesModal(false)
setSelectedBotForTrades(null) setSelectedBotForTrades(null)