gloomberb 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/app/runtime/desktop-menu.ts +5 -1
- package/src/app/runtime/ticker-refresh.ts +4 -1
- package/src/app.tsx +10 -1
- package/src/brokers/account-cache.ts +1 -1
- package/src/brokers/sync-broker-instance.test.ts +107 -0
- package/src/brokers/sync-broker-instance.ts +22 -10
- package/src/cli/desktop-pane-shot.ts +2 -1
- package/src/cli/pane-functions/screenshot.ts +13 -1
- package/src/components/chart/bar-chart-renderer.test.ts +108 -3
- package/src/components/chart/bar-chart-renderer.ts +226 -27
- package/src/components/chart/core/pointer.ts +32 -6
- package/src/components/chart/price-axis-labels.tsx +3 -1
- package/src/components/chart/static/bar-chart-surface.test.tsx +97 -0
- package/src/components/chart/static/bar-chart-surface.tsx +151 -28
- package/src/components/chart/static/chart/axis-overlays.tsx +243 -0
- package/src/components/chart/static/chart/surface.test.tsx +92 -0
- package/src/components/chart/static/chart/surface.tsx +167 -24
- package/src/components/chart/stock-chart/index.test.ts +25 -0
- package/src/components/command-bar/layout-items/current-layout.ts +5 -1
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +3 -2
- package/src/components/layout/pane/content.tsx +20 -8
- package/src/components/layout/pane/footer/registration.tsx +6 -2
- package/src/components/layout/pane/frame.tsx +4 -4
- package/src/components/layout/pane/header.tsx +40 -4
- package/src/components/layout/pane/index.tsx +3 -1
- package/src/components/layout/pane/sizing.test.ts +43 -0
- package/src/components/layout/pane/sizing.ts +13 -4
- package/src/components/layout/shell/cursor-occlusion.ts +196 -0
- package/src/components/layout/shell/drag/runtime.ts +9 -2
- package/src/components/layout/shell/index.test.tsx +92 -2
- package/src/components/layout/shell/index.tsx +37 -2
- package/src/components/layout/shell/layout-state.test.ts +33 -0
- package/src/components/layout/shell/layout-state.ts +4 -9
- package/src/components/layout/shell/pane/layers.tsx +8 -2
- package/src/components/layout/shell/visible-layout.ts +17 -0
- package/src/components/layout/status-bar.test.tsx +6 -1
- package/src/components/layout/status-bar.tsx +5 -1
- package/src/components/onboarding/onboarding-steps.tsx +8 -28
- package/src/components/onboarding/onboarding-wizard.test.tsx +13 -12
- package/src/components/onboarding/onboarding-wizard.tsx +2 -4
- package/src/components/onboarding/portfolio-step/index.tsx +0 -12
- package/src/components/onboarding/portfolio-step/types.ts +1 -3
- package/src/components/onboarding/wizard-keyboard.ts +2 -5
- package/src/components/table-view-shared.tsx +12 -4
- package/src/components/ui/data-table/opentui/index.tsx +25 -12
- package/src/components/ui/data-table/types.ts +4 -0
- package/src/components/ui/fields.tsx +10 -3
- package/src/components/ui/page-stack-view.tsx +6 -2
- package/src/components/ui/table-layout.test.ts +25 -0
- package/src/components/ui/table-layout.ts +22 -6
- package/src/components/ui/tabs.tsx +26 -10
- package/src/components/ui/ui.test.tsx +87 -0
- package/src/core/state/app/layout-reducer.ts +2 -2
- package/src/plugins/builtin/analytics/index.test.tsx +6 -2
- package/src/plugins/builtin/analytics/pane-model.ts +43 -8
- package/src/plugins/builtin/kelly-sizer/constants.ts +3 -0
- package/src/plugins/builtin/kelly-sizer/fields.ts +214 -0
- package/src/plugins/builtin/kelly-sizer/index.test.tsx +197 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +47 -0
- package/src/plugins/builtin/kelly-sizer/model.test.ts +152 -0
- package/src/plugins/builtin/kelly-sizer/model.ts +580 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +558 -0
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +71 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +142 -0
- package/src/plugins/builtin/kelly-sizer/state.ts +67 -0
- package/src/plugins/builtin/kelly-sizer/test-support.tsx +174 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +156 -0
- package/src/plugins/builtin/kelly-sizer/view.test.tsx +77 -0
- package/src/plugins/builtin/kelly-sizer/view.tsx +375 -0
- package/src/plugins/builtin/layout-manager/index.tsx +5 -1
- package/src/plugins/builtin/options/view.test.tsx +141 -4
- package/src/plugins/builtin/options/view.tsx +76 -15
- package/src/plugins/builtin/portfolio-list/account-metrics.test.ts +26 -1
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +14 -0
- package/src/plugins/builtin/portfolio-list/index.test.ts +23 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +2 -0
- package/src/plugins/builtin/portfolio-list/summary/index.test.ts +59 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +36 -5
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +149 -48
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +5 -4
- package/src/plugins/builtin/ticker-detail/data-panes.test.ts +16 -0
- package/src/plugins/builtin/ticker-detail/index.test.tsx +143 -0
- package/src/plugins/catalog-ui.ts +2 -0
- package/src/plugins/ibkr/broker-adapter.ts +19 -0
- package/src/plugins/ibkr/config.test.ts +31 -2
- package/src/plugins/ibkr/config.ts +9 -2
- package/src/plugins/ibkr/flex/index.test.ts +18 -9
- package/src/plugins/ibkr/flex/index.ts +30 -0
- package/src/plugins/ibkr/flex/statement-client.ts +68 -14
- package/src/plugins/ibkr/gateway/account-loaders.ts +1 -1
- package/src/plugins/ibkr/gateway/account-summary.ts +1 -0
- package/src/plugins/ibkr/gateway/service/index.test.ts +8 -0
- package/src/plugins/pane-manager/gridlock.ts +13 -5
- package/src/plugins/pane-manager/layout-state.ts +25 -0
- package/src/plugins/pane-manager.test.ts +33 -0
- package/src/plugins/pane-manager.ts +6 -1
- package/src/plugins/prediction-markets/chart.tsx +10 -4
- package/src/plugins/prediction-markets/detail/book.tsx +141 -96
- package/src/plugins/prediction-markets/detail/overview.tsx +3 -0
- package/src/plugins/prediction-markets/detail/pane.tsx +71 -64
- package/src/plugins/prediction-markets/detail/shared.tsx +19 -0
- package/src/plugins/prediction-markets/detail/trades.tsx +64 -19
- package/src/plugins/prediction-markets/detail.test.tsx +50 -0
- package/src/plugins/prediction-markets/pane.test.tsx +43 -0
- package/src/plugins/prediction-markets/pane.tsx +50 -22
- package/src/plugins/prediction-markets/test-helpers.tsx +41 -23
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +13 -2
- package/src/renderers/electrobun/view/data-table/index.tsx +19 -4
- package/src/renderers/electrobun/view/data-table/row.tsx +30 -6
- package/src/renderers/electrobun/view/desktop/controls.tsx +11 -5
- package/src/renderers/electrobun/view/host/input.tsx +115 -18
- package/src/renderers/electrobun/view/host/tabs.tsx +7 -2
- package/src/renderers/electrobun/view/styles.css +11 -0
- package/src/renderers/opentui/host.tsx +17 -1
- package/src/renderers/opentui/ui-host.tsx +57 -25
- package/src/sources/provider-router/batches.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +5 -0
- package/src/sources/provider-router/financials.ts +11 -23
- package/src/sources/provider-router/index.test.ts +189 -0
- package/src/sources/provider-router/primary.ts +3 -2
- package/src/sources/sec-edgar.test.ts +83 -0
- package/src/sources/sec-edgar.ts +231 -0
- package/src/sources/yahoo-finance.test.ts +51 -0
- package/src/sources/yahoo-finance.ts +49 -1
- package/src/state/app/context/index.test.tsx +5 -8
- package/src/types/broker.ts +1 -0
- package/src/types/config.ts +10 -0
- package/src/types/trading.ts +2 -0
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +2 -0
- package/src/utils/financial-statements.ts +23 -0
- package/src/version.ts +1 -1
- package/src/components/onboarding/portfolio-step/manual-name-panel.tsx +0 -62
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { DataTableView, type DataTableColumn } from "../../../components";
|
|
3
3
|
import { colors } from "../../../theme/colors";
|
|
4
|
-
import { formatNumber
|
|
4
|
+
import { formatNumber } from "../../../utils/format";
|
|
5
5
|
import { formatPredictionProbability } from "../metrics";
|
|
6
6
|
import type {
|
|
7
7
|
PredictionBookLevel,
|
|
@@ -9,111 +9,156 @@ import type {
|
|
|
9
9
|
PredictionOrderPreviewIntent,
|
|
10
10
|
} from "../types";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
type BookColumnId = "outcome" | "side" | "price" | "size";
|
|
13
|
+
type BookColumn = DataTableColumn & { id: BookColumnId };
|
|
14
|
+
|
|
15
|
+
interface BookRow {
|
|
16
|
+
id: string;
|
|
17
|
+
outcome: "yes" | "no";
|
|
18
|
+
side: "buy" | "sell";
|
|
19
|
+
level: PredictionBookLevel;
|
|
20
|
+
intent: PredictionOrderPreviewIntent;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const BOOK_COLUMNS: BookColumn[] = [
|
|
24
|
+
{ id: "outcome", label: "OUT", width: 5 },
|
|
25
|
+
{ id: "side", label: "SIDE", width: 6 },
|
|
26
|
+
{ id: "price", label: "PRICE", width: 8, align: "right" },
|
|
27
|
+
{ id: "size", label: "SIZE", width: 10, align: "right" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function bookRowsForLevels({
|
|
14
31
|
levels,
|
|
15
|
-
|
|
32
|
+
marketKey,
|
|
33
|
+
outcome,
|
|
34
|
+
side,
|
|
16
35
|
}: {
|
|
17
|
-
title: string;
|
|
18
36
|
levels: PredictionBookLevel[];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
marketKey: string;
|
|
38
|
+
outcome: "yes" | "no";
|
|
39
|
+
side: "buy" | "sell";
|
|
40
|
+
}): BookRow[] {
|
|
41
|
+
return levels.slice(0, 10).map((level, index) => ({
|
|
42
|
+
id: `${outcome}:${side}:${index}:${level.price}:${level.size}`,
|
|
43
|
+
outcome,
|
|
44
|
+
side,
|
|
45
|
+
level,
|
|
46
|
+
intent: {
|
|
47
|
+
marketKey,
|
|
48
|
+
outcome,
|
|
49
|
+
side,
|
|
50
|
+
price: level.price,
|
|
51
|
+
size: level.size,
|
|
52
|
+
},
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function buildBookRows(detail: PredictionMarketDetail): BookRow[] {
|
|
57
|
+
const marketKey = detail.summary.key;
|
|
58
|
+
return [
|
|
59
|
+
...bookRowsForLevels({
|
|
60
|
+
levels: detail.book.yesBids,
|
|
61
|
+
marketKey,
|
|
62
|
+
outcome: "yes",
|
|
63
|
+
side: "buy",
|
|
64
|
+
}),
|
|
65
|
+
...bookRowsForLevels({
|
|
66
|
+
levels: detail.book.yesAsks,
|
|
67
|
+
marketKey,
|
|
68
|
+
outcome: "yes",
|
|
69
|
+
side: "sell",
|
|
70
|
+
}),
|
|
71
|
+
...bookRowsForLevels({
|
|
72
|
+
levels: detail.book.noBids,
|
|
73
|
+
marketKey,
|
|
74
|
+
outcome: "no",
|
|
75
|
+
side: "buy",
|
|
76
|
+
}),
|
|
77
|
+
...bookRowsForLevels({
|
|
78
|
+
levels: detail.book.noAsks,
|
|
79
|
+
marketKey,
|
|
80
|
+
outcome: "no",
|
|
81
|
+
side: "sell",
|
|
82
|
+
}),
|
|
83
|
+
];
|
|
50
84
|
}
|
|
51
85
|
|
|
52
86
|
export function PredictionMarketBookView({
|
|
53
87
|
detail,
|
|
88
|
+
focused,
|
|
54
89
|
onPreviewOrder,
|
|
90
|
+
width,
|
|
55
91
|
}: {
|
|
56
92
|
detail: PredictionMarketDetail;
|
|
93
|
+
focused: boolean;
|
|
57
94
|
onPreviewOrder: (intent: PredictionOrderPreviewIntent) => void;
|
|
95
|
+
width: number;
|
|
58
96
|
}) {
|
|
97
|
+
const rows = useMemo(() => buildBookRows(detail), [detail]);
|
|
98
|
+
const [selectedIndex, setSelectedIndex] = useState<number | null>(() =>
|
|
99
|
+
rows.length > 0 ? 0 : null,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
setSelectedIndex((current) => {
|
|
104
|
+
if (rows.length === 0) return null;
|
|
105
|
+
if (current == null || current >= rows.length) return 0;
|
|
106
|
+
return current;
|
|
107
|
+
});
|
|
108
|
+
}, [rows.length]);
|
|
109
|
+
|
|
59
110
|
return (
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
size: level.size,
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
/>
|
|
116
|
-
</Box>
|
|
117
|
-
</Box>
|
|
111
|
+
<DataTableView<BookRow, BookColumn>
|
|
112
|
+
focused={focused}
|
|
113
|
+
keyboardNavigation={focused}
|
|
114
|
+
rootWidth={width}
|
|
115
|
+
rootBackgroundColor={colors.panel}
|
|
116
|
+
selection={{
|
|
117
|
+
kind: "index",
|
|
118
|
+
selectedIndex,
|
|
119
|
+
onChange: (index) => setSelectedIndex(index),
|
|
120
|
+
}}
|
|
121
|
+
columns={BOOK_COLUMNS}
|
|
122
|
+
items={rows}
|
|
123
|
+
sortColumnId={null}
|
|
124
|
+
sortDirection="asc"
|
|
125
|
+
onHeaderClick={() => {}}
|
|
126
|
+
getItemKey={(row) => row.id}
|
|
127
|
+
onActivate={(row) => onPreviewOrder(row.intent)}
|
|
128
|
+
onRowMouseDown={(row, index, event) => {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
setSelectedIndex(index);
|
|
131
|
+
onPreviewOrder(row.intent);
|
|
132
|
+
return true;
|
|
133
|
+
}}
|
|
134
|
+
renderCell={(row, column, _index, rowState) => {
|
|
135
|
+
const color = (fallback: string) =>
|
|
136
|
+
rowState.selected ? undefined : fallback;
|
|
137
|
+
switch (column.id) {
|
|
138
|
+
case "outcome":
|
|
139
|
+
return {
|
|
140
|
+
text: row.outcome.toUpperCase(),
|
|
141
|
+
color: color(colors.textBright),
|
|
142
|
+
};
|
|
143
|
+
case "side":
|
|
144
|
+
return {
|
|
145
|
+
text: row.side === "buy" ? "BID" : "ASK",
|
|
146
|
+
color: color(row.side === "buy" ? colors.positive : colors.negative),
|
|
147
|
+
};
|
|
148
|
+
case "price":
|
|
149
|
+
return {
|
|
150
|
+
text: formatPredictionProbability(row.level.price),
|
|
151
|
+
color: color(colors.text),
|
|
152
|
+
};
|
|
153
|
+
case "size":
|
|
154
|
+
return {
|
|
155
|
+
text: formatNumber(row.level.size, 0),
|
|
156
|
+
color: color(colors.textDim),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}}
|
|
160
|
+
emptyStateTitle="No book levels."
|
|
161
|
+
emptyStateHint="This venue did not return current order book depth."
|
|
162
|
+
/>
|
|
118
163
|
);
|
|
119
164
|
}
|
|
@@ -17,6 +17,7 @@ export function PredictionMarketOverviewView({
|
|
|
17
17
|
detailWidth,
|
|
18
18
|
height,
|
|
19
19
|
historyRange,
|
|
20
|
+
loading,
|
|
20
21
|
onHistoryRangeChange,
|
|
21
22
|
onSelectMarket,
|
|
22
23
|
selectedRow,
|
|
@@ -26,6 +27,7 @@ export function PredictionMarketOverviewView({
|
|
|
26
27
|
detailWidth: number;
|
|
27
28
|
height: number;
|
|
28
29
|
historyRange: PredictionHistoryRange;
|
|
30
|
+
loading: boolean;
|
|
29
31
|
onHistoryRangeChange: (range: PredictionHistoryRange) => void;
|
|
30
32
|
onSelectMarket: (marketKey: string) => void;
|
|
31
33
|
selectedRow: PredictionListRow | null;
|
|
@@ -47,6 +49,7 @@ export function PredictionMarketOverviewView({
|
|
|
47
49
|
history={detail?.history ?? []}
|
|
48
50
|
width={detailWidth}
|
|
49
51
|
height={Math.max(Math.floor(height * 0.36), 10)}
|
|
52
|
+
loading={loading}
|
|
50
53
|
range={historyRange}
|
|
51
54
|
onRangeSelect={onHistoryRangeChange}
|
|
52
55
|
/>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, ScrollBox, Text } from "../../../ui";
|
|
2
2
|
import { TextAttributes, type ScrollBoxRenderable } from "../../../ui";
|
|
3
3
|
import type { RefObject } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { Tabs } from "../../../components";
|
|
5
5
|
import { EmptyState } from "../../../components/ui/status";
|
|
6
6
|
import { colors } from "../../../theme/colors";
|
|
7
7
|
import { padTo } from "../../../utils/format";
|
|
@@ -37,8 +37,11 @@ interface MetricCell {
|
|
|
37
37
|
function MetricLabelRow({ metrics }: { metrics: MetricCell[] }) {
|
|
38
38
|
return (
|
|
39
39
|
<Box flexDirection="row" height={1}>
|
|
40
|
-
{metrics.map((metric) => (
|
|
41
|
-
<Box
|
|
40
|
+
{metrics.map((metric, index) => (
|
|
41
|
+
<Box
|
|
42
|
+
key={metric.label}
|
|
43
|
+
width={metric.width + (index === metrics.length - 1 ? 0 : 1)}
|
|
44
|
+
>
|
|
42
45
|
<Text fg={colors.textDim}>{padTo(metric.label, metric.width)}</Text>
|
|
43
46
|
</Box>
|
|
44
47
|
))}
|
|
@@ -49,8 +52,11 @@ function MetricLabelRow({ metrics }: { metrics: MetricCell[] }) {
|
|
|
49
52
|
function MetricValueRow({ metrics }: { metrics: MetricCell[] }) {
|
|
50
53
|
return (
|
|
51
54
|
<Box flexDirection="row" height={1}>
|
|
52
|
-
{metrics.map((metric) => (
|
|
53
|
-
<Box
|
|
55
|
+
{metrics.map((metric, index) => (
|
|
56
|
+
<Box
|
|
57
|
+
key={metric.label}
|
|
58
|
+
width={metric.width + (index === metrics.length - 1 ? 0 : 1)}
|
|
59
|
+
>
|
|
54
60
|
<Text
|
|
55
61
|
fg={metric.color ?? colors.textBright}
|
|
56
62
|
attributes={TextAttributes.BOLD}
|
|
@@ -108,8 +114,6 @@ export function PredictionMarketDetailPane({
|
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
const summaryMetrics = detail?.summary ?? selectedSummary;
|
|
111
|
-
const detailTitle =
|
|
112
|
-
selectedRow?.kind === "group" ? selectedRow.title : summaryMetrics.title;
|
|
113
117
|
const detailSubtitleParts: string[] = [];
|
|
114
118
|
if (selectedRow?.kind === "group") {
|
|
115
119
|
if (summaryMetrics.category) detailSubtitleParts.push(summaryMetrics.category);
|
|
@@ -126,18 +130,18 @@ export function PredictionMarketDetailPane({
|
|
|
126
130
|
);
|
|
127
131
|
if (endsLabel !== "—") detailSubtitleParts.push(endsLabel);
|
|
128
132
|
const detailSubtitle = detailSubtitleParts.join(" · ");
|
|
129
|
-
const
|
|
133
|
+
const metrics: MetricCell[] = [
|
|
130
134
|
{
|
|
131
135
|
label: "YES",
|
|
132
136
|
value: formatPredictionProbability(summaryMetrics.yesPrice),
|
|
133
137
|
color: getPredictionProbabilityColor(summaryMetrics.yesPrice),
|
|
134
|
-
width:
|
|
138
|
+
width: 6,
|
|
135
139
|
},
|
|
136
140
|
{
|
|
137
141
|
label: "NO",
|
|
138
142
|
value: formatPredictionProbability(summaryMetrics.noPrice),
|
|
139
143
|
color: getPredictionProbabilityColor(summaryMetrics.noPrice),
|
|
140
|
-
width:
|
|
144
|
+
width: 6,
|
|
141
145
|
},
|
|
142
146
|
{
|
|
143
147
|
label: "24H VOL",
|
|
@@ -145,7 +149,7 @@ export function PredictionMarketDetailPane({
|
|
|
145
149
|
summaryMetrics.volume24h,
|
|
146
150
|
summaryMetrics.volume24hUnit ?? "usd",
|
|
147
151
|
),
|
|
148
|
-
width:
|
|
152
|
+
width: 8,
|
|
149
153
|
},
|
|
150
154
|
{
|
|
151
155
|
label: "TOTAL VOL",
|
|
@@ -153,28 +157,26 @@ export function PredictionMarketDetailPane({
|
|
|
153
157
|
summaryMetrics.totalVolume,
|
|
154
158
|
summaryMetrics.totalVolumeUnit ?? "usd",
|
|
155
159
|
),
|
|
156
|
-
width:
|
|
160
|
+
width: 9,
|
|
157
161
|
},
|
|
158
|
-
];
|
|
159
|
-
const secondaryMetrics: MetricCell[] = [
|
|
160
162
|
{
|
|
161
163
|
label: "OI",
|
|
162
164
|
value: formatPredictionMetric(
|
|
163
165
|
summaryMetrics.openInterest,
|
|
164
166
|
summaryMetrics.openInterestUnit ?? "usd",
|
|
165
167
|
),
|
|
166
|
-
width:
|
|
168
|
+
width: 7,
|
|
167
169
|
},
|
|
168
170
|
{
|
|
169
171
|
label: "SPREAD",
|
|
170
172
|
value: formatPredictionSpread(summaryMetrics.spread),
|
|
171
|
-
width:
|
|
173
|
+
width: 7,
|
|
172
174
|
},
|
|
173
175
|
{
|
|
174
176
|
label: "LAST",
|
|
175
177
|
value: formatPredictionProbability(summaryMetrics.lastTradePrice),
|
|
176
178
|
color: colors.textBright,
|
|
177
|
-
width:
|
|
179
|
+
width: 7,
|
|
178
180
|
},
|
|
179
181
|
];
|
|
180
182
|
const relatedSiblings =
|
|
@@ -186,33 +188,25 @@ export function PredictionMarketDetailPane({
|
|
|
186
188
|
0,
|
|
187
189
|
Math.max(Math.min(Math.floor((detailWidth - 10) / 18), 3), 0),
|
|
188
190
|
) ?? []);
|
|
189
|
-
const headerHeight = detailSubtitle.length > 0 ?
|
|
191
|
+
const headerHeight = detailSubtitle.length > 0 ? 2 : 0;
|
|
190
192
|
const detailLoading = detailLoadCount > 0 && !detail;
|
|
191
|
-
const titleColor = focused ? colors.textBright : colors.text;
|
|
192
193
|
const detailTextWidth = Math.max(detailWidth, 12);
|
|
193
194
|
|
|
194
195
|
return (
|
|
195
196
|
<>
|
|
196
|
-
|
|
197
|
-
<Box flexDirection="
|
|
198
|
-
<Text fg={titleColor} attributes={TextAttributes.BOLD}>
|
|
199
|
-
{truncatePredictionText(detailTitle, detailTextWidth)}
|
|
200
|
-
</Text>
|
|
201
|
-
</Box>
|
|
202
|
-
{detailSubtitle.length > 0 && (
|
|
197
|
+
{headerHeight > 0 && (
|
|
198
|
+
<Box flexDirection="column" height={headerHeight} paddingBottom={1}>
|
|
203
199
|
<Box flexDirection="row" height={1}>
|
|
204
200
|
<Text fg={colors.textDim}>
|
|
205
201
|
{truncatePredictionText(detailSubtitle, detailTextWidth)}
|
|
206
202
|
</Text>
|
|
207
203
|
</Box>
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
</Box>
|
|
205
|
+
)}
|
|
210
206
|
|
|
211
|
-
<Box flexDirection="column" height={
|
|
212
|
-
<MetricLabelRow metrics={
|
|
213
|
-
<MetricValueRow metrics={
|
|
214
|
-
<MetricLabelRow metrics={secondaryMetrics} />
|
|
215
|
-
<MetricValueRow metrics={secondaryMetrics} />
|
|
207
|
+
<Box flexDirection="column" height={3} paddingBottom={1}>
|
|
208
|
+
<MetricLabelRow metrics={metrics} />
|
|
209
|
+
<MetricValueRow metrics={metrics} />
|
|
216
210
|
</Box>
|
|
217
211
|
|
|
218
212
|
{relatedSiblings.length > 0 && (
|
|
@@ -260,43 +254,56 @@ export function PredictionMarketDetailPane({
|
|
|
260
254
|
</Box>
|
|
261
255
|
)}
|
|
262
256
|
|
|
263
|
-
|
|
264
|
-
{
|
|
265
|
-
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
summary={summaryMetrics}
|
|
279
|
-
/>
|
|
280
|
-
)}
|
|
257
|
+
{detailTab === "overview" || detailTab === "rules" ? (
|
|
258
|
+
<ScrollBox ref={scrollRef} flexGrow={1} scrollY>
|
|
259
|
+
{detailTab === "overview" && (
|
|
260
|
+
<PredictionMarketOverviewView
|
|
261
|
+
detail={detail}
|
|
262
|
+
detailWidth={detailWidth}
|
|
263
|
+
height={height}
|
|
264
|
+
historyRange={historyRange}
|
|
265
|
+
loading={detailLoading}
|
|
266
|
+
onHistoryRangeChange={onHistoryRangeChange}
|
|
267
|
+
onSelectMarket={onSelectMarket}
|
|
268
|
+
selectedRow={selectedRow}
|
|
269
|
+
summary={summaryMetrics}
|
|
270
|
+
/>
|
|
271
|
+
)}
|
|
281
272
|
|
|
282
|
-
|
|
273
|
+
{detailTab === "rules" && (
|
|
274
|
+
<PredictionMarketRulesView
|
|
275
|
+
detailWidth={detailTextWidth}
|
|
276
|
+
rules={detail?.rules ?? []}
|
|
277
|
+
/>
|
|
278
|
+
)}
|
|
279
|
+
</ScrollBox>
|
|
280
|
+
) : null}
|
|
281
|
+
|
|
282
|
+
{detailTab === "book" && (
|
|
283
|
+
detail ? (
|
|
283
284
|
<PredictionMarketBookView
|
|
284
285
|
detail={detail}
|
|
286
|
+
focused={focused}
|
|
285
287
|
onPreviewOrder={onPreviewOrder}
|
|
288
|
+
width={detailWidth}
|
|
286
289
|
/>
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
) : (
|
|
291
|
+
<Box flexGrow={1} justifyContent="center">
|
|
292
|
+
<EmptyState
|
|
293
|
+
title={detailLoading ? "Loading order book." : "No order book."}
|
|
294
|
+
hint="This venue did not return current order book depth."
|
|
295
|
+
/>
|
|
296
|
+
</Box>
|
|
297
|
+
)
|
|
298
|
+
)}
|
|
292
299
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
+
{detailTab === "trades" && (
|
|
301
|
+
<PredictionMarketTradesView
|
|
302
|
+
focused={focused}
|
|
303
|
+
trades={detail?.trades ?? []}
|
|
304
|
+
width={detailWidth}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
300
307
|
</>
|
|
301
308
|
);
|
|
302
309
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Box } from "../../../ui";
|
|
2
2
|
import { colors } from "../../../theme/colors";
|
|
3
3
|
import { ExternalLinkText } from "../../../components/ui/external-link";
|
|
4
|
+
import type {
|
|
5
|
+
PredictionListRow,
|
|
6
|
+
PredictionMarketDetail,
|
|
7
|
+
PredictionMarketSummary,
|
|
8
|
+
} from "../types";
|
|
4
9
|
|
|
5
10
|
export function truncatePredictionText(
|
|
6
11
|
value: string,
|
|
@@ -11,6 +16,20 @@ export function truncatePredictionText(
|
|
|
11
16
|
return `${value.slice(0, maxLength - 3)}...`;
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
export function resolvePredictionDetailTitle({
|
|
20
|
+
detail,
|
|
21
|
+
selectedRow,
|
|
22
|
+
selectedSummary,
|
|
23
|
+
}: {
|
|
24
|
+
detail: PredictionMarketDetail | null;
|
|
25
|
+
selectedRow: PredictionListRow | null;
|
|
26
|
+
selectedSummary: PredictionMarketSummary | null;
|
|
27
|
+
}): string | undefined {
|
|
28
|
+
if (!selectedSummary) return undefined;
|
|
29
|
+
const summary = detail?.summary ?? selectedSummary;
|
|
30
|
+
return selectedRow?.kind === "group" ? selectedRow.title : summary.title;
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export function SummaryLink({
|
|
15
34
|
url,
|
|
16
35
|
maxLength,
|
|
@@ -1,31 +1,76 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DataTableView, type DataTableColumn } from "../../../components";
|
|
2
2
|
import { colors } from "../../../theme/colors";
|
|
3
|
-
import { formatNumber
|
|
3
|
+
import { formatNumber } from "../../../utils/format";
|
|
4
4
|
import { formatPredictionProbability } from "../metrics";
|
|
5
5
|
import type { PredictionTrade } from "../types";
|
|
6
6
|
|
|
7
|
+
type TradeColumnId = "time" | "side" | "outcome" | "price" | "size";
|
|
8
|
+
type TradeColumn = DataTableColumn & { id: TradeColumnId };
|
|
9
|
+
|
|
10
|
+
const TRADE_COLUMNS: TradeColumn[] = [
|
|
11
|
+
{ id: "time", label: "TIME", width: 16 },
|
|
12
|
+
{ id: "side", label: "SIDE", width: 6 },
|
|
13
|
+
{ id: "outcome", label: "OUT", width: 4 },
|
|
14
|
+
{ id: "price", label: "PRICE", width: 8, align: "right" },
|
|
15
|
+
{ id: "size", label: "SIZE", width: 10, align: "right" },
|
|
16
|
+
];
|
|
17
|
+
|
|
7
18
|
export function PredictionMarketTradesView({
|
|
19
|
+
focused,
|
|
8
20
|
trades,
|
|
21
|
+
width,
|
|
9
22
|
}: {
|
|
23
|
+
focused: boolean;
|
|
10
24
|
trades: PredictionTrade[];
|
|
25
|
+
width: number;
|
|
11
26
|
}) {
|
|
12
27
|
return (
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
<DataTableView<PredictionTrade, TradeColumn>
|
|
29
|
+
focused={focused}
|
|
30
|
+
keyboardNavigation={false}
|
|
31
|
+
rootWidth={width}
|
|
32
|
+
rootBackgroundColor={colors.panel}
|
|
33
|
+
selection={{ kind: "none" }}
|
|
34
|
+
columns={TRADE_COLUMNS}
|
|
35
|
+
items={trades.slice(0, 30)}
|
|
36
|
+
sortColumnId={null}
|
|
37
|
+
sortDirection="asc"
|
|
38
|
+
onHeaderClick={() => {}}
|
|
39
|
+
getItemKey={(trade) => trade.id}
|
|
40
|
+
renderCell={(trade, column) => {
|
|
41
|
+
const tradeColor = trade.side === "buy" ? colors.positive : colors.negative;
|
|
42
|
+
switch (column.id) {
|
|
43
|
+
case "time":
|
|
44
|
+
return {
|
|
45
|
+
text: new Date(trade.timestamp).toLocaleTimeString("en-US", {
|
|
46
|
+
hour12: false,
|
|
47
|
+
}),
|
|
48
|
+
color: colors.textDim,
|
|
49
|
+
};
|
|
50
|
+
case "side":
|
|
51
|
+
return {
|
|
52
|
+
text: trade.side.toUpperCase(),
|
|
53
|
+
color: tradeColor,
|
|
54
|
+
};
|
|
55
|
+
case "outcome":
|
|
56
|
+
return {
|
|
57
|
+
text: trade.outcome.toUpperCase(),
|
|
58
|
+
color: colors.text,
|
|
59
|
+
};
|
|
60
|
+
case "price":
|
|
61
|
+
return {
|
|
62
|
+
text: formatPredictionProbability(trade.price),
|
|
63
|
+
color: tradeColor,
|
|
64
|
+
};
|
|
65
|
+
case "size":
|
|
66
|
+
return {
|
|
67
|
+
text: formatNumber(trade.size, 0),
|
|
68
|
+
color: colors.textDim,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}}
|
|
72
|
+
emptyStateTitle="No recent trades."
|
|
73
|
+
emptyStateHint="This venue did not return recent prints."
|
|
74
|
+
/>
|
|
30
75
|
);
|
|
31
76
|
}
|