Update closing trade date on SL or TP
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'}>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user