Display data for bots

This commit is contained in:
2025-04-25 16:01:06 +07:00
parent ea1a25e699
commit 9b0505991f
3 changed files with 384 additions and 19 deletions

View File

@@ -0,0 +1,111 @@
import React, {useEffect, useState} from 'react'
import type {Position, UserStrategyDetailsViewModel} from '../../../generated/ManagingApi'
import {DataClient} from '../../../generated/ManagingApi'
import useApiUrlStore from '../../../app/store/apiStore'
import Modal from '../Modal/Modal'
interface TradesModalProps {
showModal: boolean
botName: string | null
onClose: () => void
}
const TradesModal: React.FC<TradesModalProps> = ({
showModal,
botName,
onClose,
}) => {
const { apiUrl } = useApiUrlStore()
const [strategyData, setStrategyData] = useState<UserStrategyDetailsViewModel | null>(null)
const [loading, setLoading] = useState<boolean>(false)
useEffect(() => {
if (showModal && botName) {
fetchStrategyData()
}
}, [showModal, botName])
const fetchStrategyData = async () => {
if (!botName) return
setLoading(true)
const client = new DataClient({}, apiUrl)
try {
const data = await client.data_GetUserStrategy("Oda", botName)
setStrategyData(data)
} catch (error) {
console.error('Error fetching strategy data:', error)
} finally {
setLoading(false)
}
}
return (
<Modal
showModal={showModal}
onClose={onClose}
titleHeader={`Trades for ${botName}`}
>
<div className="mt-4">
{loading ? (
<div className="flex justify-center">
<div className="loading loading-spinner loading-lg"></div>
</div>
) : strategyData ? (
<div>
<div className="mb-4">
<p className="font-bold">Strategy: {strategyData.strategyName}</p>
<p>Win Rate: {strategyData.winRate?.toFixed(2)}%</p>
<p>PnL: {strategyData.pnL?.toFixed(2)} $</p>
<p>Wins: {strategyData.wins} / Losses: {strategyData.losses}</p>
<p>Total Volume Traded: {strategyData.totalVolumeTraded?.toFixed(2)} $</p>
<p>Volume Last 24H: {strategyData.volumeLast24H?.toFixed(2)} $</p>
</div>
<div className="overflow-x-auto">
<table className="table table-compact w-full">
<thead>
<tr>
<th>Date</th>
<th>Direction</th>
<th>Status</th>
<th>Entry Price</th>
<th>Quantity</th>
<th>PnL</th>
</tr>
</thead>
<tbody>
{strategyData.positions && strategyData.positions.length > 0 ? (
strategyData.positions.map((position: Position) => (
<tr key={position.identifier}>
<td>{new Date(position.date).toLocaleString()}</td>
<td className={position.originDirection === 'Long' ? 'text-success' : 'text-error'}>
{position.originDirection}
</td>
<td>{position.status}</td>
<td>{position.open.price.toFixed(2)}</td>
<td>{position.open.quantity.toFixed(4)}</td>
<td className={position.profitAndLoss?.realized && position.profitAndLoss.realized > 0 ? 'text-success' : 'text-error'}>
{position.profitAndLoss?.realized?.toFixed(2) || '0.00'} $
</td>
</tr>
))
) : (
<tr>
<td colSpan={6} className="text-center">No trades found</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
) : (
<p className="text-center">No data available</p>
)}
</div>
</Modal>
)
}
export default TradesModal

View File

@@ -941,6 +941,189 @@ export class DataClient extends AuthorizedApiBase {
} }
return Promise.resolve<Candle[]>(null as any); return Promise.resolve<Candle[]>(null as any);
} }
data_GetStrategiesStatistics(): Promise<StrategiesStatisticsViewModel> {
let url_ = this.baseUrl + "/Data/GetStrategiesStatistics";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processData_GetStrategiesStatistics(_response);
});
}
protected processData_GetStrategiesStatistics(response: Response): Promise<StrategiesStatisticsViewModel> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as StrategiesStatisticsViewModel;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<StrategiesStatisticsViewModel>(null as any);
}
data_GetTopStrategies(): Promise<TopStrategiesViewModel> {
let url_ = this.baseUrl + "/Data/GetTopStrategies";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processData_GetTopStrategies(_response);
});
}
protected processData_GetTopStrategies(response: Response): Promise<TopStrategiesViewModel> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as TopStrategiesViewModel;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<TopStrategiesViewModel>(null as any);
}
data_GetUserStrategies(agentName: string | null | undefined): Promise<UserStrategyDetailsViewModel[]> {
let url_ = this.baseUrl + "/Data/GetUserStrategies?";
if (agentName !== undefined && agentName !== null)
url_ += "agentName=" + encodeURIComponent("" + agentName) + "&";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processData_GetUserStrategies(_response);
});
}
protected processData_GetUserStrategies(response: Response): Promise<UserStrategyDetailsViewModel[]> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as UserStrategyDetailsViewModel[];
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<UserStrategyDetailsViewModel[]>(null as any);
}
data_GetUserStrategy(agentName: string | null | undefined, strategyName: string | null | undefined): Promise<UserStrategyDetailsViewModel> {
let url_ = this.baseUrl + "/Data/GetUserStrategy?";
if (agentName !== undefined && agentName !== null)
url_ += "agentName=" + encodeURIComponent("" + agentName) + "&";
if (strategyName !== undefined && strategyName !== null)
url_ += "strategyName=" + encodeURIComponent("" + strategyName) + "&";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processData_GetUserStrategy(_response);
});
}
protected processData_GetUserStrategy(response: Response): Promise<UserStrategyDetailsViewModel> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as UserStrategyDetailsViewModel;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<UserStrategyDetailsViewModel>(null as any);
}
data_GetPlatformSummary(timeFilter: string | null | undefined): Promise<PlatformSummaryViewModel> {
let url_ = this.baseUrl + "/Data/GetPlatformSummary?";
if (timeFilter !== undefined && timeFilter !== null)
url_ += "timeFilter=" + encodeURIComponent("" + timeFilter) + "&";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processData_GetPlatformSummary(_response);
});
}
protected processData_GetPlatformSummary(response: Response): Promise<PlatformSummaryViewModel> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as PlatformSummaryViewModel;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<PlatformSummaryViewModel>(null as any);
}
} }
export class MoneyManagementClient extends AuthorizedApiBase { export class MoneyManagementClient extends AuthorizedApiBase {
@@ -2338,7 +2521,7 @@ export interface Position {
signalIdentifier?: string | null; signalIdentifier?: string | null;
identifier: string; identifier: string;
initiator: PositionInitiator; initiator: PositionInitiator;
user?: User | null; user: User;
} }
export enum TradeDirection { export enum TradeDirection {
@@ -2642,8 +2825,6 @@ export interface TradingBot {
export interface OpenPositionManuallyRequest { export interface OpenPositionManuallyRequest {
botName?: string | null; botName?: string | null;
direction?: TradeDirection; direction?: TradeDirection;
stopLossPrice?: number;
takeProfitPrice?: number;
} }
export interface SpotlightOverview { export interface SpotlightOverview {
@@ -2690,6 +2871,60 @@ export interface TickerSignal {
oneDay: Signal[]; oneDay: Signal[];
} }
export interface StrategiesStatisticsViewModel {
totalStrategiesRunning?: number;
changeInLast24Hours?: number;
}
export interface TopStrategiesViewModel {
topStrategies?: StrategyPerformance[] | null;
}
export interface StrategyPerformance {
strategyName?: string | null;
pnL?: number;
}
export interface UserStrategyDetailsViewModel {
name?: string | null;
strategyName?: string | null;
state?: string | null;
pnL?: number;
roiPercentage?: number;
roiLast24H?: number;
runtime?: Date;
winRate?: number;
totalVolumeTraded?: number;
volumeLast24H?: number;
wins?: number;
losses?: number;
positions?: Position[] | null;
}
export interface PlatformSummaryViewModel {
totalAgents?: number;
totalActiveStrategies?: number;
totalPlatformPnL?: number;
totalPlatformVolume?: number;
totalPlatformVolumeLast24h?: number;
agentSummaries?: AgentSummaryViewModel[] | null;
timeFilter?: string | null;
}
export interface AgentSummaryViewModel {
username?: string | null;
totalPnL?: number;
pnLLast24h?: number;
totalROI?: number;
roiLast24h?: number;
wins?: number;
losses?: number;
averageWinRate?: number;
activeStrategiesCount?: number;
totalVolume?: number;
volumeLast24h?: number;
}
export enum RiskLevel { export enum RiskLevel {
Low = "Low", Low = "Low",
Medium = "Medium", Medium = "Medium",

View File

@@ -1,22 +1,14 @@
import { EyeIcon, PlayIcon, StopIcon, TrashIcon, PlusCircleIcon } from '@heroicons/react/solid' import {ChartBarIcon, EyeIcon, PlayIcon, PlusCircleIcon, StopIcon, TrashIcon} from '@heroicons/react/solid'
import React, { useState } from 'react' import React, {useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore' import useApiUrlStore from '../../app/store/apiStore'
import { import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
CardPosition,
CardSignal,
CardText,
Toast,
} from '../../components/mollecules'
import ManualPositionModal from '../../components/mollecules/ManualPositionModal' import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
import { TradeChart } from '../../components/organism' import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
import { BotClient } from '../../generated/ManagingApi' import {TradeChart} from '../../components/organism'
import type { import type {BotType, MoneyManagement, TradingBot,} from '../../generated/ManagingApi'
BotType, import {BotClient} from '../../generated/ManagingApi'
MoneyManagement, import type {IBotList} from '../../global/type'
TradingBot,
} from '../../generated/ManagingApi'
import type { IBotList } from '../../global/type'
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal' import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
function baseBadgeClass(isOutlined = false) { function baseBadgeClass(isOutlined = false) {
@@ -44,6 +36,8 @@ const BotList: React.FC<IBotList> = ({ list }) => {
useState<MoneyManagement>() useState<MoneyManagement>()
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 [selectedBotForTrades, setSelectedBotForTrades] = useState<string | null>(null)
function getIsForWatchingBadge(isForWatchingOnly: boolean, name: string) { function getIsForWatchingBadge(isForWatchingOnly: boolean, name: string) {
const classes = const classes =
@@ -135,11 +129,27 @@ const BotList: React.FC<IBotList> = ({ list }) => {
) )
} }
function getTradesBadge(botName: string) {
const classes = baseBadgeClass() + ' bg-secondary'
return (
<button className={classes} onClick={() => openTradesModal(botName)}>
<p className="text-primary-content flex">
<ChartBarIcon width={15}></ChartBarIcon>
</p>
</button>
)
}
function openManualPositionModal(botName: string) { function openManualPositionModal(botName: string) {
setSelectedBotForManualPosition(botName) setSelectedBotForManualPosition(botName)
setShowManualPositionModal(true) setShowManualPositionModal(true)
} }
function openTradesModal(botName: string) {
setSelectedBotForTrades(botName)
setShowTradesModal(true)
}
function toggleBotStatus(status: string, name: string, botType: BotType) { function toggleBotStatus(status: string, name: string, botType: BotType) {
const isUp = status == 'Up' const isUp = status == 'Up'
const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot') const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot')
@@ -246,6 +256,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)}
</div> </div>
</div> </div>
</div> </div>
@@ -267,6 +278,14 @@ const BotList: React.FC<IBotList> = ({ list }) => {
setSelectedBotForManualPosition(null) setSelectedBotForManualPosition(null)
}} }}
/> />
<TradesModal
showModal={showTradesModal}
botName={selectedBotForTrades}
onClose={() => {
setShowTradesModal(false)
setSelectedBotForTrades(null)
}}
/>
</div> </div>
) )
} }