Trading bot grain (#33)
* Trading bot Grain * Fix a bit more of the trading bot * Advance on the tradingbot grain * Fix build * Fix db script * Fix user login * Fix a bit backtest * Fix cooldown and backtest * start fixing bot start * Fix startup * Setup local db * Fix build and update candles and scenario * Add bot registry * Add reminder * Updateing the grains * fix bootstraping * Save stats on tick * Save bot data every tick * Fix serialization * fix save bot stats * Fix get candles * use dict instead of list for position * Switch hashset to dict * Fix a bit * Fix bot launch and bot view * add migrations * Remove the tolist * Add agent grain * Save agent summary * clean * Add save bot * Update get bots * Add get bots * Fix stop/restart * fix Update config * Update scanner table on new backtest saved * Fix backtestRowDetails.tsx * Fix agentIndex * Update agentIndex * Fix more things * Update user cache * Fix * Fix account load/start/restart/run
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import {create} from 'zustand'
|
||||
|
||||
import type {Scenario} from '../../generated/ManagingApi'
|
||||
import type {LightScenario} from '../../generated/ManagingApi'
|
||||
|
||||
type CustomScenarioStore = {
|
||||
setCustomScenario: (custom: Scenario | null) => void
|
||||
scenario: Scenario | null
|
||||
setCustomScenario: (custom: LightScenario | null) => void
|
||||
scenario: LightScenario | null
|
||||
}
|
||||
|
||||
export const useCustomScenario = create<CustomScenarioStore>((set) => ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ArrowDownIcon from '@heroicons/react/solid/ArrowDownIcon'
|
||||
import ArrowUpIcon from '@heroicons/react/solid/ArrowUpIcon'
|
||||
|
||||
import {Position, TradeDirection} from '../../../generated/ManagingApi'
|
||||
import type {ICardPosition, ICardSignal, ICardText} from '../../../global/type'
|
||||
import {LightSignal, Position, TradeDirection} from '../../../generated/ManagingApi'
|
||||
import type {ICardPosition, ICardSignal, ICardText} from '../../../global/type.tsx'
|
||||
|
||||
function getItemTextHeaderClass() {
|
||||
return 'text-xs opacity-50 '
|
||||
@@ -38,7 +38,7 @@ export function CardPosition({ positions, positivePosition }: ICardPosition) {
|
||||
display="initial"
|
||||
></ArrowUpIcon>{' '}
|
||||
{
|
||||
positions.filter((p) => p.originDirection == TradeDirection.Short)
|
||||
positions.filter((p: Position) => p.originDirection == TradeDirection.Short)
|
||||
.length
|
||||
}{' '}
|
||||
<ArrowDownIcon
|
||||
@@ -58,13 +58,13 @@ export function CardSignal({ signals }: ICardSignal) {
|
||||
<div>
|
||||
<p className={getItemTextHeaderClass()}>Signals</p>
|
||||
<p className={getItemTextValueClass()}>
|
||||
{signals.filter((p) => p.direction == TradeDirection.Long).length}{' '}
|
||||
{signals.filter((p: LightSignal) => p.direction == TradeDirection.Long).length}{' '}
|
||||
<ArrowUpIcon
|
||||
width="10"
|
||||
className="text-primary inline"
|
||||
display="initial"
|
||||
></ArrowUpIcon>{' '}
|
||||
{signals.filter((p) => p.direction == TradeDirection.Short).length}{' '}
|
||||
{signals.filter((p: LightSignal) => p.direction == TradeDirection.Short).length}{' '}
|
||||
<ArrowDownIcon
|
||||
width="10"
|
||||
className="text-accent inline"
|
||||
|
||||
@@ -9,6 +9,7 @@ import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {UserClient} from '../../../generated/ManagingApi'
|
||||
import Logo from '../../../assets/img/logo.png'
|
||||
import {Loader} from '../../atoms'
|
||||
import useCookie from '../../../hooks/useCookie'
|
||||
|
||||
const navigation = [
|
||||
{ href: '/bots', name: 'Bots' },
|
||||
@@ -47,13 +48,17 @@ const GlobalLoader = () => {
|
||||
const PrivyWalletButton = () => {
|
||||
const { login, logout, authenticated, user } = usePrivy()
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const { getCookie } = useCookie()
|
||||
const api = new UserClient({}, apiUrl)
|
||||
|
||||
// Get JWT token from cookies
|
||||
const jwtToken = getCookie('token')
|
||||
|
||||
// Fetch user information from the API
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: ['user'],
|
||||
queryFn: () => api.user_GetCurrentUser(),
|
||||
enabled: authenticated, // Only fetch when authenticated
|
||||
enabled: authenticated && !!jwtToken, // Only fetch when authenticated AND JWT token exists
|
||||
})
|
||||
|
||||
if (!authenticated) {
|
||||
|
||||
@@ -1,212 +1,116 @@
|
||||
import {ArrowDownIcon, ArrowUpIcon, ChevronDownIcon, ChevronRightIcon, PlayIcon,} from '@heroicons/react/solid'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import {Hub} from '../../../app/providers/Hubs'
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import type {Account, TradingBotResponse} from '../../../generated/ManagingApi'
|
||||
import {AccountClient, BotClient, TradeDirection, TradeStatus,} from '../../../generated/ManagingApi'
|
||||
import {IndicatorsDisplay, SelectColumnFilter, Table} from '../../mollecules'
|
||||
import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid'
|
||||
import React from 'react'
|
||||
import useBots from '../../../hooks/useBots'
|
||||
import {SelectColumnFilter, Table} from '../../mollecules'
|
||||
import StatusBadge from '../StatusBadge/StatusBadge'
|
||||
import Summary from '../Trading/Summary'
|
||||
import BotRowDetails from './botRowDetails'
|
||||
|
||||
export default function ActiveBots() {
|
||||
const [bots, setBots] = useState<TradingBotResponse[]>([])
|
||||
const [accounts, setAccounts] = useState<Account[]>([])
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const {data: bots = []} = useBots({})
|
||||
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Cell: ({ row }: any) => (
|
||||
// Use Cell to render an expander for each row.
|
||||
// We can use the getToggleRowExpandedProps prop-getter
|
||||
// to build the expander.
|
||||
<span {...row.getToggleRowExpandedProps()}>
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Cell: ({row}: any) => (
|
||||
// Use Cell to render an expander for each row.
|
||||
// We can use the getToggleRowExpandedProps prop-getter
|
||||
// to build the expander.
|
||||
<span {...row.getToggleRowExpandedProps()}>
|
||||
{row.isExpanded ? (
|
||||
<ChevronDownIcon></ChevronDownIcon>
|
||||
<ChevronDownIcon></ChevronDownIcon>
|
||||
) : (
|
||||
<ChevronRightIcon></ChevronRightIcon>
|
||||
<ChevronRightIcon></ChevronRightIcon>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
),
|
||||
|
||||
// Make sure it has an ID
|
||||
Header: ({ getToggleAllRowsExpandedProps, isAllRowsExpanded }: any) => (
|
||||
<span {...getToggleAllRowsExpandedProps()}>
|
||||
// Make sure it has an ID
|
||||
Header: ({getToggleAllRowsExpandedProps, isAllRowsExpanded}: any) => (
|
||||
<span {...getToggleAllRowsExpandedProps()}>
|
||||
{isAllRowsExpanded ? 'v' : '>'}
|
||||
</span>
|
||||
),
|
||||
// Build our expander column
|
||||
id: 'expander',
|
||||
},
|
||||
{
|
||||
Cell: ({ cell }: any) => (
|
||||
<>
|
||||
<StatusBadge
|
||||
status={cell.row.values.status}
|
||||
isForWatchOnly={cell.row.values.isForWatchingOnly}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
disableFilters: true,
|
||||
disableSortBy: true,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
accessor: 'isForWatchingOnly',
|
||||
disableFilters: true,
|
||||
disableSortBy: true,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
Filter: SelectColumnFilter,
|
||||
Header: 'Ticker',
|
||||
accessor: 'config.ticker',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'config.name',
|
||||
},
|
||||
{
|
||||
Filter: SelectColumnFilter,
|
||||
Header: 'Timeframe',
|
||||
accessor: 'config.timeframe',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: 'Indicators',
|
||||
accessor: 'config.scenario.indicators',
|
||||
Cell: ({cell}: any) => {
|
||||
const bot = cell.row.original as TradingBotResponse;
|
||||
const indicators = bot.config?.scenario?.indicators || [];
|
||||
|
||||
return (
|
||||
<IndicatorsDisplay indicators={indicators} />
|
||||
);
|
||||
},
|
||||
disableFilters: true,
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Cell: ({ cell }: any) => (
|
||||
<>
|
||||
),
|
||||
// Build our expander column
|
||||
id: 'expander',
|
||||
},
|
||||
{
|
||||
<>
|
||||
{
|
||||
cell.row.values.positions.filter(
|
||||
(p: any) => p.originDirection == TradeDirection.Long
|
||||
).length
|
||||
}{' '}
|
||||
<ArrowUpIcon
|
||||
width="10"
|
||||
className="text-primary inline"
|
||||
display="initial"
|
||||
></ArrowUpIcon>
|
||||
{' | '}
|
||||
{
|
||||
cell.row.values.positions.filter(
|
||||
(p: any) => p.originDirection == TradeDirection.Short
|
||||
).length
|
||||
}{' '}
|
||||
<ArrowDownIcon
|
||||
width="10"
|
||||
className="text-accent inline"
|
||||
display="initial"
|
||||
></ArrowDownIcon>{' '}
|
||||
{
|
||||
cell.row.values.positions.filter(
|
||||
(p: any) => p.status == TradeStatus.Filled
|
||||
).length
|
||||
}{' '}
|
||||
<PlayIcon
|
||||
width="10"
|
||||
className="text-accent inline"
|
||||
display="initial"
|
||||
></PlayIcon>{' '}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
Cell: ({cell}: any) => (
|
||||
<>
|
||||
<StatusBadge
|
||||
status={cell.row.values.status}
|
||||
isForWatchOnly={cell.row.values.isForWatchingOnly}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
disableFilters: true,
|
||||
disableSortBy: true,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
accessor: 'isForWatchingOnly',
|
||||
disableFilters: true,
|
||||
disableSortBy: true,
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
Filter: SelectColumnFilter,
|
||||
Header: 'Ticker',
|
||||
accessor: 'ticker',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
},
|
||||
{
|
||||
Filter: SelectColumnFilter,
|
||||
Header: 'Timeframe',
|
||||
accessor: 'timeframe',
|
||||
disableSortBy: true,
|
||||
},
|
||||
{
|
||||
Cell: ({cell}: any) => <>{cell.row.values.winRate} %</>,
|
||||
Header: 'Winrate',
|
||||
accessor: 'winRate',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Cell: ({cell}: any) => <>{cell.row.values.profitAndLoss} $</>,
|
||||
Header: 'PNL',
|
||||
accessor: 'profitAndLoss',
|
||||
disableFilters: true,
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
const renderRowSubComponent = React.useCallback(
|
||||
({row}: any) => (
|
||||
<>
|
||||
<BotRowDetails
|
||||
bot={row.original}
|
||||
></BotRowDetails>
|
||||
</>
|
||||
),
|
||||
Header: 'Positions',
|
||||
accessor: 'positions',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Cell: ({ cell }) => <>{cell.row.values.winRate} %</>,
|
||||
Header: 'Winrate',
|
||||
accessor: 'winRate',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Cell: ({ cell }) => <>{cell.row.values.profitAndLoss} $</>,
|
||||
Header: 'PNL',
|
||||
accessor: 'profitAndLoss',
|
||||
disableFilters: true,
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
[]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setupHubConnection().then(() => {
|
||||
if (bots.length == 0) {
|
||||
const client = new BotClient({}, apiUrl)
|
||||
client.bot_GetActiveBots().then((data) => {
|
||||
setBots(data)
|
||||
})
|
||||
}
|
||||
})
|
||||
const client = new AccountClient({}, apiUrl)
|
||||
client.account_GetAccounts().then((data) => {
|
||||
setAccounts(data)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const setupHubConnection = async () => {
|
||||
const hub = new Hub('bothub', apiUrl).hub
|
||||
|
||||
hub.on('BotsSubscription', (data: TradingBotResponse[]) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
'bot List',
|
||||
bots.map((bot: TradingBotResponse) => {
|
||||
return bot.config.name
|
||||
})
|
||||
)
|
||||
setBots(data)
|
||||
})
|
||||
|
||||
return hub
|
||||
}
|
||||
|
||||
const renderRowSubComponent = React.useCallback(
|
||||
({ row }: any) => (
|
||||
<>
|
||||
<BotRowDetails
|
||||
bot={row.original}
|
||||
></BotRowDetails>
|
||||
</>
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap">
|
||||
<Summary bots={bots} accounts={accounts}></Summary>
|
||||
</div>
|
||||
<div className="flex flex-wrap">
|
||||
<Table
|
||||
columns={columns}
|
||||
data={bots}
|
||||
renderRowSubCompontent={renderRowSubComponent}
|
||||
hiddenColumns={['isForWatchingOnly']}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap">
|
||||
<Summary bots={bots}></Summary>
|
||||
</div>
|
||||
<div className="flex flex-wrap">
|
||||
<Table
|
||||
columns={columns}
|
||||
data={bots}
|
||||
renderRowSubCompontent={renderRowSubComponent}
|
||||
hiddenColumns={['isForWatchingOnly']}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import {
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotType,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
@@ -379,16 +378,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
</option>
|
||||
</select>
|
||||
</FormInput>
|
||||
|
||||
<FormInput label="Bot Type" htmlFor="botType">
|
||||
<select className="select select-bordered w-full" {...register('botType')}>
|
||||
{[BotType.ScalpingBot, BotType.FlippingBot].map((item) => (
|
||||
<option key={item} value={item}>
|
||||
{item}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Losing streak info */}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DataClient,
|
||||
GetCandlesWithIndicatorsRequest,
|
||||
IndicatorType,
|
||||
LightSignal,
|
||||
Position,
|
||||
SignalType
|
||||
} from '../../../generated/ManagingApi'
|
||||
@@ -42,7 +43,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
if (backtest.candles && backtest.candles.length > 0) {
|
||||
return {
|
||||
candles: backtest.candles,
|
||||
indicatorsValues: backtest.indicatorsValues || {}
|
||||
indicatorsValues: {} // Default empty object since Backtest doesn't have indicatorsValues
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,17 +84,17 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
|
||||
// Use the data from query or fallback to backtest data
|
||||
const candles = candlesData?.candles || currentBacktest.candles || [];
|
||||
const indicatorsValues = candlesData?.indicatorsValues || currentBacktest.indicatorsValues || {};
|
||||
const indicatorsValues = candlesData?.indicatorsValues || {};
|
||||
|
||||
// Only destructure these properties if we have full backtest data
|
||||
const positions = fullBacktestData?.positions || [];
|
||||
const walletBalances = fullBacktestData?.walletBalances || [];
|
||||
const signals = fullBacktestData?.signals || [];
|
||||
const statistics = fullBacktestData?.statistics;
|
||||
// Convert positions and signals objects to arrays
|
||||
const positionsArray: Position[] = Object.values(currentBacktest.positions || {});
|
||||
const signalsArray: LightSignal[] = Object.values(currentBacktest.signals || {});
|
||||
const walletBalances = currentBacktest.walletBalances || [];
|
||||
const statistics = currentBacktest.statistics;
|
||||
const config = currentBacktest.config;
|
||||
|
||||
// Helper function to calculate position open time in hours
|
||||
const calculateOpenTimeInHours = (position: Position) => {
|
||||
const calculateOpenTimeInHours = (position: Position): number => {
|
||||
const openDate = new Date(position.Open.date);
|
||||
let closeDate: Date | null = null;
|
||||
|
||||
@@ -116,15 +117,15 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
};
|
||||
|
||||
// Calculate average open time for winning positions
|
||||
const getAverageOpenTimeWinning = () => {
|
||||
const winningPositions = positions.filter((p) => {
|
||||
const getAverageOpenTimeWinning = (): string => {
|
||||
const winningPositions = positionsArray.filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0;
|
||||
return realized > 0;
|
||||
});
|
||||
|
||||
if (winningPositions.length === 0) return "0.00";
|
||||
|
||||
const totalHours = winningPositions.reduce((sum, position) => {
|
||||
const totalHours = winningPositions.reduce((sum: number, position: Position) => {
|
||||
return sum + calculateOpenTimeInHours(position);
|
||||
}, 0);
|
||||
|
||||
@@ -133,15 +134,15 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
};
|
||||
|
||||
// Calculate average open time for losing positions
|
||||
const getAverageOpenTimeLosing = () => {
|
||||
const losingPositions = positions.filter((p) => {
|
||||
const getAverageOpenTimeLosing = (): string => {
|
||||
const losingPositions = positionsArray.filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0;
|
||||
return realized <= 0;
|
||||
});
|
||||
|
||||
if (losingPositions.length === 0) return "0.00";
|
||||
|
||||
const totalHours = losingPositions.reduce((sum, position) => {
|
||||
const totalHours = losingPositions.reduce((sum: number, position: Position) => {
|
||||
return sum + calculateOpenTimeInHours(position);
|
||||
}, 0);
|
||||
|
||||
@@ -150,25 +151,25 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
};
|
||||
|
||||
// Calculate maximum open time for winning positions
|
||||
const getMaxOpenTimeWinning = () => {
|
||||
const winningPositions = positions.filter((p) => {
|
||||
const getMaxOpenTimeWinning = (): string => {
|
||||
const winningPositions = positionsArray.filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0;
|
||||
return realized > 0;
|
||||
});
|
||||
|
||||
if (winningPositions.length === 0) return "0.00";
|
||||
|
||||
const openTimes = winningPositions.map(position => calculateOpenTimeInHours(position));
|
||||
const openTimes = winningPositions.map((position: Position) => calculateOpenTimeInHours(position));
|
||||
const maxHours = Math.max(...openTimes);
|
||||
return maxHours.toFixed(2);
|
||||
};
|
||||
|
||||
// Calculate median opening time for all positions
|
||||
const getMedianOpenTime = () => {
|
||||
if (positions.length === 0) return "0.00";
|
||||
const getMedianOpenTime = (): string => {
|
||||
if (positionsArray.length === 0) return "0.00";
|
||||
|
||||
const openTimes = positions.map(position => calculateOpenTimeInHours(position));
|
||||
const sortedTimes = openTimes.sort((a, b) => a - b);
|
||||
const openTimes = positionsArray.map((position: Position) => calculateOpenTimeInHours(position));
|
||||
const sortedTimes = openTimes.sort((a: number, b: number) => a - b);
|
||||
|
||||
const mid = Math.floor(sortedTimes.length / 2);
|
||||
const median = sortedTimes.length % 2 === 0
|
||||
@@ -179,10 +180,10 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
};
|
||||
|
||||
// Calculate total volume traded with leverage
|
||||
const getTotalVolumeTraded = () => {
|
||||
const getTotalVolumeTraded = (): number => {
|
||||
let totalVolume = 0;
|
||||
|
||||
positions.forEach((position) => {
|
||||
positionsArray.forEach((position: Position) => {
|
||||
// Calculate volume for open trade
|
||||
const openLeverage = position.Open.leverage || 1;
|
||||
const openVolume = position.Open.quantity * position.Open.price * openLeverage;
|
||||
@@ -211,7 +212,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
};
|
||||
|
||||
// Calculate estimated UI fee (0.02% of total volume)
|
||||
const getEstimatedUIFee = () => {
|
||||
const getEstimatedUIFee = (): number => {
|
||||
const totalVolume = getTotalVolumeTraded();
|
||||
const uiFeePercentage = 0.001; // 0.1%
|
||||
return totalVolume * uiFeePercentage;
|
||||
@@ -219,14 +220,14 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
|
||||
// Calculate recommended cooldown based on positions that fail after a win
|
||||
const getCooldownRecommendations = () => {
|
||||
if (positions?.length < 2 || !candles || candles?.length < 2) {
|
||||
if (positionsArray.length < 2 || !candles || candles.length < 2) {
|
||||
return { percentile75: "0", average: "0", median: "0" };
|
||||
}
|
||||
|
||||
// Determine candle timeframe in milliseconds
|
||||
const candleTimeframeMs = new Date(candles[1].date).getTime() - new Date(candles[0].date).getTime();
|
||||
|
||||
const sortedPositions = [...positions].sort((a, b) => {
|
||||
const sortedPositions = [...positionsArray].sort((a: Position, b: Position) => {
|
||||
const dateA = new Date(a.Open.date).getTime();
|
||||
const dateB = new Date(b.Open.date).getTime();
|
||||
return dateA - dateB;
|
||||
@@ -271,12 +272,12 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
}
|
||||
|
||||
// Calculate the 75th percentile
|
||||
const sortedGaps = [...failAfterWinGaps].sort((a, b) => a - b);
|
||||
const sortedGaps = [...failAfterWinGaps].sort((a: number, b: number) => a - b);
|
||||
const percentile75Index = Math.floor(sortedGaps.length * 0.75);
|
||||
const percentile75 = sortedGaps[percentile75Index] || 0;
|
||||
|
||||
// Calculate the average
|
||||
const sum = failAfterWinGaps.reduce((acc, gap) => acc + gap, 0);
|
||||
const sum = failAfterWinGaps.reduce((acc: number, gap: number) => acc + gap, 0);
|
||||
const average = sum / failAfterWinGaps.length;
|
||||
|
||||
// Calculate the median
|
||||
@@ -295,13 +296,13 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
const cooldownRecommendations = getCooldownRecommendations();
|
||||
|
||||
// Calculate average trades per day
|
||||
const getAverageTradesPerDay = () => {
|
||||
if (positions.length === 0) return "0.00";
|
||||
const getAverageTradesPerDay = (): string => {
|
||||
if (positionsArray.length === 0) return "0.00";
|
||||
|
||||
// Get all trade dates and sort them
|
||||
const tradeDates = positions.map(position => new Date(position.Open.date)).sort((a, b) => a.getTime() - b.getTime());
|
||||
const tradeDates = positionsArray.map((position: Position) => new Date(position.Open.date)).sort((a: Date, b: Date) => a.getTime() - b.getTime());
|
||||
|
||||
if (tradeDates.length < 2) return positions.length.toString();
|
||||
if (tradeDates.length < 2) return positionsArray.length.toString();
|
||||
|
||||
// Calculate the date range in days
|
||||
const firstTradeDate = tradeDates[0];
|
||||
@@ -309,7 +310,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
const diffInMs = lastTradeDate.getTime() - firstTradeDate.getTime();
|
||||
const diffInDays = Math.max(1, diffInMs / (1000 * 60 * 60 * 24)); // Ensure at least 1 day
|
||||
|
||||
const averageTradesPerDay = positions.length / diffInDays;
|
||||
const averageTradesPerDay = positionsArray.length / diffInDays;
|
||||
return averageTradesPerDay.toFixed(2);
|
||||
};
|
||||
|
||||
@@ -327,19 +328,19 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
<div className="grid grid-cols-4 p-5">
|
||||
<CardPosition
|
||||
positivePosition={true}
|
||||
positions={positions.filter((p) => {
|
||||
positions={positionsArray.filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized > 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
<CardPosition
|
||||
positivePosition={false}
|
||||
positions={positions.filter((p) => {
|
||||
positions={positionsArray.filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized <= 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
<CardPositionItem positions={positions}></CardPositionItem>
|
||||
<CardPositionItem positions={positionsArray}></CardPositionItem>
|
||||
<CardText
|
||||
title="Max Drowdown"
|
||||
content={
|
||||
@@ -431,10 +432,10 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
<figure className="w-full">
|
||||
<TradeChart
|
||||
candles={candles}
|
||||
positions={positions}
|
||||
positions={positionsArray}
|
||||
walletBalances={walletBalances}
|
||||
indicatorsValues={indicatorsValues}
|
||||
signals={signals}
|
||||
signals={signalsArray}
|
||||
height={1000}
|
||||
></TradeChart>
|
||||
</figure>
|
||||
|
||||
@@ -135,11 +135,12 @@ interface BacktestTableProps {
|
||||
displaySummary?: boolean
|
||||
onSortChange?: (sortBy: string, sortOrder: 'asc' | 'desc') => void
|
||||
currentSort?: { sortBy: string; sortOrder: 'asc' | 'desc' }
|
||||
onBacktestDeleted?: () => void // Callback when a backtest is deleted
|
||||
}
|
||||
|
||||
|
||||
|
||||
const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortChange, currentSort}) => {
|
||||
const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortChange, currentSort, onBacktestDeleted}) => {
|
||||
const [rows, setRows] = useState<LightBacktestResponse[]>([])
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
const {removeBacktest} = useBacktestStore()
|
||||
@@ -213,6 +214,10 @@ const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching, onSortCh
|
||||
t.update('success', 'Backtest deleted')
|
||||
// Remove the deleted backtest from the store
|
||||
removeBacktest(id)
|
||||
// Call the callback to invalidate queries
|
||||
if (onBacktestDeleted) {
|
||||
onBacktestDeleted()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', err)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import type {Indicator, Scenario} from '../../../generated/ManagingApi'
|
||||
import type {LightIndicator, LightScenario} from '../../../generated/ManagingApi'
|
||||
import {IndicatorType} from '../../../generated/ManagingApi'
|
||||
import FormInput from '../../mollecules/FormInput/FormInput'
|
||||
import {useCustomScenario} from '../../../app/store/customScenario'
|
||||
|
||||
type ICustomScenario = {
|
||||
onCreateScenario: (scenario: Scenario) => void
|
||||
onCreateScenario: (scenario: LightScenario) => void
|
||||
showCustomScenario: boolean
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
|
||||
const [name, setName] = useState<string>(scenario?.name || 'Custom Scenario')
|
||||
const [loopbackPeriod, setLoopbackPeriod] = useState<number>(scenario?.loopbackPeriod || 1)
|
||||
const [indicators, setIndicators] = useState<Indicator[]>(scenario?.indicators || [])
|
||||
const [indicators, setIndicators] = useState<LightIndicator[]>(scenario?.indicators || [])
|
||||
|
||||
// Available indicator types with their required parameters
|
||||
const indicatorTypes = Object.values(IndicatorType).map(type => {
|
||||
@@ -124,7 +124,7 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
});
|
||||
|
||||
const addIndicator = () => {
|
||||
const newIndicator: Indicator = {
|
||||
const newIndicator: LightIndicator = {
|
||||
name: `Indicator ${indicators.length + 1}`,
|
||||
type: indicatorTypes[0].type,
|
||||
period: 14,
|
||||
@@ -154,7 +154,7 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
}
|
||||
|
||||
const handleCreateScenario = () => {
|
||||
const scenario: Scenario = {
|
||||
const scenario: LightScenario = {
|
||||
name,
|
||||
indicators,
|
||||
loopbackPeriod,
|
||||
@@ -262,7 +262,7 @@ const CustomScenario: React.FC<ICustomScenario> = ({
|
||||
{getRequiredParams(indicator.type || indicatorTypes[0].type).map((param) => (
|
||||
<FormInput key={param} label={param.charAt(0).toUpperCase() + param.slice(1)} htmlFor={`${param}-${index}`} inline={false}>
|
||||
<input
|
||||
value={indicator[param as keyof Indicator] as number || ''}
|
||||
value={indicator[param as keyof LightIndicator] as number || ''}
|
||||
onChange={(e) => updateIndicator(index, param, param.includes('multiplier') ? parseFloat(e.target.value) : parseInt(e.target.value))}
|
||||
type='number'
|
||||
step={param.includes('multiplier') ? '0.1' : '1'}
|
||||
|
||||
@@ -1,200 +1,124 @@
|
||||
import {ArrowDownIcon, ArrowUpIcon} from '@heroicons/react/solid'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import type {PlatformSummaryViewModel, TradingBot} from '../../../generated/ManagingApi'
|
||||
import {DataClient, PositionStatus, TradeDirection} from '../../../generated/ManagingApi'
|
||||
import type {IAccountBalanceProps} from '../../../global/type'
|
||||
import type {PlatformSummaryViewModel, TradingBotResponse} from '../../../generated/ManagingApi'
|
||||
import {DataClient} from '../../../generated/ManagingApi'
|
||||
|
||||
// Time filter options matching backend
|
||||
const TIME_FILTERS = ['24H', '3D', '1W', '1M', '1Y', 'Total']
|
||||
|
||||
function GetGlobalWinrate(bots: TradingBot[]) {
|
||||
if (bots == null || bots == undefined || bots.length == 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
let totalPositions = 0
|
||||
let winningPosition = 0
|
||||
|
||||
bots.forEach((bot) => {
|
||||
totalPositions += bot.positions.filter(
|
||||
(p) => p.status != PositionStatus.New
|
||||
).length
|
||||
winningPosition += bot.positions.filter((p) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0
|
||||
return realized > 0 &&
|
||||
(p.status == PositionStatus.Finished ||
|
||||
p.status == PositionStatus.Flipped)
|
||||
? p
|
||||
: null
|
||||
}).length
|
||||
})
|
||||
|
||||
if (totalPositions == 0) return 0
|
||||
|
||||
return (winningPosition * 100) / totalPositions
|
||||
}
|
||||
|
||||
function GetPositionCount(
|
||||
bots: TradingBot[],
|
||||
direction: TradeDirection,
|
||||
status: PositionStatus
|
||||
) {
|
||||
let totalPositions = 0
|
||||
|
||||
if (bots == null || bots == undefined) {
|
||||
return 0
|
||||
}
|
||||
|
||||
bots.forEach((bot) => {
|
||||
totalPositions += bot.positions.filter(
|
||||
(p) => p.status == status && p.originDirection == direction
|
||||
).length
|
||||
})
|
||||
|
||||
return totalPositions
|
||||
}
|
||||
|
||||
const Summary: React.FC<IAccountBalanceProps> = ({ bots }) => {
|
||||
const [globalPnl, setGlobalPnl] = useState<number>(0)
|
||||
const [globalWinrate, setGlobalWinrate] = useState<number>(0)
|
||||
const [selectedTimeFilter, setSelectedTimeFilter] = useState<string>('Total')
|
||||
const [platformStats, setPlatformStats] = useState<PlatformSummaryViewModel | null>(null)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
|
||||
const [openLong, setLong] = useState<number>(0)
|
||||
const [openShort, setShort] = useState<number>(0)
|
||||
|
||||
const [closedLong, setClosedLong] = useState<number>(0)
|
||||
const [closedShort, setClosedShort] = useState<number>(0)
|
||||
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
|
||||
useEffect(() => {
|
||||
if (bots) {
|
||||
const pnl = bots.reduce((acc, bot) => {
|
||||
return acc + bot.profitAndLoss
|
||||
}, 0)
|
||||
setGlobalPnl(pnl)
|
||||
setGlobalWinrate(GetGlobalWinrate(bots))
|
||||
setLong(
|
||||
GetPositionCount(bots, TradeDirection.Long, PositionStatus.Filled)
|
||||
)
|
||||
setShort(
|
||||
GetPositionCount(bots, TradeDirection.Short, PositionStatus.Filled)
|
||||
)
|
||||
setClosedLong(
|
||||
GetPositionCount(bots, TradeDirection.Long, PositionStatus.Finished) +
|
||||
GetPositionCount(bots, TradeDirection.Long, PositionStatus.Flipped)
|
||||
)
|
||||
setClosedShort(
|
||||
GetPositionCount(bots, TradeDirection.Short, PositionStatus.Finished) +
|
||||
GetPositionCount(bots, TradeDirection.Short, PositionStatus.Flipped)
|
||||
)
|
||||
}
|
||||
}, [bots])
|
||||
|
||||
// Fetch platform summary data
|
||||
useEffect(() => {
|
||||
const fetchPlatformStats = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const dataClient = new DataClient({}, apiUrl)
|
||||
const data = await dataClient.data_GetPlatformSummary(selectedTimeFilter)
|
||||
setPlatformStats(data)
|
||||
} catch (error) {
|
||||
console.error('Error fetching platform stats:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
function GetGlobalWinrate(bots: TradingBotResponse[]) {
|
||||
if (bots == null || bots == undefined || bots.length == 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
fetchPlatformStats()
|
||||
}, [apiUrl, selectedTimeFilter])
|
||||
return bots.reduce((acc, bot) => {
|
||||
return acc + bot.winRate
|
||||
}, 0) / bots.length
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold">Platform Overview</h2>
|
||||
<div className="join">
|
||||
{TIME_FILTERS.map((filter) => (
|
||||
<button
|
||||
key={filter}
|
||||
className={`btn btn-sm join-item ${selectedTimeFilter === filter ? 'btn-primary' : 'btn-ghost'}`}
|
||||
onClick={() => setSelectedTimeFilter(filter)}
|
||||
>
|
||||
{filter}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
const Summary: React.FC<{ bots: TradingBotResponse[] }> = ({bots}) => {
|
||||
const [globalPnl, setGlobalPnl] = useState<number>(0)
|
||||
const [globalWinrate, setGlobalWinrate] = useState<number>(0)
|
||||
const [selectedTimeFilter, setSelectedTimeFilter] = useState<string>('Total')
|
||||
const [platformStats, setPlatformStats] = useState<PlatformSummaryViewModel | null>(null)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
|
||||
<div className="stats bg-primary text-primary-content mb-4">
|
||||
<div className="stat">
|
||||
<div className="stat-title">Total Agents</div>
|
||||
<div className="stat-value">{platformStats?.totalAgents ?? 0}</div>
|
||||
</div>
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Active Strategies</div>
|
||||
<div className="stat-value">{platformStats?.totalActiveStrategies ?? 0}</div>
|
||||
</div>
|
||||
useEffect(() => {
|
||||
if (bots) {
|
||||
const pnl = bots.reduce((acc, bot) => {
|
||||
return acc + bot.profitAndLoss
|
||||
}, 0)
|
||||
setGlobalPnl(pnl)
|
||||
setGlobalWinrate(GetGlobalWinrate(bots))
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Total Platform PnL</div>
|
||||
<div className="stat-value">{(platformStats?.totalPlatformPnL ?? 0).toFixed(2)} $</div>
|
||||
</div>
|
||||
}
|
||||
}, [bots])
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Volume (Total)</div>
|
||||
<div className="stat-value">{(platformStats?.totalPlatformVolume ?? 0).toFixed(2)} $</div>
|
||||
</div>
|
||||
// Fetch platform summary data
|
||||
useEffect(() => {
|
||||
const fetchPlatformStats = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const dataClient = new DataClient({}, apiUrl)
|
||||
const data = await dataClient.data_GetPlatformSummary(selectedTimeFilter)
|
||||
setPlatformStats(data)
|
||||
} catch (error) {
|
||||
console.error('Error fetching platform stats:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Volume (24h)</div>
|
||||
<div className="stat-value">{(platformStats?.totalPlatformVolumeLast24h ?? 0).toFixed(2)} $</div>
|
||||
</div>
|
||||
</div>
|
||||
fetchPlatformStats()
|
||||
}, [apiUrl, selectedTimeFilter])
|
||||
|
||||
<div className="stats bg-primary text-primary-content">
|
||||
<div className="stat">
|
||||
<div className="stat-title">Bots running</div>
|
||||
<div className="stat-value">{bots.length}</div>
|
||||
</div>
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold">Platform Overview</h2>
|
||||
<div className="join">
|
||||
{TIME_FILTERS.map((filter) => (
|
||||
<button
|
||||
key={filter}
|
||||
className={`btn btn-sm join-item ${selectedTimeFilter === filter ? 'btn-primary' : 'btn-ghost'}`}
|
||||
onClick={() => setSelectedTimeFilter(filter)}
|
||||
>
|
||||
{filter}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Total Profit</div>
|
||||
<div className="stat-value">{globalPnl.toFixed(4)} $</div>
|
||||
</div>
|
||||
<div className="stats bg-primary text-primary-content mb-4">
|
||||
<div className="stat">
|
||||
<div className="stat-title">Total Agents</div>
|
||||
<div className="stat-value">{platformStats?.totalAgents ?? 0}</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Global Winrate</div>
|
||||
<div className="stat-value">
|
||||
{globalWinrate ? globalWinrate.toFixed(2) : 0} %
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-title">Active Strategies</div>
|
||||
<div className="stat-value">{platformStats?.totalActiveStrategies ?? 0}</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Positions Openend</div>
|
||||
<div className="stat-value">
|
||||
{openLong} <ArrowUpIcon width={20} className="inline"></ArrowUpIcon>{' '}
|
||||
{openShort}{' '}
|
||||
<ArrowDownIcon width={20} className="inline"></ArrowDownIcon>{' '}
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-title">Total Platform PnL</div>
|
||||
<div className="stat-value">{(platformStats?.totalPlatformPnL ?? 0).toFixed(2)} $</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Volume (Total)</div>
|
||||
<div className="stat-value">{(platformStats?.totalPlatformVolume ?? 0).toFixed(2)} $</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Volume (24h)</div>
|
||||
<div className="stat-value">{(platformStats?.totalPlatformVolumeLast24h ?? 0).toFixed(2)} $</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="stats bg-primary text-primary-content">
|
||||
<div className="stat">
|
||||
<div className="stat-title">Bots running</div>
|
||||
<div className="stat-value">{bots.length}</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Total Profit</div>
|
||||
<div className="stat-value">{globalPnl.toFixed(4)} $</div>
|
||||
</div>
|
||||
|
||||
<div className="stat">
|
||||
<div className="stat-title">Global Winrate</div>
|
||||
<div className="stat-value">
|
||||
{globalWinrate ? globalWinrate.toFixed(2) : 0} %
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stat">
|
||||
<div className="stat-title">Positions Closed</div>
|
||||
<div className="stat-value">
|
||||
{closedLong}{' '}
|
||||
<ArrowUpIcon width={20} className="inline"></ArrowUpIcon>{' '}
|
||||
{closedShort}{' '}
|
||||
<ArrowDownIcon width={20} className="inline"></ArrowDownIcon>{' '}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default Summary
|
||||
|
||||
@@ -12,12 +12,12 @@ import {
|
||||
BotClient,
|
||||
DataClient,
|
||||
LightBacktestResponse,
|
||||
LightScenario,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
RiskManagement,
|
||||
RiskToleranceLevel,
|
||||
RunBacktestRequest,
|
||||
Scenario,
|
||||
ScenarioClient,
|
||||
ScenarioRequest,
|
||||
SignalType,
|
||||
@@ -41,6 +41,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
setBacktests,
|
||||
backtest,
|
||||
existingBot,
|
||||
onBacktestComplete,
|
||||
}) => {
|
||||
// Default dates for backtests
|
||||
const defaultStartDate = new Date();
|
||||
@@ -128,7 +129,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
const [selectedMoneyManagement, setSelectedMoneyManagement] = useState<string>();
|
||||
const [showCustomMoneyManagement, setShowCustomMoneyManagement] = useState(false);
|
||||
|
||||
const [customScenario, setCustomScenario] = useState<Scenario | undefined>(undefined);
|
||||
const [customScenario, setCustomScenario] = useState<LightScenario | undefined>(undefined);
|
||||
const [selectedScenario, setSelectedScenario] = useState<string>();
|
||||
const [showCustomScenario, setShowCustomScenario] = useState(false);
|
||||
|
||||
@@ -334,10 +335,11 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
}
|
||||
|
||||
// Handle scenario - check if we have scenario data or just a name reference
|
||||
if ((config as any).scenario) {
|
||||
if (config.scenario) {
|
||||
setShowCustomScenario(true);
|
||||
setCustomScenario((config as any).scenario);
|
||||
setGlobalCustomScenario((config as any).scenario); // Also update global store for prefilling
|
||||
// Use LightScenario directly since CustomScenario now supports it
|
||||
setCustomScenario(config.scenario);
|
||||
setGlobalCustomScenario(config.scenario); // Also update global store for prefilling
|
||||
setSelectedScenario('custom'); // Set dropdown to show "custom"
|
||||
} else if (config.scenarioName) {
|
||||
setValue('scenarioName', config.scenarioName);
|
||||
@@ -556,7 +558,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
};
|
||||
|
||||
// Helper function to convert custom scenario to ScenarioRequest format
|
||||
const convertScenarioToRequest = (scenario: Scenario): ScenarioRequest => {
|
||||
const convertScenarioToRequest = (scenario: LightScenario): ScenarioRequest => {
|
||||
return {
|
||||
name: scenario.name || 'Custom Scenario',
|
||||
loopbackPeriod: scenario.loopbackPeriod || null,
|
||||
@@ -605,9 +607,13 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// Bot submission handler
|
||||
const handleBotSubmission = async (form: IUnifiedTradingConfigInput) => {
|
||||
const t = new Toast(mode === 'createBot' ? 'Creating bot...' : 'Updating bot...');
|
||||
// Unified bot submission handler
|
||||
const handleBotSubmission = async (form: IUnifiedTradingConfigInput, saveOnly: boolean = false) => {
|
||||
const t = new Toast(
|
||||
mode === 'createBot'
|
||||
? (saveOnly ? 'Saving bot configuration...' : 'Creating bot...')
|
||||
: 'Updating bot...'
|
||||
);
|
||||
|
||||
try {
|
||||
// Create money management object
|
||||
@@ -657,8 +663,14 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
const request: StartBotRequest = {
|
||||
config: tradingBotConfigRequest,
|
||||
};
|
||||
await botClient.bot_Start(request);
|
||||
t.update('success', 'Bot created successfully!');
|
||||
|
||||
if (saveOnly) {
|
||||
await botClient.bot_Save(request);
|
||||
t.update('success', 'Bot configuration saved successfully!');
|
||||
} else {
|
||||
await botClient.bot_Start(request);
|
||||
t.update('success', 'Bot created successfully!');
|
||||
}
|
||||
} else {
|
||||
const request: UpdateBotConfigRequest = {
|
||||
identifier: existingBot!.identifier,
|
||||
@@ -715,6 +727,11 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
|
||||
t.update('success', `${ticker} Backtest Succeeded`);
|
||||
addBacktest(backtest as unknown as LightBacktestResponse);
|
||||
|
||||
// Call the callback to notify parent component that backtest is completed
|
||||
if (onBacktestComplete) {
|
||||
onBacktestComplete();
|
||||
}
|
||||
|
||||
|
||||
} catch (err: any) {
|
||||
@@ -1595,9 +1612,24 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
{mode === 'backtest' ? 'Run Backtest' : mode === 'createBot' ? 'Create Bot' : 'Update Bot'}
|
||||
</button>
|
||||
{mode === 'createBot' ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={handleSubmit((form) => handleBotSubmission(form, true))}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
Create Bot
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button type="submit" className="btn btn-primary">
|
||||
{mode === 'backtest' ? 'Run Backtest' : 'Update Bot'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1161,9 +1161,50 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<string>(null as any);
|
||||
}
|
||||
|
||||
bot_Stop(identifier: string | null | undefined): Promise<string> {
|
||||
bot_Save(request: SaveBotRequest): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/Save";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(request);
|
||||
|
||||
let options_: RequestInit = {
|
||||
body: content_,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBot_Save(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_Save(response: Response): Promise<string> {
|
||||
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 string;
|
||||
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<string>(null as any);
|
||||
}
|
||||
|
||||
bot_Stop(identifier: string | undefined): Promise<BotStatus> {
|
||||
let url_ = this.baseUrl + "/Bot/Stop?";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
if (identifier === null)
|
||||
throw new Error("The parameter 'identifier' cannot be null.");
|
||||
else if (identifier !== undefined)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -1181,13 +1222,13 @@ export class BotClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_Stop(response: Response): Promise<string> {
|
||||
protected processBot_Stop(response: Response): Promise<BotStatus> {
|
||||
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 string;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as BotStatus;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
@@ -1195,12 +1236,14 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<string>(null as any);
|
||||
return Promise.resolve<BotStatus>(null as any);
|
||||
}
|
||||
|
||||
bot_Delete(identifier: string | null | undefined): Promise<boolean> {
|
||||
bot_Delete(identifier: string | undefined): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/Bot/Delete?";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
if (identifier === null)
|
||||
throw new Error("The parameter 'identifier' cannot be null.");
|
||||
else if (identifier !== undefined)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -1235,48 +1278,11 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<boolean>(null as any);
|
||||
}
|
||||
|
||||
bot_StopAll(): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/stop-all";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBot_StopAll(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_StopAll(response: Response): Promise<string> {
|
||||
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 string;
|
||||
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<string>(null as any);
|
||||
}
|
||||
|
||||
bot_Restart(botType: BotType | undefined, identifier: string | null | undefined): Promise<string> {
|
||||
bot_Restart(identifier: string | undefined): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/Restart?";
|
||||
if (botType === null)
|
||||
throw new Error("The parameter 'botType' cannot be null.");
|
||||
else if (botType !== undefined)
|
||||
url_ += "botType=" + encodeURIComponent("" + botType) + "&";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
if (identifier === null)
|
||||
throw new Error("The parameter 'identifier' cannot be null.");
|
||||
else if (identifier !== undefined)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -1311,78 +1317,6 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<string>(null as any);
|
||||
}
|
||||
|
||||
bot_RestartAll(): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/restart-all";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.transformOptions(options_).then(transformedOptions_ => {
|
||||
return this.http.fetch(url_, transformedOptions_);
|
||||
}).then((_response: Response) => {
|
||||
return this.processBot_RestartAll(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_RestartAll(response: Response): Promise<string> {
|
||||
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 string;
|
||||
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<string>(null as any);
|
||||
}
|
||||
|
||||
bot_ToggleIsForWatching(identifier: string | null | undefined): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/ToggleIsForWatching?";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
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.processBot_ToggleIsForWatching(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_ToggleIsForWatching(response: Response): Promise<string> {
|
||||
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 string;
|
||||
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<string>(null as any);
|
||||
}
|
||||
|
||||
bot_GetActiveBots(): Promise<TradingBotResponse[]> {
|
||||
let url_ = this.baseUrl + "/Bot";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
@@ -1457,6 +1391,134 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<MoneyManagement>(null as any);
|
||||
}
|
||||
|
||||
bot_GetBotsByStatus(status: BotStatus): Promise<TradingBotResponse[]> {
|
||||
let url_ = this.baseUrl + "/Bot/ByStatus/{status}";
|
||||
if (status === undefined || status === null)
|
||||
throw new Error("The parameter 'status' must be defined.");
|
||||
url_ = url_.replace("{status}", encodeURIComponent("" + status));
|
||||
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.processBot_GetBotsByStatus(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_GetBotsByStatus(response: Response): Promise<TradingBotResponse[]> {
|
||||
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 TradingBotResponse[];
|
||||
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<TradingBotResponse[]>(null as any);
|
||||
}
|
||||
|
||||
bot_GetMySavedBots(): Promise<TradingBotResponse[]> {
|
||||
let url_ = this.baseUrl + "/Bot/GetMySavedBots";
|
||||
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.processBot_GetMySavedBots(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_GetMySavedBots(response: Response): Promise<TradingBotResponse[]> {
|
||||
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 TradingBotResponse[];
|
||||
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<TradingBotResponse[]>(null as any);
|
||||
}
|
||||
|
||||
bot_GetBotsPaginated(pageNumber: number | undefined, pageSize: number | undefined, status: BotStatus | null | undefined, name: string | null | undefined, ticker: string | null | undefined, agentName: string | null | undefined, sortBy: string | null | undefined, sortDirection: string | null | undefined): Promise<PaginatedResponseOfTradingBotResponse> {
|
||||
let url_ = this.baseUrl + "/Bot/Paginated?";
|
||||
if (pageNumber === null)
|
||||
throw new Error("The parameter 'pageNumber' cannot be null.");
|
||||
else if (pageNumber !== undefined)
|
||||
url_ += "pageNumber=" + encodeURIComponent("" + pageNumber) + "&";
|
||||
if (pageSize === null)
|
||||
throw new Error("The parameter 'pageSize' cannot be null.");
|
||||
else if (pageSize !== undefined)
|
||||
url_ += "pageSize=" + encodeURIComponent("" + pageSize) + "&";
|
||||
if (status !== undefined && status !== null)
|
||||
url_ += "status=" + encodeURIComponent("" + status) + "&";
|
||||
if (name !== undefined && name !== null)
|
||||
url_ += "name=" + encodeURIComponent("" + name) + "&";
|
||||
if (ticker !== undefined && ticker !== null)
|
||||
url_ += "ticker=" + encodeURIComponent("" + ticker) + "&";
|
||||
if (agentName !== undefined && agentName !== null)
|
||||
url_ += "agentName=" + encodeURIComponent("" + agentName) + "&";
|
||||
if (sortBy !== undefined && sortBy !== null)
|
||||
url_ += "sortBy=" + encodeURIComponent("" + sortBy) + "&";
|
||||
if (sortDirection !== undefined && sortDirection !== null)
|
||||
url_ += "sortDirection=" + encodeURIComponent("" + sortDirection) + "&";
|
||||
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.processBot_GetBotsPaginated(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_GetBotsPaginated(response: Response): Promise<PaginatedResponseOfTradingBotResponse> {
|
||||
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 PaginatedResponseOfTradingBotResponse;
|
||||
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<PaginatedResponseOfTradingBotResponse>(null as any);
|
||||
}
|
||||
|
||||
bot_OpenPositionManually(request: OpenPositionManuallyRequest): Promise<Position> {
|
||||
let url_ = this.baseUrl + "/Bot/OpenPosition";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
@@ -1535,6 +1597,44 @@ export class BotClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<Position>(null as any);
|
||||
}
|
||||
|
||||
bot_GetBotConfig(identifier: string): Promise<TradingBotConfig> {
|
||||
let url_ = this.baseUrl + "/Bot/GetConfig/{identifier}";
|
||||
if (identifier === undefined || identifier === null)
|
||||
throw new Error("The parameter 'identifier' must be defined.");
|
||||
url_ = url_.replace("{identifier}", encodeURIComponent("" + identifier));
|
||||
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.processBot_GetBotConfig(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processBot_GetBotConfig(response: Response): Promise<TradingBotConfig> {
|
||||
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 TradingBotConfig;
|
||||
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<TradingBotConfig>(null as any);
|
||||
}
|
||||
|
||||
bot_UpdateBotConfig(request: UpdateBotConfigRequest): Promise<string> {
|
||||
let url_ = this.baseUrl + "/Bot/UpdateConfig";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
@@ -1882,47 +1982,8 @@ export class DataClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<PlatformSummaryViewModel>(null as any);
|
||||
}
|
||||
|
||||
data_GetAgentIndex(timeFilter: string | null | undefined): Promise<AgentIndexViewModel> {
|
||||
let url_ = this.baseUrl + "/Data/GetAgentIndex?";
|
||||
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_GetAgentIndex(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processData_GetAgentIndex(response: Response): Promise<AgentIndexViewModel> {
|
||||
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 AgentIndexViewModel;
|
||||
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<AgentIndexViewModel>(null as any);
|
||||
}
|
||||
|
||||
data_GetAgentIndexPaginated(timeFilter: string | null | undefined, page: number | undefined, pageSize: number | undefined, sortBy: string | null | undefined, sortOrder: string | null | undefined, agentNames: string | null | undefined): Promise<PaginatedAgentIndexResponse> {
|
||||
data_GetAgentIndexPaginated(page: number | undefined, pageSize: number | undefined, sortBy: SortableFields | undefined, sortOrder: string | null | undefined, agentNames: string | null | undefined): Promise<PaginatedAgentIndexResponse> {
|
||||
let url_ = this.baseUrl + "/Data/GetAgentIndexPaginated?";
|
||||
if (timeFilter !== undefined && timeFilter !== null)
|
||||
url_ += "timeFilter=" + encodeURIComponent("" + timeFilter) + "&";
|
||||
if (page === null)
|
||||
throw new Error("The parameter 'page' cannot be null.");
|
||||
else if (page !== undefined)
|
||||
@@ -1931,7 +1992,9 @@ export class DataClient extends AuthorizedApiBase {
|
||||
throw new Error("The parameter 'pageSize' cannot be null.");
|
||||
else if (pageSize !== undefined)
|
||||
url_ += "pageSize=" + encodeURIComponent("" + pageSize) + "&";
|
||||
if (sortBy !== undefined && sortBy !== null)
|
||||
if (sortBy === null)
|
||||
throw new Error("The parameter 'sortBy' cannot be null.");
|
||||
else if (sortBy !== undefined)
|
||||
url_ += "sortBy=" + encodeURIComponent("" + sortBy) + "&";
|
||||
if (sortOrder !== undefined && sortOrder !== null)
|
||||
url_ += "sortOrder=" + encodeURIComponent("" + sortOrder) + "&";
|
||||
@@ -2983,9 +3046,11 @@ export class TradingClient extends AuthorizedApiBase {
|
||||
return Promise.resolve<Trade>(null as any);
|
||||
}
|
||||
|
||||
trading_ClosePosition(identifier: string | null | undefined): Promise<Position> {
|
||||
trading_ClosePosition(identifier: string | undefined): Promise<Position> {
|
||||
let url_ = this.baseUrl + "/Trading/ClosePosition?";
|
||||
if (identifier !== undefined && identifier !== null)
|
||||
if (identifier === null)
|
||||
throw new Error("The parameter 'identifier' cannot be null.");
|
||||
else if (identifier !== undefined)
|
||||
url_ += "identifier=" + encodeURIComponent("" + identifier) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
@@ -3133,7 +3198,7 @@ export class UserClient extends AuthorizedApiBase {
|
||||
}
|
||||
|
||||
user_CreateToken(login: LoginRequest): Promise<string> {
|
||||
let url_ = this.baseUrl + "/User";
|
||||
let url_ = this.baseUrl + "/User/create-token";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
const content_ = JSON.stringify(login);
|
||||
@@ -3550,6 +3615,7 @@ export enum AccountType {
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: number;
|
||||
name?: string | null;
|
||||
accounts?: Account[] | null;
|
||||
agentName?: string | null;
|
||||
@@ -3736,8 +3802,8 @@ export interface Backtest {
|
||||
growthPercentage: number;
|
||||
hodlPercentage: number;
|
||||
config: TradingBotConfig;
|
||||
positions: Position[];
|
||||
signals: LightSignal[];
|
||||
positions: { [key: string]: Position; };
|
||||
signals: { [key: string]: LightSignal; };
|
||||
candles: Candle[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
@@ -3745,7 +3811,6 @@ export interface Backtest {
|
||||
fees: number;
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
user: User;
|
||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
score: number;
|
||||
requestId?: string;
|
||||
metadata?: any | null;
|
||||
@@ -3948,10 +4013,7 @@ export enum PositionInitiator {
|
||||
CopyTrading = "CopyTrading",
|
||||
}
|
||||
|
||||
export interface ValueObject {
|
||||
}
|
||||
|
||||
export interface LightSignal extends ValueObject {
|
||||
export interface LightSignal {
|
||||
status: SignalStatus;
|
||||
direction: TradeDirection;
|
||||
confidence: Confidence;
|
||||
@@ -4008,96 +4070,6 @@ export interface KeyValuePairOfDateTimeAndDecimal {
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface IndicatorsResultBase {
|
||||
ema?: EmaResult[] | null;
|
||||
fastEma?: EmaResult[] | null;
|
||||
slowEma?: EmaResult[] | null;
|
||||
macd?: MacdResult[] | null;
|
||||
rsi?: RsiResult[] | null;
|
||||
stoch?: StochResult[] | null;
|
||||
stochRsi?: StochRsiResult[] | null;
|
||||
bollingerBands?: BollingerBandsResult[] | null;
|
||||
chandelierShort?: ChandelierResult[] | null;
|
||||
stc?: StcResult[] | null;
|
||||
stdDev?: StdDevResult[] | null;
|
||||
superTrend?: SuperTrendResult[] | null;
|
||||
chandelierLong?: ChandelierResult[] | null;
|
||||
}
|
||||
|
||||
export interface ResultBase {
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export interface EmaResult extends ResultBase {
|
||||
ema?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface MacdResult extends ResultBase {
|
||||
macd?: number | null;
|
||||
signal?: number | null;
|
||||
histogram?: number | null;
|
||||
fastEma?: number | null;
|
||||
slowEma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface RsiResult extends ResultBase {
|
||||
rsi?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */
|
||||
export interface StochResult extends ResultBase {
|
||||
oscillator?: number | null;
|
||||
signal?: number | null;
|
||||
percentJ?: number | null;
|
||||
k?: number | null;
|
||||
d?: number | null;
|
||||
j?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StochRsiResult extends ResultBase {
|
||||
stochRsi?: number | null;
|
||||
signal?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface BollingerBandsResult extends ResultBase {
|
||||
sma?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
percentB?: number | null;
|
||||
zScore?: number | null;
|
||||
width?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface ChandelierResult extends ResultBase {
|
||||
chandelierExit?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StcResult extends ResultBase {
|
||||
stc?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StdDevResult extends ResultBase {
|
||||
stdDev?: number | null;
|
||||
mean?: number | null;
|
||||
zScore?: number | null;
|
||||
stdDevSma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface SuperTrendResult extends ResultBase {
|
||||
superTrend?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
}
|
||||
|
||||
export interface DeleteBacktestsRequest {
|
||||
backtestIds: string[];
|
||||
}
|
||||
@@ -4332,34 +4304,48 @@ export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export enum BotType {
|
||||
SimpleBot = "SimpleBot",
|
||||
ScalpingBot = "ScalpingBot",
|
||||
FlippingBot = "FlippingBot",
|
||||
export interface SaveBotRequest extends StartBotRequest {
|
||||
}
|
||||
|
||||
export enum BotStatus {
|
||||
None = "None",
|
||||
Down = "Down",
|
||||
Up = "Up",
|
||||
}
|
||||
|
||||
export interface TradingBotResponse {
|
||||
status: string;
|
||||
signals: LightSignal[];
|
||||
positions: Position[];
|
||||
signals: { [key: string]: LightSignal; };
|
||||
positions: { [key: string]: Position; };
|
||||
candles: Candle[];
|
||||
winRate: number;
|
||||
profitAndLoss: number;
|
||||
identifier: string;
|
||||
agentName: string;
|
||||
config: TradingBotConfig;
|
||||
createDate: Date;
|
||||
startupTime: Date;
|
||||
name: string;
|
||||
ticker: Ticker;
|
||||
}
|
||||
|
||||
export interface PaginatedResponseOfTradingBotResponse {
|
||||
items?: TradingBotResponse[] | null;
|
||||
totalCount?: number;
|
||||
pageNumber?: number;
|
||||
pageSize?: number;
|
||||
totalPages?: number;
|
||||
hasPreviousPage?: boolean;
|
||||
hasNextPage?: boolean;
|
||||
}
|
||||
|
||||
export interface OpenPositionManuallyRequest {
|
||||
identifier?: string | null;
|
||||
identifier?: string;
|
||||
direction?: TradeDirection;
|
||||
}
|
||||
|
||||
export interface ClosePositionRequest {
|
||||
identifier?: string | null;
|
||||
positionId?: string | null;
|
||||
identifier?: string;
|
||||
positionId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateBotConfigRequest {
|
||||
@@ -4388,14 +4374,13 @@ export interface Spotlight {
|
||||
|
||||
export interface Scenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
indicators?: IndicatorBase[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
export interface IndicatorBase {
|
||||
name?: string | null;
|
||||
candles?: FixedSizeQueueOfCandle | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
minimumHistory?: number;
|
||||
@@ -4410,15 +4395,6 @@ export interface Indicator {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Anonymous {
|
||||
maxSize?: number;
|
||||
}
|
||||
|
||||
export interface FixedSizeQueueOfCandle extends Anonymous {
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: LightSignal[];
|
||||
@@ -4433,6 +4409,96 @@ export interface CandlesWithIndicatorsResponse {
|
||||
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
|
||||
}
|
||||
|
||||
export interface IndicatorsResultBase {
|
||||
ema?: EmaResult[] | null;
|
||||
fastEma?: EmaResult[] | null;
|
||||
slowEma?: EmaResult[] | null;
|
||||
macd?: MacdResult[] | null;
|
||||
rsi?: RsiResult[] | null;
|
||||
stoch?: StochResult[] | null;
|
||||
stochRsi?: StochRsiResult[] | null;
|
||||
bollingerBands?: BollingerBandsResult[] | null;
|
||||
chandelierShort?: ChandelierResult[] | null;
|
||||
stc?: StcResult[] | null;
|
||||
stdDev?: StdDevResult[] | null;
|
||||
superTrend?: SuperTrendResult[] | null;
|
||||
chandelierLong?: ChandelierResult[] | null;
|
||||
}
|
||||
|
||||
export interface ResultBase {
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export interface EmaResult extends ResultBase {
|
||||
ema?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface MacdResult extends ResultBase {
|
||||
macd?: number | null;
|
||||
signal?: number | null;
|
||||
histogram?: number | null;
|
||||
fastEma?: number | null;
|
||||
slowEma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface RsiResult extends ResultBase {
|
||||
rsi?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */
|
||||
export interface StochResult extends ResultBase {
|
||||
oscillator?: number | null;
|
||||
signal?: number | null;
|
||||
percentJ?: number | null;
|
||||
k?: number | null;
|
||||
d?: number | null;
|
||||
j?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StochRsiResult extends ResultBase {
|
||||
stochRsi?: number | null;
|
||||
signal?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface BollingerBandsResult extends ResultBase {
|
||||
sma?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
percentB?: number | null;
|
||||
zScore?: number | null;
|
||||
width?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface ChandelierResult extends ResultBase {
|
||||
chandelierExit?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StcResult extends ResultBase {
|
||||
stc?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StdDevResult extends ResultBase {
|
||||
stdDev?: number | null;
|
||||
mean?: number | null;
|
||||
zScore?: number | null;
|
||||
stdDevSma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface SuperTrendResult extends ResultBase {
|
||||
superTrend?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
}
|
||||
|
||||
export interface GetCandlesWithIndicatorsRequest {
|
||||
ticker?: Ticker;
|
||||
startDate?: Date;
|
||||
@@ -4467,9 +4533,9 @@ export interface UserStrategyDetailsViewModel {
|
||||
volumeLast24H?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
positions?: Position[] | null;
|
||||
positions?: { [key: string]: Position; } | null;
|
||||
identifier?: string | null;
|
||||
scenarioName?: string | null;
|
||||
walletBalances?: { [key: string]: number; } | null;
|
||||
}
|
||||
|
||||
export interface PlatformSummaryViewModel {
|
||||
@@ -4481,25 +4547,6 @@ export interface PlatformSummaryViewModel {
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentIndexViewModel {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentSummaryViewModel {
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
pnLLast24h?: number;
|
||||
totalROI?: number;
|
||||
roiLast24h?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
averageWinRate?: number;
|
||||
activeStrategiesCount?: number;
|
||||
totalVolume?: number;
|
||||
volumeLast24h?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedAgentIndexResponse {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
totalCount?: number;
|
||||
@@ -4509,11 +4556,31 @@ export interface PaginatedAgentIndexResponse {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
timeFilter?: string | null;
|
||||
sortBy?: string | null;
|
||||
sortBy?: SortableFields;
|
||||
sortOrder?: string | null;
|
||||
filteredAgentNames?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentSummaryViewModel {
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
totalROI?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
activeStrategiesCount?: number;
|
||||
totalVolume?: number;
|
||||
}
|
||||
|
||||
export enum SortableFields {
|
||||
TotalPnL = "TotalPnL",
|
||||
TotalROI = "TotalROI",
|
||||
Wins = "Wins",
|
||||
Losses = "Losses",
|
||||
AgentName = "AgentName",
|
||||
CreatedAt = "CreatedAt",
|
||||
UpdatedAt = "UpdatedAt",
|
||||
}
|
||||
|
||||
export interface AgentBalanceHistory {
|
||||
agentName?: string | null;
|
||||
agentBalances?: AgentBalance[] | null;
|
||||
|
||||
@@ -38,6 +38,7 @@ export enum AccountType {
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: number;
|
||||
name?: string | null;
|
||||
accounts?: Account[] | null;
|
||||
agentName?: string | null;
|
||||
@@ -224,8 +225,8 @@ export interface Backtest {
|
||||
growthPercentage: number;
|
||||
hodlPercentage: number;
|
||||
config: TradingBotConfig;
|
||||
positions: Position[];
|
||||
signals: LightSignal[];
|
||||
positions: { [key: string]: Position; };
|
||||
signals: { [key: string]: LightSignal; };
|
||||
candles: Candle[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
@@ -233,7 +234,6 @@ export interface Backtest {
|
||||
fees: number;
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
user: User;
|
||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
score: number;
|
||||
requestId?: string;
|
||||
metadata?: any | null;
|
||||
@@ -436,10 +436,7 @@ export enum PositionInitiator {
|
||||
CopyTrading = "CopyTrading",
|
||||
}
|
||||
|
||||
export interface ValueObject {
|
||||
}
|
||||
|
||||
export interface LightSignal extends ValueObject {
|
||||
export interface LightSignal {
|
||||
status: SignalStatus;
|
||||
direction: TradeDirection;
|
||||
confidence: Confidence;
|
||||
@@ -496,96 +493,6 @@ export interface KeyValuePairOfDateTimeAndDecimal {
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface IndicatorsResultBase {
|
||||
ema?: EmaResult[] | null;
|
||||
fastEma?: EmaResult[] | null;
|
||||
slowEma?: EmaResult[] | null;
|
||||
macd?: MacdResult[] | null;
|
||||
rsi?: RsiResult[] | null;
|
||||
stoch?: StochResult[] | null;
|
||||
stochRsi?: StochRsiResult[] | null;
|
||||
bollingerBands?: BollingerBandsResult[] | null;
|
||||
chandelierShort?: ChandelierResult[] | null;
|
||||
stc?: StcResult[] | null;
|
||||
stdDev?: StdDevResult[] | null;
|
||||
superTrend?: SuperTrendResult[] | null;
|
||||
chandelierLong?: ChandelierResult[] | null;
|
||||
}
|
||||
|
||||
export interface ResultBase {
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export interface EmaResult extends ResultBase {
|
||||
ema?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface MacdResult extends ResultBase {
|
||||
macd?: number | null;
|
||||
signal?: number | null;
|
||||
histogram?: number | null;
|
||||
fastEma?: number | null;
|
||||
slowEma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface RsiResult extends ResultBase {
|
||||
rsi?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */
|
||||
export interface StochResult extends ResultBase {
|
||||
oscillator?: number | null;
|
||||
signal?: number | null;
|
||||
percentJ?: number | null;
|
||||
k?: number | null;
|
||||
d?: number | null;
|
||||
j?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StochRsiResult extends ResultBase {
|
||||
stochRsi?: number | null;
|
||||
signal?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface BollingerBandsResult extends ResultBase {
|
||||
sma?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
percentB?: number | null;
|
||||
zScore?: number | null;
|
||||
width?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface ChandelierResult extends ResultBase {
|
||||
chandelierExit?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StcResult extends ResultBase {
|
||||
stc?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StdDevResult extends ResultBase {
|
||||
stdDev?: number | null;
|
||||
mean?: number | null;
|
||||
zScore?: number | null;
|
||||
stdDevSma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface SuperTrendResult extends ResultBase {
|
||||
superTrend?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
}
|
||||
|
||||
export interface DeleteBacktestsRequest {
|
||||
backtestIds: string[];
|
||||
}
|
||||
@@ -820,34 +727,48 @@ export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export enum BotType {
|
||||
SimpleBot = "SimpleBot",
|
||||
ScalpingBot = "ScalpingBot",
|
||||
FlippingBot = "FlippingBot",
|
||||
export interface SaveBotRequest extends StartBotRequest {
|
||||
}
|
||||
|
||||
export enum BotStatus {
|
||||
None = "None",
|
||||
Down = "Down",
|
||||
Up = "Up",
|
||||
}
|
||||
|
||||
export interface TradingBotResponse {
|
||||
status: string;
|
||||
signals: LightSignal[];
|
||||
positions: Position[];
|
||||
signals: { [key: string]: LightSignal; };
|
||||
positions: { [key: string]: Position; };
|
||||
candles: Candle[];
|
||||
winRate: number;
|
||||
profitAndLoss: number;
|
||||
identifier: string;
|
||||
agentName: string;
|
||||
config: TradingBotConfig;
|
||||
createDate: Date;
|
||||
startupTime: Date;
|
||||
name: string;
|
||||
ticker: Ticker;
|
||||
}
|
||||
|
||||
export interface PaginatedResponseOfTradingBotResponse {
|
||||
items?: TradingBotResponse[] | null;
|
||||
totalCount?: number;
|
||||
pageNumber?: number;
|
||||
pageSize?: number;
|
||||
totalPages?: number;
|
||||
hasPreviousPage?: boolean;
|
||||
hasNextPage?: boolean;
|
||||
}
|
||||
|
||||
export interface OpenPositionManuallyRequest {
|
||||
identifier?: string | null;
|
||||
identifier?: string;
|
||||
direction?: TradeDirection;
|
||||
}
|
||||
|
||||
export interface ClosePositionRequest {
|
||||
identifier?: string | null;
|
||||
positionId?: string | null;
|
||||
identifier?: string;
|
||||
positionId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateBotConfigRequest {
|
||||
@@ -876,14 +797,13 @@ export interface Spotlight {
|
||||
|
||||
export interface Scenario {
|
||||
name?: string | null;
|
||||
indicators?: Indicator[] | null;
|
||||
indicators?: IndicatorBase[] | null;
|
||||
loopbackPeriod?: number | null;
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Indicator {
|
||||
export interface IndicatorBase {
|
||||
name?: string | null;
|
||||
candles?: FixedSizeQueueOfCandle | null;
|
||||
type?: IndicatorType;
|
||||
signalType?: SignalType;
|
||||
minimumHistory?: number;
|
||||
@@ -898,15 +818,6 @@ export interface Indicator {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface Anonymous {
|
||||
maxSize?: number;
|
||||
}
|
||||
|
||||
export interface FixedSizeQueueOfCandle extends Anonymous {
|
||||
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: LightSignal[];
|
||||
@@ -921,6 +832,96 @@ export interface CandlesWithIndicatorsResponse {
|
||||
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
|
||||
}
|
||||
|
||||
export interface IndicatorsResultBase {
|
||||
ema?: EmaResult[] | null;
|
||||
fastEma?: EmaResult[] | null;
|
||||
slowEma?: EmaResult[] | null;
|
||||
macd?: MacdResult[] | null;
|
||||
rsi?: RsiResult[] | null;
|
||||
stoch?: StochResult[] | null;
|
||||
stochRsi?: StochRsiResult[] | null;
|
||||
bollingerBands?: BollingerBandsResult[] | null;
|
||||
chandelierShort?: ChandelierResult[] | null;
|
||||
stc?: StcResult[] | null;
|
||||
stdDev?: StdDevResult[] | null;
|
||||
superTrend?: SuperTrendResult[] | null;
|
||||
chandelierLong?: ChandelierResult[] | null;
|
||||
}
|
||||
|
||||
export interface ResultBase {
|
||||
date?: Date;
|
||||
}
|
||||
|
||||
export interface EmaResult extends ResultBase {
|
||||
ema?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface MacdResult extends ResultBase {
|
||||
macd?: number | null;
|
||||
signal?: number | null;
|
||||
histogram?: number | null;
|
||||
fastEma?: number | null;
|
||||
slowEma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface RsiResult extends ResultBase {
|
||||
rsi?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */
|
||||
export interface StochResult extends ResultBase {
|
||||
oscillator?: number | null;
|
||||
signal?: number | null;
|
||||
percentJ?: number | null;
|
||||
k?: number | null;
|
||||
d?: number | null;
|
||||
j?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StochRsiResult extends ResultBase {
|
||||
stochRsi?: number | null;
|
||||
signal?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface BollingerBandsResult extends ResultBase {
|
||||
sma?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
percentB?: number | null;
|
||||
zScore?: number | null;
|
||||
width?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface ChandelierResult extends ResultBase {
|
||||
chandelierExit?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StcResult extends ResultBase {
|
||||
stc?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface StdDevResult extends ResultBase {
|
||||
stdDev?: number | null;
|
||||
mean?: number | null;
|
||||
zScore?: number | null;
|
||||
stdDevSma?: number | null;
|
||||
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
|
||||
}
|
||||
|
||||
export interface SuperTrendResult extends ResultBase {
|
||||
superTrend?: number | null;
|
||||
upperBand?: number | null;
|
||||
lowerBand?: number | null;
|
||||
}
|
||||
|
||||
export interface GetCandlesWithIndicatorsRequest {
|
||||
ticker?: Ticker;
|
||||
startDate?: Date;
|
||||
@@ -955,9 +956,9 @@ export interface UserStrategyDetailsViewModel {
|
||||
volumeLast24H?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
positions?: Position[] | null;
|
||||
positions?: { [key: string]: Position; } | null;
|
||||
identifier?: string | null;
|
||||
scenarioName?: string | null;
|
||||
walletBalances?: { [key: string]: number; } | null;
|
||||
}
|
||||
|
||||
export interface PlatformSummaryViewModel {
|
||||
@@ -969,25 +970,6 @@ export interface PlatformSummaryViewModel {
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentIndexViewModel {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
timeFilter?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentSummaryViewModel {
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
pnLLast24h?: number;
|
||||
totalROI?: number;
|
||||
roiLast24h?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
averageWinRate?: number;
|
||||
activeStrategiesCount?: number;
|
||||
totalVolume?: number;
|
||||
volumeLast24h?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedAgentIndexResponse {
|
||||
agentSummaries?: AgentSummaryViewModel[] | null;
|
||||
totalCount?: number;
|
||||
@@ -997,11 +979,31 @@ export interface PaginatedAgentIndexResponse {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
timeFilter?: string | null;
|
||||
sortBy?: string | null;
|
||||
sortBy?: SortableFields;
|
||||
sortOrder?: string | null;
|
||||
filteredAgentNames?: string | null;
|
||||
}
|
||||
|
||||
export interface AgentSummaryViewModel {
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
totalROI?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
activeStrategiesCount?: number;
|
||||
totalVolume?: number;
|
||||
}
|
||||
|
||||
export enum SortableFields {
|
||||
TotalPnL = "TotalPnL",
|
||||
TotalROI = "TotalROI",
|
||||
Wins = "Wins",
|
||||
Losses = "Losses",
|
||||
AgentName = "AgentName",
|
||||
CreatedAt = "CreatedAt",
|
||||
UpdatedAt = "UpdatedAt",
|
||||
}
|
||||
|
||||
export interface AgentBalanceHistory {
|
||||
agentName?: string | null;
|
||||
agentBalances?: AgentBalance[] | null;
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface UnifiedTradingModalProps {
|
||||
|
||||
// For backtests
|
||||
setBacktests?: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
onBacktestComplete?: () => void // Callback when backtest is completed
|
||||
|
||||
// For bot creation/update
|
||||
backtest?: Backtest // Backtest object when creating from backtest
|
||||
|
||||
32
src/Managing.WebApp/src/hooks/useBots.tsx
Normal file
32
src/Managing.WebApp/src/hooks/useBots.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import useApiUrlStore from '../app/store/apiStore'
|
||||
import {BotClient, BotStatus, type TradingBotResponse} from '../generated/ManagingApi'
|
||||
|
||||
type UseBotsProps = {
|
||||
status?: BotStatus
|
||||
callback?: (data: TradingBotResponse[]) => void | undefined
|
||||
}
|
||||
|
||||
const useBots = ({status = BotStatus.None, callback}: UseBotsProps) => {
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
const botClient = new BotClient({}, apiUrl)
|
||||
|
||||
const query = useQuery({
|
||||
queryFn: () => botClient.bot_GetBotsByStatus(status),
|
||||
queryKey: ['bots', status],
|
||||
refetchInterval: 5000, // Refetch every 5 seconds for real-time updates
|
||||
})
|
||||
|
||||
// Call callback when data changes
|
||||
React.useEffect(() => {
|
||||
if (query.data && callback) {
|
||||
callback(query.data)
|
||||
}
|
||||
}, [query.data, callback])
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
export default useBots
|
||||
@@ -1,9 +1,9 @@
|
||||
import {ColorSwatchIcon, TrashIcon} from '@heroicons/react/solid'
|
||||
import {useQuery, useQueryClient} from '@tanstack/react-query'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import useBacktestStore from '../../app/store/backtestStore'
|
||||
import {Loader, Slider} from '../../components/atoms'
|
||||
import {Modal, Toast} from '../../components/mollecules'
|
||||
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
|
||||
@@ -21,43 +21,53 @@ const BacktestScanner: React.FC = () => {
|
||||
score: 50
|
||||
})
|
||||
const [currentPage, setCurrentPage] = useState(1)
|
||||
const [totalBacktests, setTotalBacktests] = useState(0)
|
||||
const [totalPages, setTotalPages] = useState(0)
|
||||
const [currentSort, setCurrentSort] = useState<{ sortBy: string; sortOrder: 'asc' | 'desc' }>({
|
||||
sortBy: 'score',
|
||||
sortOrder: 'desc'
|
||||
})
|
||||
const [backtests, setBacktests] = useState<LightBacktestResponse[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const { setBacktests: setBacktestsFromStore, setLoading } = useBacktestStore()
|
||||
const queryClient = useQueryClient()
|
||||
const backtestClient = new BacktestClient({}, apiUrl)
|
||||
|
||||
// Fetch paginated/sorted backtests
|
||||
const fetchBacktests = async (page = 1, sort = currentSort) => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const response = await backtestClient.backtest_GetBacktestsPaginated(page, PAGE_SIZE, sort.sortBy, sort.sortOrder)
|
||||
setBacktests((response.backtests as LightBacktestResponse[]) || [])
|
||||
setTotalBacktests(response.totalCount || 0)
|
||||
setTotalPages(response.totalPages || 0)
|
||||
} catch (err) {
|
||||
// Use TanStack Query for fetching backtests
|
||||
const {
|
||||
data: backtestData,
|
||||
isLoading,
|
||||
error,
|
||||
refetch
|
||||
} = useQuery({
|
||||
queryKey: ['backtests', currentPage, currentSort.sortBy, currentSort.sortOrder],
|
||||
queryFn: async () => {
|
||||
const response = await backtestClient.backtest_GetBacktestsPaginated(
|
||||
currentPage,
|
||||
PAGE_SIZE,
|
||||
currentSort.sortBy,
|
||||
currentSort.sortOrder
|
||||
)
|
||||
return {
|
||||
backtests: (response.backtests as LightBacktestResponse[]) || [],
|
||||
totalCount: response.totalCount || 0,
|
||||
totalPages: response.totalPages || 0
|
||||
}
|
||||
},
|
||||
staleTime: 30000, // Consider data fresh for 30 seconds
|
||||
gcTime: 5 * 60 * 1000, // Keep in cache for 5 minutes (formerly cacheTime)
|
||||
})
|
||||
|
||||
const backtests = backtestData?.backtests || []
|
||||
const totalBacktests = backtestData?.totalCount || 0
|
||||
const totalPages = backtestData?.totalPages || 0
|
||||
|
||||
// Note: We no longer need to sync with the store since we're using TanStack Query
|
||||
// The store is kept for backward compatibility with other components
|
||||
|
||||
// Handle errors
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
new Toast('Failed to load backtests', false)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchBacktests(currentPage, currentSort)
|
||||
// eslint-disable-next-line
|
||||
}, [currentPage, currentSort])
|
||||
|
||||
useEffect(() => {
|
||||
setBacktestsFromStore(backtests as any) // Cast to any for backward compatibility
|
||||
setLoading(isLoading)
|
||||
}, [backtests, setBacktestsFromStore, setLoading, isLoading])
|
||||
}, [error])
|
||||
|
||||
useEffect(() => {
|
||||
if (backtests && showModalRemoveBacktest) {
|
||||
@@ -150,8 +160,8 @@ const BacktestScanner: React.FC = () => {
|
||||
await backtestClient.backtest_DeleteBacktests({ backtestIds })
|
||||
notify.update('success', `${backTestToDelete.length} backtests deleted successfully`)
|
||||
|
||||
// Refetch backtests to update the list
|
||||
fetchBacktests(currentPage, currentSort)
|
||||
// Invalidate and refetch backtests to update the list
|
||||
queryClient.invalidateQueries({ queryKey: ['backtests'] })
|
||||
} catch (err: any) {
|
||||
notify.update('error', err?.message || 'An error occurred while deleting backtests')
|
||||
}
|
||||
@@ -202,6 +212,10 @@ const BacktestScanner: React.FC = () => {
|
||||
isFetching={isLoading}
|
||||
onSortChange={handleSortChange}
|
||||
currentSort={currentSort}
|
||||
onBacktestDeleted={() => {
|
||||
// Invalidate backtest queries when a backtest is deleted
|
||||
queryClient.invalidateQueries({ queryKey: ['backtests'] })
|
||||
}}
|
||||
/>
|
||||
{/* Pagination controls */}
|
||||
{totalPages > 1 && (
|
||||
@@ -220,6 +234,10 @@ const BacktestScanner: React.FC = () => {
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
onBacktestComplete={() => {
|
||||
// Invalidate backtest queries when a new backtest is completed
|
||||
queryClient.invalidateQueries({ queryKey: ['backtests'] })
|
||||
}}
|
||||
/>
|
||||
|
||||
{/****************************/}
|
||||
|
||||
@@ -8,15 +8,16 @@ import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
|
||||
import {TradeChart, UnifiedTradingModal} from '../../components/organism'
|
||||
import {
|
||||
BotClient,
|
||||
BotType,
|
||||
MoneyManagement,
|
||||
Position,
|
||||
TradingBotConfig,
|
||||
TradingBotResponse,
|
||||
UserClient
|
||||
} from '../../generated/ManagingApi'
|
||||
import type {IBotList} from '../../global/type.tsx'
|
||||
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
import useCookie from '../../hooks/useCookie'
|
||||
|
||||
function baseBadgeClass(isOutlined = false) {
|
||||
let classes = 'text-xs badge badge-sm transition-all duration-200 hover:scale-105 '
|
||||
@@ -38,10 +39,15 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const client = new BotClient({}, apiUrl)
|
||||
const userClient = new UserClient({}, apiUrl)
|
||||
const { getCookie } = useCookie()
|
||||
|
||||
// Get JWT token from cookies
|
||||
const jwtToken = getCookie('token')
|
||||
|
||||
const { data: currentUser } = useQuery({
|
||||
queryFn: () => userClient.user_GetCurrentUser(),
|
||||
queryKey: ['currentUser'],
|
||||
enabled: !!jwtToken, // Only fetch when JWT token exists
|
||||
})
|
||||
|
||||
const [showMoneyManagementModal, setShowMoneyManagementModal] =
|
||||
@@ -55,7 +61,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
|
||||
const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{
|
||||
identifier: string
|
||||
config: any
|
||||
config: TradingBotConfig
|
||||
} | null>(null)
|
||||
|
||||
// Helper function to check if current user owns the bot
|
||||
@@ -63,32 +69,6 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
return currentUser?.agentName === botAgentName
|
||||
}
|
||||
|
||||
function getIsForWatchingBadge(isForWatchingOnly: boolean, identifier: string) {
|
||||
const classes =
|
||||
baseBadgeClass() + (isForWatchingOnly ? ' bg-accent' : ' bg-primary')
|
||||
return (
|
||||
<button className={classes} onClick={() => toggleIsForWatchingOnly(identifier)}>
|
||||
{isForWatchingOnly ? (
|
||||
<p className="text-accent-content flex">
|
||||
<EyeIcon width={12}></EyeIcon>
|
||||
Watch Only
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-primary-content flex">
|
||||
<PlayIcon width={12}></PlayIcon>
|
||||
Trading
|
||||
</p>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function toggleIsForWatchingOnly(identifier: string) {
|
||||
const t = new Toast('Switch watch only')
|
||||
client.bot_ToggleIsForWatching(identifier).then(() => {
|
||||
t.update('success', 'Bot updated')
|
||||
})
|
||||
}
|
||||
function getDeleteBadge(identifier: string) {
|
||||
const classes = baseBadgeClass() + 'bg-error'
|
||||
return (
|
||||
@@ -99,17 +79,13 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
</button>
|
||||
)
|
||||
}
|
||||
function getToggleBotStatusBadge(
|
||||
status: string,
|
||||
identifier: string,
|
||||
botType: BotType
|
||||
) {
|
||||
function getToggleBotStatusBadge(status: string, identifier: string) {
|
||||
const classes =
|
||||
baseBadgeClass() + (status == 'Up' ? ' bg-error' : ' bg-success')
|
||||
return (
|
||||
<button
|
||||
className={classes}
|
||||
onClick={() => toggleBotStatus(status, identifier, botType)}
|
||||
onClick={() => toggleBotStatus(status, identifier)}
|
||||
>
|
||||
{status == 'Up' ? (
|
||||
<p className="text-accent-content flex">
|
||||
@@ -174,10 +150,11 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
setShowTradesModal(true)
|
||||
}
|
||||
|
||||
function toggleBotStatus(status: string, identifier: string, botType: BotType) {
|
||||
function toggleBotStatus(status: string, identifier: string) {
|
||||
const isUp = status == 'Up'
|
||||
const t = new Toast(isUp ? 'Stoping bot' : 'Restarting bot')
|
||||
|
||||
console.log('toggleBotStatus', status, identifier)
|
||||
if (status == 'Up') {
|
||||
client
|
||||
.bot_Stop(identifier)
|
||||
@@ -187,9 +164,9 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
.catch((err) => {
|
||||
t.update('error', err)
|
||||
})
|
||||
} else if (status == 'Down') {
|
||||
} else if (status == 'Down' || status == 'None') {
|
||||
client
|
||||
.bot_Restart(botType, identifier)
|
||||
.bot_Restart(identifier)
|
||||
.then(() => {
|
||||
t.update('success', 'Bot up')
|
||||
})
|
||||
@@ -215,7 +192,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
function getUpdateBotBadge(bot: TradingBotResponse) {
|
||||
const classes = baseBadgeClass() + ' bg-orange-500'
|
||||
return (
|
||||
<button className={classes} onClick={() => openUpdateBotModal(bot)}>
|
||||
<button className={classes} onClick={() => openUpdateBotModal(bot).catch(console.error)}>
|
||||
<p className="text-primary-content flex">
|
||||
<CogIcon width={15}></CogIcon>
|
||||
</p>
|
||||
@@ -223,12 +200,21 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
)
|
||||
}
|
||||
|
||||
function openUpdateBotModal(bot: TradingBotResponse) {
|
||||
setSelectedBotForUpdate({
|
||||
identifier: bot.identifier,
|
||||
config: bot.config
|
||||
})
|
||||
setShowBotConfigModal(true)
|
||||
async function openUpdateBotModal(bot: TradingBotResponse) {
|
||||
const t = new Toast('Loading bot configuration...')
|
||||
|
||||
try {
|
||||
const config = await client.bot_GetBotConfig(bot.identifier)
|
||||
setSelectedBotForUpdate({
|
||||
identifier: bot.identifier,
|
||||
config: config
|
||||
})
|
||||
setShowBotConfigModal(true)
|
||||
t.update('success', 'Bot configuration loaded')
|
||||
} catch (error: any) {
|
||||
t.update('error', `Error loading bot configuration: ${error.message || error}`)
|
||||
console.error('Error fetching bot config:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -243,8 +229,8 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
{bot.candles && bot.candles.length > 0 ? (
|
||||
<TradeChart
|
||||
candles={bot.candles}
|
||||
positions={bot.positions}
|
||||
signals={bot.signals}
|
||||
positions={Object.values(bot.positions)}
|
||||
signals={Object.values(bot.signals)}
|
||||
></TradeChart>
|
||||
) : null}
|
||||
</figure>
|
||||
@@ -252,21 +238,16 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
<div className="mb-4">
|
||||
{/* Bot Name - Always on its own line */}
|
||||
<h2 className="card-title text-sm mb-3">
|
||||
{bot.config.name}
|
||||
{bot.name}
|
||||
</h2>
|
||||
|
||||
{/* Badge Container - Responsive */}
|
||||
<div className="flex flex-wrap gap-1 sm:gap-2">
|
||||
{/* Info Badges */}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{getMoneyManagementBadge(bot.config.moneyManagement)}
|
||||
</div>
|
||||
|
||||
{/* Action Badges - Only show for bot owners */}
|
||||
{isBotOwner(bot.agentName) && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{getIsForWatchingBadge(bot.config.isForWatchingOnly, bot.identifier)}
|
||||
{getToggleBotStatusBadge(bot.status, bot.identifier, bot.config.flipPosition ? BotType.FlippingBot : BotType.SimpleBot)}
|
||||
{getToggleBotStatusBadge(bot.status, bot.identifier)}
|
||||
{getUpdateBotBadge(bot)}
|
||||
{getManualPositionBadge(bot.identifier)}
|
||||
{getDeleteBadge(bot.identifier)}
|
||||
@@ -280,11 +261,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
<div>
|
||||
<CardText
|
||||
title="Ticker"
|
||||
content={bot.config.ticker}
|
||||
></CardText>
|
||||
<CardText
|
||||
title="Scenario"
|
||||
content={bot.config.scenarioName ?? bot.config.scenario?.name}
|
||||
content={bot.ticker}
|
||||
></CardText>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,19 +271,19 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
title="Agent"
|
||||
content={bot.agentName}
|
||||
></CardText>
|
||||
<CardSignal signals={bot.signals}></CardSignal>
|
||||
<CardSignal signals={Object.values(bot.signals ?? {})}></CardSignal>
|
||||
</div>
|
||||
<div className="columns-2">
|
||||
<CardPosition
|
||||
positivePosition={true}
|
||||
positions={bot.positions.filter((p: Position) => {
|
||||
positions={Object.values(bot.positions ?? {}).filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized > 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
<CardPosition
|
||||
positivePosition={false}
|
||||
positions={bot.positions.filter((p: Position) => {
|
||||
positions={Object.values(bot.positions ?? {}).filter((p: Position) => {
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized <= 0 ? p : null
|
||||
})}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {PlayIcon, StopIcon, ViewGridAddIcon} from '@heroicons/react/solid'
|
||||
import {ViewGridAddIcon} from '@heroicons/react/solid'
|
||||
import React, {useState} from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {Toast} from '../../components/mollecules'
|
||||
import {UnifiedTradingModal} from '../../components/organism'
|
||||
import type {TradingBotResponse,} from '../../generated/ManagingApi'
|
||||
import {BotClient, UserClient,} from '../../generated/ManagingApi'
|
||||
import {BotClient, BotStatus, UserClient} from '../../generated/ManagingApi'
|
||||
|
||||
import BotList from './botList'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
@@ -13,87 +11,55 @@ import {useQuery} from '@tanstack/react-query'
|
||||
const Bots: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState(0)
|
||||
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
|
||||
const [pageNumber, setPageNumber] = useState(1)
|
||||
const [pageSize] = useState(50) // Fixed page size for now
|
||||
|
||||
// Reset page number when tab changes
|
||||
const handleTabChange = (newTab: number) => {
|
||||
setActiveTab(newTab)
|
||||
setPageNumber(1) // Reset to first page when changing tabs
|
||||
}
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const botClient = new BotClient({}, apiUrl)
|
||||
const userClient = new UserClient({}, apiUrl)
|
||||
|
||||
const { data: bots } = useQuery({
|
||||
queryFn: () => botClient.bot_GetActiveBots(),
|
||||
queryKey: ['bots'],
|
||||
})
|
||||
|
||||
const { data: currentUser } = useQuery({
|
||||
queryFn: () => userClient.user_GetCurrentUser(),
|
||||
queryKey: ['currentUser'],
|
||||
})
|
||||
|
||||
// Filter bots based on active tab and current user
|
||||
const getFilteredBots = (): TradingBotResponse[] => {
|
||||
if (!bots || !currentUser) return []
|
||||
|
||||
switch (activeTab) {
|
||||
case 0: // All Active Bots
|
||||
return bots.filter(bot => bot.status === 'Up')
|
||||
case 1: // My Active Bots
|
||||
return bots.filter(bot => bot.status === 'Up' && bot.agentName === currentUser.agentName)
|
||||
case 2: // My Down Bots
|
||||
return bots.filter(bot => bot.status === 'Down' && bot.agentName === currentUser.agentName)
|
||||
default:
|
||||
return bots
|
||||
}
|
||||
}
|
||||
// Query for paginated bots using the new endpoint
|
||||
const { data: paginatedBots } = useQuery({
|
||||
queryFn: () => {
|
||||
switch (activeTab) {
|
||||
case 0: // All Active Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Up, undefined, undefined, undefined, 'CreatedAt', 'Desc')
|
||||
case 1: // My Active Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Up, undefined, undefined, currentUser?.agentName, 'CreatedAt', 'Desc')
|
||||
case 2: // My Down Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.Down, undefined, undefined, currentUser?.agentName, 'CreatedAt', 'Desc')
|
||||
case 3: // Saved Bots
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, BotStatus.None, undefined, undefined, currentUser?.agentName, 'CreatedAt', 'Desc')
|
||||
default:
|
||||
return botClient.bot_GetBotsPaginated(pageNumber, pageSize, undefined, undefined, undefined, undefined, 'CreatedAt', 'Desc')
|
||||
}
|
||||
},
|
||||
queryKey: ['paginatedBots', activeTab, pageNumber, pageSize, currentUser?.agentName],
|
||||
enabled: !!currentUser,
|
||||
})
|
||||
|
||||
const filteredBots = getFilteredBots()
|
||||
const filteredBots = paginatedBots?.items || []
|
||||
|
||||
function openCreateBotModal() {
|
||||
setShowBotConfigModal(true)
|
||||
}
|
||||
|
||||
// const setupHubConnection = async () => {
|
||||
// const hub = new Hub('bothub', apiUrl).hub
|
||||
|
||||
// hub.on('BotsSubscription', (bots: TradingBotResponse[]) => {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log(
|
||||
// 'bot List',
|
||||
// bots.map((bot) => {
|
||||
// return bot.name
|
||||
// })
|
||||
// )
|
||||
// setBots(bots)
|
||||
// })
|
||||
|
||||
// return hub
|
||||
// }
|
||||
|
||||
async function stopAllBots() {
|
||||
const t = new Toast('Stoping all bots')
|
||||
await botClient
|
||||
.bot_StopAll()
|
||||
.then((result) => {
|
||||
t.update('success', 'All bots stopped')
|
||||
})
|
||||
.catch((reason) => {
|
||||
t.update('error', reason)
|
||||
})
|
||||
}
|
||||
|
||||
async function restartAllBots() {
|
||||
const t = new Toast('Restarting all bots')
|
||||
await botClient
|
||||
.bot_RestartAll()
|
||||
.then(() => {
|
||||
t.update('success', 'All bots restarted')
|
||||
})
|
||||
.catch((reason) => {
|
||||
t.update('error', reason)
|
||||
})
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ label: 'All Active Bots', index: 0 },
|
||||
{ label: 'My Active Bots', index: 1 },
|
||||
{ label: 'My Down Bots', index: 2 },
|
||||
{ label: 'Saved Bots', index: 3 },
|
||||
]
|
||||
|
||||
return (
|
||||
@@ -106,19 +72,6 @@ const Bots: React.FC = () => {
|
||||
<ViewGridAddIcon width="20"></ViewGridAddIcon>
|
||||
</button>
|
||||
</div>
|
||||
<div className="tooltip" data-tip="Stop all bots">
|
||||
<button className="btn btn-error m-1 text-xs" onClick={stopAllBots}>
|
||||
<StopIcon width="20"></StopIcon>
|
||||
</button>
|
||||
</div>
|
||||
<div className="tooltip" data-tip="Restart all bots">
|
||||
<button
|
||||
className="btn btn-success m-1 text-xs"
|
||||
onClick={restartAllBots}
|
||||
>
|
||||
<PlayIcon width="20"></PlayIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tabs */}
|
||||
@@ -127,7 +80,7 @@ const Bots: React.FC = () => {
|
||||
<button
|
||||
key={tab.index}
|
||||
className={`tab ${activeTab === tab.index ? 'tab-active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.index)}
|
||||
onClick={() => handleTabChange(tab.index)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
|
||||
@@ -15,15 +15,12 @@ const TIME_FILTERS = [
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ label: 'Total PnL', value: 'TotalPnL' },
|
||||
{ label: '24H PnL', value: 'PnLLast24h' },
|
||||
{ label: 'Total ROI', value: 'TotalROI' },
|
||||
{ label: '24H ROI', value: 'ROILast24h' },
|
||||
{ label: 'Wins', value: 'Wins' },
|
||||
{ label: 'Losses', value: 'Losses' },
|
||||
{ label: 'Win Rate', value: 'AverageWinRate' },
|
||||
{ label: 'Active Strategies', value: 'ActiveStrategiesCount' },
|
||||
{ label: 'Total Volume', value: 'TotalVolume' },
|
||||
{ label: '24H Volume', value: 'VolumeLast24h' },
|
||||
{ label: 'Agent Name', value: 'AgentName' },
|
||||
{ label: 'Created At', value: 'CreatedAt' },
|
||||
{ label: 'Updated At', value: 'UpdatedAt' },
|
||||
]
|
||||
|
||||
function AgentIndex({ index }: { index: number }) {
|
||||
@@ -40,6 +37,7 @@ function AgentIndex({ index }: { index: number }) {
|
||||
const [timeFilter, setTimeFilter] = useState('Total')
|
||||
const [sortBy, setSortBy] = useState('TotalPnL')
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
|
||||
const [agentNameFilter, setAgentNameFilter] = useState('')
|
||||
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true)
|
||||
@@ -48,11 +46,11 @@ function AgentIndex({ index }: { index: number }) {
|
||||
try {
|
||||
const client = new DataClient({}, apiUrl)
|
||||
const response = await client.data_GetAgentIndexPaginated(
|
||||
timeFilter,
|
||||
currentPage,
|
||||
pageSize,
|
||||
sortBy,
|
||||
sortOrder
|
||||
sortBy as any, // Cast to enum type
|
||||
sortOrder,
|
||||
agentNameFilter || undefined,
|
||||
)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
@@ -65,7 +63,7 @@ function AgentIndex({ index }: { index: number }) {
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [currentPage, pageSize, timeFilter, sortBy, sortOrder])
|
||||
}, [currentPage, pageSize, timeFilter, sortBy, sortOrder, agentNameFilter])
|
||||
|
||||
const handleSort = (columnId: string) => {
|
||||
if (sortBy === columnId) {
|
||||
@@ -76,6 +74,15 @@ function AgentIndex({ index }: { index: number }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handlePageChange = (newPage: number) => {
|
||||
setCurrentPage(newPage)
|
||||
}
|
||||
|
||||
const handleAgentNameFilterChange = (value: string) => {
|
||||
setAgentNameFilter(value)
|
||||
setCurrentPage(1) // Reset to first page when filtering
|
||||
}
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
Header: 'Agent Name',
|
||||
@@ -93,15 +100,6 @@ function AgentIndex({ index }: { index: number }) {
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: '24H PnL',
|
||||
accessor: 'pnLLast24h',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className={value >= 0 ? 'text-green-500' : 'text-red-500'}>
|
||||
{value >= 0 ? '+' : ''}${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Total ROI',
|
||||
accessor: 'totalROI',
|
||||
@@ -111,15 +109,6 @@ function AgentIndex({ index }: { index: number }) {
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: '24H ROI',
|
||||
accessor: 'roiLast24h',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span className={value >= 0 ? 'text-green-500' : 'text-red-500'}>
|
||||
{value >= 0 ? '+' : ''}{value.toFixed(2)}%
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Wins/Losses',
|
||||
accessor: 'wins',
|
||||
@@ -150,31 +139,28 @@ function AgentIndex({ index }: { index: number }) {
|
||||
<span>${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: '24H Volume',
|
||||
accessor: 'volumeLast24h',
|
||||
Cell: ({ value }: { value: number }) => (
|
||||
<span>${value.toLocaleString(undefined, { maximumFractionDigits: 2 })}</span>
|
||||
),
|
||||
},
|
||||
], [])
|
||||
|
||||
const tableData = useMemo(() => {
|
||||
if (!data?.agentSummaries) return []
|
||||
return data.agentSummaries.map(agent => ({
|
||||
...agent,
|
||||
// Ensure all numeric values are numbers for proper sorting
|
||||
totalPnL: Number(agent.totalPnL) || 0,
|
||||
pnLLast24h: Number(agent.pnLLast24h) || 0,
|
||||
totalROI: Number(agent.totalROI) || 0,
|
||||
roiLast24h: Number(agent.roiLast24h) || 0,
|
||||
wins: Number(agent.wins) || 0,
|
||||
losses: Number(agent.losses) || 0,
|
||||
averageWinRate: Number(agent.averageWinRate) || 0,
|
||||
activeStrategiesCount: Number(agent.activeStrategiesCount) || 0,
|
||||
totalVolume: Number(agent.totalVolume) || 0,
|
||||
volumeLast24h: Number(agent.volumeLast24h) || 0,
|
||||
}))
|
||||
return data.agentSummaries.map(agent => {
|
||||
const wins = Number(agent.wins) || 0
|
||||
const losses = Number(agent.losses) || 0
|
||||
const totalTrades = wins + losses
|
||||
const averageWinRate = totalTrades > 0 ? (wins * 100) / totalTrades : 0
|
||||
|
||||
return {
|
||||
...agent,
|
||||
// Ensure all numeric values are numbers for proper sorting
|
||||
totalPnL: Number(agent.totalPnL) || 0,
|
||||
totalROI: Number(agent.totalROI) || 0,
|
||||
wins,
|
||||
losses,
|
||||
averageWinRate,
|
||||
activeStrategiesCount: Number(agent.activeStrategiesCount) || 0,
|
||||
totalVolume: Number(agent.totalVolume) || 0,
|
||||
}
|
||||
})
|
||||
}, [data?.agentSummaries])
|
||||
|
||||
return (
|
||||
@@ -183,18 +169,14 @@ function AgentIndex({ index }: { index: number }) {
|
||||
{/* Filters and Controls */}
|
||||
<div className="flex flex-wrap gap-4 mb-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-sm font-medium">Time Filter:</label>
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={timeFilter}
|
||||
onChange={(e) => setTimeFilter(e.target.value)}
|
||||
>
|
||||
{TIME_FILTERS.map(filter => (
|
||||
<option key={filter.value} value={filter.value}>
|
||||
{filter.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="text-sm font-medium">Agent Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input input-bordered input-sm w-48"
|
||||
placeholder="Filter by agent name..."
|
||||
value={agentNameFilter}
|
||||
onChange={(e) => handleAgentNameFilterChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -229,7 +211,10 @@ function AgentIndex({ index }: { index: number }) {
|
||||
<select
|
||||
className="select select-bordered select-sm"
|
||||
value={pageSize}
|
||||
onChange={(e) => setPageSize(Number(e.target.value))}
|
||||
onChange={(e) => {
|
||||
setPageSize(Number(e.target.value))
|
||||
setCurrentPage(1) // Reset to first page when changing page size
|
||||
}}
|
||||
>
|
||||
{[10, 20, 50, 100].map(size => (
|
||||
<option key={size} value={size}>
|
||||
@@ -260,7 +245,10 @@ function AgentIndex({ index }: { index: number }) {
|
||||
<div className="mb-4 p-4 bg-base-200 rounded-lg">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="text-sm opacity-70">Showing {data.agentSummaries?.length || 0} of {data.totalCount || 0} agents</span>
|
||||
<span className="text-sm opacity-70">
|
||||
Showing {data.agentSummaries?.length || 0} of {data.totalCount || 0} agents
|
||||
{agentNameFilter && ` (filtered by "${agentNameFilter}")`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm opacity-70">
|
||||
Page {data.currentPage || 1} of {data.totalPages || 1}
|
||||
@@ -284,33 +272,55 @@ function AgentIndex({ index }: { index: number }) {
|
||||
<div className="flex justify-center items-center gap-2 mt-4">
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(1)}
|
||||
onClick={() => handlePageChange(1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
{'<<'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(currentPage - 1)}
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={!data.hasPreviousPage}
|
||||
>
|
||||
{'<'}
|
||||
</button>
|
||||
|
||||
<span className="px-4">
|
||||
Page {currentPage} of {data.totalPages}
|
||||
</span>
|
||||
{/* Page numbers */}
|
||||
<div className="flex gap-1">
|
||||
{Array.from({ length: Math.min(5, data.totalPages || 1) }, (_, i) => {
|
||||
let pageNum
|
||||
if ((data.totalPages || 1) <= 5) {
|
||||
pageNum = i + 1
|
||||
} else if (currentPage <= 3) {
|
||||
pageNum = i + 1
|
||||
} else if (currentPage >= (data.totalPages || 1) - 2) {
|
||||
pageNum = (data.totalPages || 1) - 4 + i
|
||||
} else {
|
||||
pageNum = currentPage - 2 + i
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
key={pageNum}
|
||||
className={`btn btn-sm ${currentPage === pageNum ? 'btn-primary' : ''}`}
|
||||
onClick={() => handlePageChange(pageNum)}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(currentPage + 1)}
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={!data.hasNextPage}
|
||||
>
|
||||
{'>'}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm"
|
||||
onClick={() => setCurrentPage(data.totalPages || 1)}
|
||||
onClick={() => handlePageChange(data.totalPages || 1)}
|
||||
disabled={currentPage === data.totalPages}
|
||||
>
|
||||
{'>>'}
|
||||
@@ -321,7 +331,20 @@ function AgentIndex({ index }: { index: number }) {
|
||||
{/* No Data State */}
|
||||
{data && (!data.agentSummaries || data.agentSummaries.length === 0) && !isLoading && (
|
||||
<div className="text-center py-8">
|
||||
<p>No agents found for the selected criteria.</p>
|
||||
<p>
|
||||
{agentNameFilter
|
||||
? `No agents found matching "${agentNameFilter}".`
|
||||
: 'No agents found for the selected criteria.'
|
||||
}
|
||||
</p>
|
||||
{agentNameFilter && (
|
||||
<button
|
||||
className="btn btn-sm btn-outline mt-2"
|
||||
onClick={() => handleAgentNameFilterChange('')}
|
||||
>
|
||||
Clear Filter
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</GridTile>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {UserClient} from '../../generated/ManagingApi'
|
||||
import Modal from '../../components/mollecules/Modal/Modal'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {Toast} from '../../components/mollecules'
|
||||
import useCookie from '../../hooks/useCookie'
|
||||
|
||||
type UpdateAgentNameForm = {
|
||||
agentName: string
|
||||
@@ -25,10 +26,15 @@ function UserInfoSettings() {
|
||||
const queryClient = useQueryClient()
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const api = new UserClient({}, apiUrl)
|
||||
const { getCookie } = useCookie()
|
||||
|
||||
// Get JWT token from cookies
|
||||
const jwtToken = getCookie('token')
|
||||
|
||||
const { data: user } = useQuery({
|
||||
queryKey: ['user'],
|
||||
queryFn: () => api.user_GetCurrentUser(),
|
||||
enabled: !!jwtToken, // Only fetch when JWT token exists
|
||||
})
|
||||
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user