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 RESPONDED_STATES = new Set(['submitted', 'awaiting_outcome', 'not_filled', 'completed']);
|
||||||
const TRADING_PAIR_MODES = new Set(['maker', 'taker', 'both']);
|
const TRADING_PAIR_MODES = new Set(['maker', 'taker', 'both']);
|
||||||
const COMPETITIVENESS_REFRESH_MS = 5_000;
|
|
||||||
const COMPETITIVENESS_GROUP_ROW_COUNT = 12;
|
const COMPETITIVENESS_GROUP_ROW_COUNT = 12;
|
||||||
const COMPETITIVENESS_DETAIL_ROW_COUNT = 8;
|
const COMPETITIVENESS_DETAIL_ROW_COUNT = 8;
|
||||||
const COMPETITIVENESS_LATENCY_ROW_COUNT = 6;
|
const COMPETITIVENESS_LATENCY_ROW_COUNT = 6;
|
||||||
const QUOTE_LIFECYCLE_REFRESH_MS = 5_000;
|
|
||||||
const QUOTE_LIFECYCLE_ROW_COUNT = 20;
|
const QUOTE_LIFECYCLE_ROW_COUNT = 20;
|
||||||
|
|
||||||
async function copyIdentifier(value) {
|
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) {
|
function formatRelativeAge(value, now) {
|
||||||
const age = formatAgeFromTimestamp(value, now);
|
const age = formatAgeFromTimestamp(value, now);
|
||||||
return age === 'Unavailable' ? 'Age unavailable' : `${age} ago`;
|
return age === 'Unavailable' ? 'Age unavailable' : `${age} ago`;
|
||||||
|
|
@ -274,7 +283,6 @@ function fixedRows(items, count) {
|
||||||
function MakerCompetitivenessSection({ summary, pairConfig }) {
|
function MakerCompetitivenessSection({ summary, pairConfig }) {
|
||||||
const latestSummaryRef = useRef(summary || {});
|
const latestSummaryRef = useRef(summary || {});
|
||||||
const [displaySummary, setDisplaySummary] = useState(() => summary || {});
|
const [displaySummary, setDisplaySummary] = useState(() => summary || {});
|
||||||
const [displayUpdatedAt, setDisplayUpdatedAt] = useState(() => new Date().toISOString());
|
|
||||||
const [updatesPaused, setUpdatesPaused] = useState(false);
|
const [updatesPaused, setUpdatesPaused] = useState(false);
|
||||||
const total = displaySummary?.total || {};
|
const total = displaySummary?.total || {};
|
||||||
const groups = displaySummary?.groups || [];
|
const groups = displaySummary?.groups || [];
|
||||||
|
|
@ -290,23 +298,14 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
latestSummaryRef.current = summary || {};
|
latestSummaryRef.current = summary || {};
|
||||||
}, [summary]);
|
if (!updatesPaused) setDisplaySummary(summary || {});
|
||||||
|
}, [summary, updatesPaused]);
|
||||||
useEffect(() => {
|
|
||||||
if (updatesPaused) return undefined;
|
|
||||||
const timer = window.setInterval(() => {
|
|
||||||
setDisplaySummary(latestSummaryRef.current || {});
|
|
||||||
setDisplayUpdatedAt(new Date().toISOString());
|
|
||||||
}, COMPETITIVENESS_REFRESH_MS);
|
|
||||||
return () => window.clearInterval(timer);
|
|
||||||
}, [updatesPaused]);
|
|
||||||
|
|
||||||
function toggleUpdatesPaused() {
|
function toggleUpdatesPaused() {
|
||||||
setUpdatesPaused((current) => {
|
setUpdatesPaused((current) => {
|
||||||
const nextPaused = !current;
|
const nextPaused = !current;
|
||||||
if (current) {
|
if (current) {
|
||||||
setDisplaySummary(latestSummaryRef.current || {});
|
setDisplaySummary(latestSummaryRef.current || {});
|
||||||
setDisplayUpdatedAt(new Date().toISOString());
|
|
||||||
}
|
}
|
||||||
return nextPaused;
|
return nextPaused;
|
||||||
});
|
});
|
||||||
|
|
@ -325,7 +324,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
||||||
<div className="pills">
|
<div className="pills">
|
||||||
<Pill label={`${total.count || 0} rows`} stateLabel="info" />
|
<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={`${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
|
<button
|
||||||
aria-pressed={updatesPaused}
|
aria-pressed={updatesPaused}
|
||||||
className="button secondary trace-copy-button"
|
className="button secondary trace-copy-button"
|
||||||
|
|
@ -337,7 +336,7 @@ function MakerCompetitivenessSection({ summary, pairConfig }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="status-subtle competitiveness-snapshot-note">
|
<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>
|
||||||
<div className="metric-stack">
|
<div className="metric-stack">
|
||||||
<MetricCard label="Accepted" meta={formatRate(total.accepted_rate)} value={String(total.accepted_count || 0)} />
|
<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 latestItemsRef = useRef(items || []);
|
||||||
const [quoteDisplayPaused, setQuoteDisplayPaused] = useState(false);
|
const [quoteDisplayPaused, setQuoteDisplayPaused] = useState(false);
|
||||||
const [displayItems, setDisplayItems] = useState(() => items || []);
|
const [displayItems, setDisplayItems] = useState(() => items || []);
|
||||||
|
const liveNow = useNow();
|
||||||
const [displayNow, setDisplayNow] = useState(() => Date.now());
|
const [displayNow, setDisplayNow] = useState(() => Date.now());
|
||||||
const [displayUpdatedAt, setDisplayUpdatedAt] = useState(() => new Date().toISOString());
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
latestItemsRef.current = items || [];
|
latestItemsRef.current = items || [];
|
||||||
}, [items]);
|
if (!quoteDisplayPaused) setDisplayItems(items || []);
|
||||||
|
}, [items, quoteDisplayPaused]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quoteDisplayPaused) return undefined;
|
if (!quoteDisplayPaused) setDisplayNow(liveNow);
|
||||||
const timer = window.setInterval(() => {
|
}, [liveNow, quoteDisplayPaused]);
|
||||||
const now = Date.now();
|
|
||||||
setDisplayItems(latestItemsRef.current || []);
|
|
||||||
setDisplayNow(now);
|
|
||||||
setDisplayUpdatedAt(new Date(now).toISOString());
|
|
||||||
}, QUOTE_LIFECYCLE_REFRESH_MS);
|
|
||||||
return () => window.clearInterval(timer);
|
|
||||||
}, [quoteDisplayPaused]);
|
|
||||||
|
|
||||||
const rejectedCount = useMemo(
|
const rejectedCount = useMemo(
|
||||||
() => displayItems.filter((item) => isStrategyRejected(item)).length,
|
() => displayItems.filter((item) => isStrategyRejected(item)).length,
|
||||||
|
|
@ -607,10 +600,8 @@ function QuoteLifecycleTable({ items }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyLatestLifecycleDisplay() {
|
function applyLatestLifecycleDisplay() {
|
||||||
const now = Date.now();
|
|
||||||
setDisplayItems(latestItemsRef.current || []);
|
setDisplayItems(latestItemsRef.current || []);
|
||||||
setDisplayNow(now);
|
setDisplayNow(Date.now());
|
||||||
setDisplayUpdatedAt(new Date(now).toISOString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleQuoteDisplayPaused() {
|
function toggleQuoteDisplayPaused() {
|
||||||
|
|
@ -637,10 +628,10 @@ function QuoteLifecycleTable({ items }) {
|
||||||
>
|
>
|
||||||
{quoteDisplayPaused ? 'Resume display' : 'Pause display'}
|
{quoteDisplayPaused ? 'Resume display' : 'Pause display'}
|
||||||
</button>
|
</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>
|
||||||
<div className="status-subtle quote-lifecycle-snapshot-note">
|
<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>
|
</div>
|
||||||
|
|
||||||
<TableFrame className="quote-lifecycle-frame">
|
<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, /quoteDisplayPaused/);
|
||||||
assert.match(strategySource, /Pause display/);
|
assert.match(strategySource, /Pause display/);
|
||||||
assert.match(strategySource, /Resume display/);
|
assert.match(strategySource, /Resume display/);
|
||||||
assert.match(strategySource, /QUOTE_LIFECYCLE_REFRESH_MS/);
|
|
||||||
assert.match(strategySource, /QUOTE_LIFECYCLE_ROW_COUNT/);
|
assert.match(strategySource, /QUOTE_LIFECYCLE_ROW_COUNT/);
|
||||||
assert.match(strategySource, /latestItemsRef/);
|
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, /visibleRows/);
|
||||||
assert.match(strategySource, /quote-lifecycle-placeholder-row/);
|
assert.match(strategySource, /quote-lifecycle-placeholder-row/);
|
||||||
assert.match(stylesSource, /\.quote-lifecycle-table tbody tr\.quote-lifecycle-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, /Resume updates/);
|
||||||
assert.match(strategySource, /displaySummary/);
|
assert.match(strategySource, /displaySummary/);
|
||||||
assert.match(strategySource, /fixedRows/);
|
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(strategySource, /latencyStageRows/);
|
||||||
assert.match(stylesSource, /\.metric-stack/);
|
assert.match(stylesSource, /\.metric-stack/);
|
||||||
assert.match(stylesSource, /\.competitiveness-table-stack/);
|
assert.match(stylesSource, /\.competitiveness-table-stack/);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue