Display data for bots
This commit is contained in:
@@ -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
|
||||
@@ -941,6 +941,189 @@ export class DataClient extends AuthorizedApiBase {
|
||||
}
|
||||
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 {
|
||||
@@ -2338,7 +2521,7 @@ export interface Position {
|
||||
signalIdentifier?: string | null;
|
||||
identifier: string;
|
||||
initiator: PositionInitiator;
|
||||
user?: User | null;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export enum TradeDirection {
|
||||
@@ -2642,8 +2825,6 @@ export interface TradingBot {
|
||||
export interface OpenPositionManuallyRequest {
|
||||
botName?: string | null;
|
||||
direction?: TradeDirection;
|
||||
stopLossPrice?: number;
|
||||
takeProfitPrice?: number;
|
||||
}
|
||||
|
||||
export interface SpotlightOverview {
|
||||
@@ -2690,6 +2871,60 @@ export interface TickerSignal {
|
||||
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 {
|
||||
Low = "Low",
|
||||
Medium = "Medium",
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
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 useApiUrlStore from '../../app/store/apiStore'
|
||||
import {
|
||||
CardPosition,
|
||||
CardSignal,
|
||||
CardText,
|
||||
Toast,
|
||||
} from '../../components/mollecules'
|
||||
import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
|
||||
import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
|
||||
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
|
||||
import {TradeChart} from '../../components/organism'
|
||||
import type {BotType, MoneyManagement, TradingBot,} from '../../generated/ManagingApi'
|
||||
import {BotClient} from '../../generated/ManagingApi'
|
||||
import type {
|
||||
BotType,
|
||||
MoneyManagement,
|
||||
TradingBot,
|
||||
} from '../../generated/ManagingApi'
|
||||
import type {IBotList} from '../../global/type'
|
||||
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
|
||||
|
||||
@@ -44,6 +36,8 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
useState<MoneyManagement>()
|
||||
const [showManualPositionModal, setShowManualPositionModal] = useState(false)
|
||||
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) {
|
||||
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) {
|
||||
setSelectedBotForManualPosition(botName)
|
||||
setShowManualPositionModal(true)
|
||||
}
|
||||
|
||||
function openTradesModal(botName: string) {
|
||||
setSelectedBotForTrades(botName)
|
||||
setShowTradesModal(true)
|
||||
}
|
||||
|
||||
function toggleBotStatus(status: string, name: string, botType: BotType) {
|
||||
const isUp = status == 'Up'
|
||||
const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot')
|
||||
@@ -246,6 +256,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
<div className={baseBadgeClass(true)}>
|
||||
PNL {bot.profitAndLoss.toFixed(2).toString()} $
|
||||
</div>
|
||||
{getTradesBadge(bot.name)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,6 +278,14 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
setSelectedBotForManualPosition(null)
|
||||
}}
|
||||
/>
|
||||
<TradesModal
|
||||
showModal={showTradesModal}
|
||||
botName={selectedBotForTrades}
|
||||
onClose={() => {
|
||||
setShowTradesModal(false)
|
||||
setSelectedBotForTrades(null)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user