Keep dashboard updates live without jitter
All checks were successful
deploy / deploy (push) Successful in 1m3s
All checks were successful
deploy / deploy (push) Successful in 1m3s
Proof: Quote lifecycle and competitiveness now update live while unpaused, with fixed row slots and clamped cells carrying the jitter prevention; static UI coverage, full npm test, and the operator dashboard build validate the correction. Assumptions: this is a UI-only operator cleanup for the active maker timing and competitiveness turn; it does not change strategy decisions, execution, DB config, persistence, live funds, or response policy. Still fake: venue-native terminal fill ids and fee-complete realized PnL remain unavailable.
This commit is contained in:
parent
cf4160245f
commit
7b2f31fd4d
2 changed files with 29 additions and 34 deletions
|
|
@ -8,11 +8,9 @@ import { formatAgeFromTimestamp, formatBoolean, formatEur, formatTimestamp, trun
|
|||
|
||||
const RESPONDED_STATES = new Set(['submitted', 'awaiting_outcome', 'not_filled', 'completed']);
|
||||
const TRADING_PAIR_MODES = new Set(['maker', 'taker', 'both']);
|
||||
const COMPETITIVENESS_REFRESH_MS = 5_000;
|
||||
const COMPETITIVENESS_GROUP_ROW_COUNT = 12;
|
||||
const COMPETITIVENESS_DETAIL_ROW_COUNT = 8;
|
||||
const COMPETITIVENESS_LATENCY_ROW_COUNT = 6;
|
||||
const QUOTE_LIFECYCLE_REFRESH_MS = 5_000;
|
||||
const QUOTE_LIFECYCLE_ROW_COUNT = 20;
|
||||
|
||||
async function copyIdentifier(value) {
|
||||
|
|
@ -24,6 +22,17 @@ async function copyIdentifier(value) {
|
|||
}
|
||||
}
|
||||
|
||||
function useNow(intervalMs = 1000) {
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => setNow(Date.now()), intervalMs);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [intervalMs]);
|
||||
|
||||
return now;
|
||||
}
|
||||
|
||||
function formatRelativeAge(value, now) {
|
||||
const age = formatAgeFromTimestamp(value, now);
|
||||
return age === 'Unavailable' ? 'Age unavailable' : `${age} ago`;
|
||||
|
|
@ -274,7 +283,6 @@ function fixedRows(items, count) {
|
|||
function MakerCompetitivenessSection({ summary, pairConfig }) {
|
||||
const latestSummaryRef = useRef(summary || {});
|
||||
const [displaySummary, setDisplaySummary] = useState(() => summary || {});
|
||||
const [displayUpdatedAt, setDisplayUpdatedAt] = useState(() => new Date().toISOString());
|
||||
const [updatesPaused, setUpdatesPaused] = useState(false);
|
||||
const total = displaySummary?.total || {};
|
||||
const groups = displaySummary?.groups || [];
|
||||
|
|
@ -290,23 +298,14 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
|
||||
useEffect(() => {
|
||||
latestSummaryRef.current = summary || {};
|
||||
}, [summary]);
|
||||
|
||||
useEffect(() => {
|
||||
if (updatesPaused) return undefined;
|
||||
const timer = window.setInterval(() => {
|
||||
setDisplaySummary(latestSummaryRef.current || {});
|
||||
setDisplayUpdatedAt(new Date().toISOString());
|
||||
}, COMPETITIVENESS_REFRESH_MS);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [updatesPaused]);
|
||||
if (!updatesPaused) setDisplaySummary(summary || {});
|
||||
}, [summary, updatesPaused]);
|
||||
|
||||
function toggleUpdatesPaused() {
|
||||
setUpdatesPaused((current) => {
|
||||
const nextPaused = !current;
|
||||
if (current) {
|
||||
setDisplaySummary(latestSummaryRef.current || {});
|
||||
setDisplayUpdatedAt(new Date().toISOString());
|
||||
}
|
||||
return nextPaused;
|
||||
});
|
||||
|
|
@ -325,7 +324,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
<div className="pills">
|
||||
<Pill label={`${total.count || 0} rows`} stateLabel="info" />
|
||||
<Pill label={`${total.quote_not_found_or_finished_count || 0} stale/finished`} stateLabel={(total.quote_not_found_or_finished_count || 0) > 0 ? 'warning' : 'unknown'} />
|
||||
<Pill label={updatesPaused ? 'Updates paused' : 'Timed snapshot'} stateLabel={updatesPaused ? 'warning' : 'healthy'} />
|
||||
<Pill label={updatesPaused ? 'Updates paused' : 'Live updates'} stateLabel={updatesPaused ? 'warning' : 'healthy'} />
|
||||
<button
|
||||
aria-pressed={updatesPaused}
|
||||
className="button secondary trace-copy-button"
|
||||
|
|
@ -337,7 +336,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="status-subtle competitiveness-snapshot-note">
|
||||
Display snapshot {formatTimestamp(displayUpdatedAt)}. Live updates are applied every {COMPETITIVENESS_REFRESH_MS / 1000}s to keep table height stable.
|
||||
Live rows update as they arrive. Fixed row slots and clamped cells keep the table height stable.
|
||||
</div>
|
||||
<div className="metric-stack">
|
||||
<MetricCard label="Accepted" meta={formatRate(total.accepted_rate)} value={String(total.accepted_count || 0)} />
|
||||
|
|
@ -564,23 +563,17 @@ function QuoteLifecycleTable({ items }) {
|
|||
const latestItemsRef = useRef(items || []);
|
||||
const [quoteDisplayPaused, setQuoteDisplayPaused] = useState(false);
|
||||
const [displayItems, setDisplayItems] = useState(() => items || []);
|
||||
const liveNow = useNow();
|
||||
const [displayNow, setDisplayNow] = useState(() => Date.now());
|
||||
const [displayUpdatedAt, setDisplayUpdatedAt] = useState(() => new Date().toISOString());
|
||||
|
||||
useEffect(() => {
|
||||
latestItemsRef.current = items || [];
|
||||
}, [items]);
|
||||
if (!quoteDisplayPaused) setDisplayItems(items || []);
|
||||
}, [items, quoteDisplayPaused]);
|
||||
|
||||
useEffect(() => {
|
||||
if (quoteDisplayPaused) return undefined;
|
||||
const timer = window.setInterval(() => {
|
||||
const now = Date.now();
|
||||
setDisplayItems(latestItemsRef.current || []);
|
||||
setDisplayNow(now);
|
||||
setDisplayUpdatedAt(new Date(now).toISOString());
|
||||
}, QUOTE_LIFECYCLE_REFRESH_MS);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [quoteDisplayPaused]);
|
||||
if (!quoteDisplayPaused) setDisplayNow(liveNow);
|
||||
}, [liveNow, quoteDisplayPaused]);
|
||||
|
||||
const rejectedCount = useMemo(
|
||||
() => displayItems.filter((item) => isStrategyRejected(item)).length,
|
||||
|
|
@ -607,10 +600,8 @@ function QuoteLifecycleTable({ items }) {
|
|||
}
|
||||
|
||||
function applyLatestLifecycleDisplay() {
|
||||
const now = Date.now();
|
||||
setDisplayItems(latestItemsRef.current || []);
|
||||
setDisplayNow(now);
|
||||
setDisplayUpdatedAt(new Date(now).toISOString());
|
||||
setDisplayNow(Date.now());
|
||||
}
|
||||
|
||||
function toggleQuoteDisplayPaused() {
|
||||
|
|
@ -637,10 +628,10 @@ function QuoteLifecycleTable({ items }) {
|
|||
>
|
||||
{quoteDisplayPaused ? 'Resume display' : 'Pause display'}
|
||||
</button>
|
||||
<Pill label={quoteDisplayPaused ? 'Display paused' : 'Timed snapshot'} stateLabel={quoteDisplayPaused ? 'warning' : 'healthy'} />
|
||||
<Pill label={quoteDisplayPaused ? 'Display paused' : 'Live updates'} stateLabel={quoteDisplayPaused ? 'warning' : 'healthy'} />
|
||||
</div>
|
||||
<div className="status-subtle quote-lifecycle-snapshot-note">
|
||||
Display snapshot {formatTimestamp(displayUpdatedAt)}. Live rows are applied every {QUOTE_LIFECYCLE_REFRESH_MS / 1000}s to keep table height stable.
|
||||
Live rows update as they arrive. Fixed row slots and clamped cells keep the table height stable.
|
||||
</div>
|
||||
|
||||
<TableFrame className="quote-lifecycle-frame">
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@ test('strategy page owns consolidated quote lifecycle and successful trade table
|
|||
assert.match(strategySource, /quoteDisplayPaused/);
|
||||
assert.match(strategySource, /Pause display/);
|
||||
assert.match(strategySource, /Resume display/);
|
||||
assert.match(strategySource, /QUOTE_LIFECYCLE_REFRESH_MS/);
|
||||
assert.match(strategySource, /QUOTE_LIFECYCLE_ROW_COUNT/);
|
||||
assert.match(strategySource, /latestItemsRef/);
|
||||
assert.match(strategySource, /setDisplayItems\(items \|\| \[\]\)/);
|
||||
assert.match(strategySource, /useNow\(\)/);
|
||||
assert.match(strategySource, /Live updates/);
|
||||
assert.match(strategySource, /Live rows update as they arrive/);
|
||||
assert.match(strategySource, /visibleRows/);
|
||||
assert.match(strategySource, /quote-lifecycle-placeholder-row/);
|
||||
assert.match(stylesSource, /\.quote-lifecycle-table tbody tr\.quote-lifecycle-row/);
|
||||
|
|
@ -144,7 +147,8 @@ test('strategy page exposes maker timing waterfall and competitiveness summaries
|
|||
assert.match(strategySource, /Resume updates/);
|
||||
assert.match(strategySource, /displaySummary/);
|
||||
assert.match(strategySource, /fixedRows/);
|
||||
assert.match(strategySource, /COMPETITIVENESS_REFRESH_MS/);
|
||||
assert.match(strategySource, /latestSummaryRef/);
|
||||
assert.match(strategySource, /Live updates/);
|
||||
assert.match(strategySource, /latencyStageRows/);
|
||||
assert.match(stylesSource, /\.metric-stack/);
|
||||
assert.match(stylesSource, /\.competitiveness-table-stack/);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue