'use client';
import { useMemo, useState } from 'react';
import {
Badge,
Box,
Button,
Flex,
Menu,
Portal,
SimpleGrid,
Stat,
Text,
} from '@chakra-ui/react';
import { getFormatted } from '@tradejs/core/backtest';
import type { RuntimeStrategyView, TestThresholdsKey } from '@tradejs/types';
import {
formatDateTime,
OrdersDrawerPanel,
} from '#components/Shared/OrdersDrawer';
import { RuntimeStrategyChart } from './RuntimeStrategyChart';
import { RuntimeStrategyConfigDrawer } from './RuntimeStrategyConfigDrawer';
import { RuntimeStrategyRevisionsDrawer } from './RuntimeStrategyRevisionsDrawer';
import { RuntimeStrategyStatsDrawer } from './RuntimeStrategyStatsDrawer';
import {
buildRuntimeStrategyCardViewModel,
getColorByLevel,
RUNTIME_ORDER_ROW_HEIGHT,
} from './RuntimeStrategyCard.presenter';
import { toaster } from '#ui';
const DEPLOYMENT_PRESENTATIONS = [
{ colorPalette: 'blue' },
{ colorPalette: 'purple' },
{ colorPalette: 'pink' },
{ colorPalette: 'cyan' },
] as const;
const getDeploymentPresentation = (deploymentId: string) => {
const hash = [...deploymentId].reduce(
(value, character) => (value * 31 + character.charCodeAt(0)) >>> 0,
0,
);
return DEPLOYMENT_PRESENTATIONS[hash % DEPLOYMENT_PRESENTATIONS.length];
};
const StatItem = ({
stat,
id,
title,
}: {
stat: RuntimeStrategyView['stat'];
id: TestThresholdsKey;
title: string;
}) => {
const { formatted, level } = getFormatted(stat, id);
return (
{title}
{formatted}
);
};
export const RuntimeStrategyCard = ({
strategy,
provider,
startTimestamp,
endTimestamp,
onUpdated,
}: {
strategy: RuntimeStrategyView;
provider: string;
startTimestamp: number;
endTimestamp: number;
onUpdated: () => Promise | void;
}) => {
const [configOpen, setConfigOpen] = useState(false);
const [ordersOpen, setOrdersOpen] = useState(false);
const [statsOpen, setStatsOpen] = useState(false);
const [revisionsOpen, setRevisionsOpen] = useState(false);
const [controlSaving, setControlSaving] = useState(false);
const viewModel = useMemo(
() => buildRuntimeStrategyCardViewModel(strategy),
[strategy],
);
const { lastTrade, runtimeOrders } = viewModel;
const deploymentPresentation = getDeploymentPresentation(
strategy.deploymentId,
);
const strategyInstanceLabel = `${strategy.strategyName}, ${strategy.deploymentLabel}`;
const setControlState = async (controlState: 'active' | 'entries_paused') => {
setControlSaving(true);
try {
const response = await fetch(
`/api/user/runtime-deployments/${encodeURIComponent(strategy.deploymentId)}/strategies/${encodeURIComponent(strategy.strategyName)}/control`,
{
method: 'PATCH',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ controlState }),
},
);
const payload = (await response.json()) as { error?: string };
if (!response.ok) throw new Error(payload.error || 'Update failed');
toaster.success({
title:
controlState === 'entries_paused'
? 'New entries paused'
: 'Strategy resumed',
});
await onUpdated();
} catch (error) {
toaster.error({
title: 'Could not update strategy state',
description: error instanceof Error ? error.message : String(error),
});
} finally {
setControlSaving(false);
}
};
return (
{strategy.strategyName}
{strategy.deploymentLabel}
{strategy.controlState === 'entries_paused'
? 'entries paused'
: strategy.enabled
? 'enabled'
: 'disabled'}
{strategy.universe}
TF: {strategy.interval}m
connector:
{provider}
trades:
{strategy.summary.totalTrades}
active:
0 ? 'orange.300' : 'gray.200'
}
>
{strategy.summary.activeTrades}
{lastTrade
? `last trade: ${lastTrade.symbol} ${formatDateTime(lastTrade.entryTimestamp)}`
: strategy.connected
? 'connected, no runtime trades yet'
: 'runtime trades only'}
setConfigOpen(true)}>
View config
void setControlState(
strategy.controlState === 'entries_paused'
? 'active'
: 'entries_paused',
)
}
>
{strategy.controlState === 'entries_paused'
? 'Resume entries'
: 'Pause new entries'}
setOrdersOpen(true)}>
Orders
setStatsOpen(true)}>
Stat
setRevisionsOpen(true)}
>
Revisions
);
};