Fix SLTP for backtests
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {CardPositionItem, TradeChart} from '..'
|
||||
import {CardPositionItem, PositionsModal, TradeChart} from '..'
|
||||
import {
|
||||
Backtest,
|
||||
BacktestClient,
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import {CardPosition, CardText} from '../../mollecules'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {useState} from 'react'
|
||||
|
||||
interface IBacktestRowDetailsProps {
|
||||
backtest: Backtest;
|
||||
@@ -24,6 +25,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
const {apiUrl} = useApiUrlStore();
|
||||
const dataClient = new DataClient({}, apiUrl);
|
||||
const backtestClient = new BacktestClient({}, apiUrl);
|
||||
const [showPositionsModal, setShowPositionsModal] = useState(false);
|
||||
|
||||
// Use TanStack Query to load full backtest data
|
||||
const {data: fullBacktestData, isLoading: isLoadingFullBacktest, error: fullBacktestError} = useQuery({
|
||||
@@ -427,6 +429,14 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
content={getAverageTradesPerDay() + " trades/day"}
|
||||
></CardText>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => setShowPositionsModal(true)}
|
||||
>
|
||||
View All Positions ({positionsArray.length})
|
||||
</button>
|
||||
</div>
|
||||
{!isLoadingCandles && !isLoadingFullBacktest && (
|
||||
<div className="w-full">
|
||||
<figure className="w-full">
|
||||
@@ -442,6 +452,11 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<PositionsModal
|
||||
showModal={showPositionsModal}
|
||||
onClose={() => setShowPositionsModal(false)}
|
||||
positions={positionsArray}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import React from 'react'
|
||||
import {Position, TradeDirection} from '../../../generated/ManagingApi'
|
||||
import {Modal} from '../../mollecules'
|
||||
|
||||
interface PositionsModalProps {
|
||||
showModal: boolean
|
||||
onClose: () => void
|
||||
positions: Position[]
|
||||
}
|
||||
|
||||
function PositionsModal({ showModal, onClose, positions }: PositionsModalProps) {
|
||||
// Sort positions by date (most recent first)
|
||||
const sortedPositions = [...positions].sort((a, b) => {
|
||||
return new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
})
|
||||
|
||||
// Helper function to get exit price based on PnL
|
||||
const getExitPrice = (position: Position): number | null => {
|
||||
if (position.ProfitAndLoss?.realized != null) {
|
||||
if (position.ProfitAndLoss.realized > 0) {
|
||||
return position.TakeProfit1?.price || null
|
||||
} else {
|
||||
return position.StopLoss?.price || null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Helper function to get exit date based on PnL
|
||||
const getExitDate = (position: Position): Date | null => {
|
||||
if (position.ProfitAndLoss?.realized != null) {
|
||||
if (position.ProfitAndLoss.realized > 0) {
|
||||
return position.TakeProfit1?.date || null
|
||||
} else {
|
||||
return position.StopLoss?.date || null
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Helper function to format date
|
||||
const formatDate = (date: Date | null): string => {
|
||||
if (!date) return 'N/A'
|
||||
return new Date(date).toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function to format currency
|
||||
const formatCurrency = (value: number | null | undefined): string => {
|
||||
if (value == null) return 'N/A'
|
||||
return `$${value.toFixed(4)}`
|
||||
}
|
||||
|
||||
// Helper function to get PnL color class
|
||||
const getPnLColorClass = (pnl: number | undefined | null): string => {
|
||||
if (pnl == null) return ''
|
||||
return pnl > 0 ? 'text-success' : pnl < 0 ? 'text-error' : ''
|
||||
}
|
||||
|
||||
// Helper function to calculate ROI
|
||||
const calculateROI = (position: Position): number | null => {
|
||||
const pnl = position.ProfitAndLoss?.realized
|
||||
if (pnl == null || !position.Open) return null
|
||||
|
||||
// Calculate initial position value (considering leverage)
|
||||
const entryPrice = position.Open.price
|
||||
const quantity = position.Open.quantity
|
||||
const leverage = position.Open.leverage || 1
|
||||
const fee = position.Open.fee || 0
|
||||
|
||||
// Initial capital invested = (Entry Price * Quantity) / Leverage + Fee
|
||||
const initialCapital = (entryPrice * quantity / leverage) + fee
|
||||
|
||||
if (initialCapital === 0) return null
|
||||
|
||||
// ROI = (Net PnL / Initial Capital) * 100
|
||||
const roi = (pnl / initialCapital) * 100
|
||||
return roi
|
||||
}
|
||||
|
||||
// Helper function to format ROI
|
||||
const formatROI = (roi: number | null): string => {
|
||||
if (roi == null) return 'N/A'
|
||||
return `${roi.toFixed(2)}%`
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
showModal={showModal}
|
||||
onClose={onClose}
|
||||
titleHeader="Backtest Positions"
|
||||
size="7xl"
|
||||
>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra table-sm w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-xs">#</th>
|
||||
<th className="text-xs">Open Date</th>
|
||||
<th className="text-xs">Close Date</th>
|
||||
<th className="text-xs">Direction</th>
|
||||
<th className="text-xs">Entry Price</th>
|
||||
<th className="text-xs">Exit Price</th>
|
||||
<th className="text-xs">Quantity</th>
|
||||
<th className="text-xs">Leverage</th>
|
||||
<th className="text-xs">Realized P&L</th>
|
||||
<th className="text-xs">ROI</th>
|
||||
<th className="text-xs">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedPositions.map((position, index) => {
|
||||
const exitPrice = getExitPrice(position)
|
||||
const exitDate = getExitDate(position)
|
||||
const pnl = position.ProfitAndLoss?.realized
|
||||
const roi = calculateROI(position)
|
||||
|
||||
return (
|
||||
<tr key={position.identifier || index}>
|
||||
<td className="text-xs">{index + 1}</td>
|
||||
<td className="text-xs">{formatDate(position.Open?.date)}</td>
|
||||
<td className="text-xs">{formatDate(exitDate)}</td>
|
||||
<td className="text-xs">
|
||||
<span className={`badge badge-sm ${
|
||||
position.originDirection === TradeDirection.Long
|
||||
? 'badge-success'
|
||||
: 'badge-error'
|
||||
}`}>
|
||||
{position.originDirection}
|
||||
</span>
|
||||
</td>
|
||||
<td className="text-xs">{formatCurrency(position.Open?.price)}</td>
|
||||
<td className="text-xs">{formatCurrency(exitPrice)}</td>
|
||||
<td className="text-xs">{position.Open?.quantity?.toFixed(4) || 'N/A'}</td>
|
||||
<td className="text-xs">x{position.Open?.leverage || 1}</td>
|
||||
<td className={`text-xs font-semibold ${getPnLColorClass(pnl)}`}>
|
||||
{formatCurrency(pnl)}
|
||||
</td>
|
||||
<td className={`text-xs font-semibold ${getPnLColorClass(roi)}`}>
|
||||
{formatROI(roi)}
|
||||
</td>
|
||||
<td className="text-xs">
|
||||
<span className="badge badge-sm badge-outline">
|
||||
{position.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{sortedPositions.length === 0 && (
|
||||
<div className="text-center py-8 text-base-content/60">
|
||||
No positions found
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<button type="button" className="btn" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default PositionsModal
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as PositionsModal } from './PositionsModal'
|
||||
|
||||
@@ -13,3 +13,4 @@ export { default as StatusBadge } from './StatusBadge/StatusBadge'
|
||||
export { default as PositionsList } from './Positions/PositionList'
|
||||
export { default as ScenarioModal } from './ScenarioModal'
|
||||
export { default as BotNameModal } from './BotNameModal/BotNameModal'
|
||||
export { default as PositionsModal } from './PositionsModal/PositionsModal'
|
||||
|
||||
Reference in New Issue
Block a user