gloomberb 0.11.2 → 0.11.3

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.
Files changed (68) hide show
  1. package/README.md +1 -0
  2. package/package.json +1 -1
  3. package/src/api-client/data.ts +5 -0
  4. package/src/api-client/index.ts +5 -0
  5. package/src/api-client/types.ts +24 -0
  6. package/src/capabilities/chart-series.ts +3 -2
  7. package/src/cli/commands/market.ts +2 -2
  8. package/src/cli/pane-functions/capabilities.ts +3 -3
  9. package/src/cli/registry.ts +0 -22
  10. package/src/cli/result.ts +7 -10
  11. package/src/components/data-table/export.test.ts +21 -0
  12. package/src/components/data-table/export.ts +21 -0
  13. package/src/components/ui/data-table/index.tsx +19 -2
  14. package/src/data/config/chart-settings.ts +4 -4
  15. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  16. package/src/layout-marketplace/gallery.test.tsx +65 -1
  17. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  18. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  19. package/src/plugins/builtin/composite-plugins.ts +2 -0
  20. package/src/plugins/builtin/earnings/index.tsx +1 -0
  21. package/src/plugins/builtin/econ/index.tsx +1 -0
  22. package/src/plugins/builtin/futures/index.tsx +1 -0
  23. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  24. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  25. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  26. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  27. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  28. package/src/plugins/builtin/options/analytics.ts +110 -0
  29. package/src/plugins/builtin/options/index.tsx +17 -2
  30. package/src/plugins/builtin/options/table.ts +121 -51
  31. package/src/plugins/builtin/options/types.ts +22 -15
  32. package/src/plugins/builtin/options/view.test.tsx +24 -0
  33. package/src/plugins/builtin/options/view.tsx +91 -19
  34. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  35. package/src/plugins/builtin/plugin-module.ts +0 -7
  36. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  37. package/src/plugins/builtin/scanner/index.ts +2 -0
  38. package/src/plugins/builtin/sectors/index.tsx +1 -0
  39. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  40. package/src/plugins/builtin/ticker-detail/index.tsx +2 -0
  41. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  42. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  43. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  44. package/src/plugins/builtin/world-venue-map/map.tsx +337 -0
  45. package/src/plugins/builtin/world-venue-map/model.test.ts +53 -0
  46. package/src/plugins/builtin/world-venue-map/model.ts +147 -0
  47. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  48. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  49. package/src/plugins/catalog-backend.ts +6 -44
  50. package/src/plugins/catalog-browser.ts +2 -0
  51. package/src/plugins/registry/pane-settings.test.ts +53 -0
  52. package/src/plugins/registry/pane-settings.ts +36 -3
  53. package/src/renderers/browser/ui-host.tsx +11 -0
  54. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  55. package/src/renderers/electrobun/bun/index.ts +1 -0
  56. package/src/renderers/electrobun/shared/protocol.ts +4 -0
  57. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  58. package/src/renderers/opentui/host.tsx +4 -0
  59. package/src/state/pane-table-export-registry.ts +31 -0
  60. package/src/time-series/range.ts +12 -0
  61. package/src/time-series/resolution.ts +3 -15
  62. package/src/time-series/spec.ts +5 -5
  63. package/src/types/plugin.ts +2 -20
  64. package/src/ui/host.tsx +7 -0
  65. package/src/utils/csv.test.ts +13 -0
  66. package/src/utils/csv.ts +37 -0
  67. package/src/utils/save-text-file.test.ts +24 -0
  68. package/src/utils/save-text-file.ts +36 -0
@@ -4,6 +4,8 @@ import { testRender } from "../renderers/opentui/test-utils";
4
4
  import { AppContext, createInitialState, type AppAction } from "../state/app/context";
5
5
  import { cloneLayout, createDefaultConfig } from "../types/config";
6
6
  import type { PaneDef } from "../types/plugin";
7
+ import { Box } from "../ui";
8
+ import { PaneFooterBar, PaneFooterProvider } from "../components/layout/pane/footer";
7
9
  import type { PluginRegistry } from "../plugins/registry";
8
10
  import { LayoutMarketplaceGallery } from "./gallery";
9
11
 
@@ -43,7 +45,21 @@ async function renderGallery() {
43
45
 
44
46
  testSetup = await testRender(
45
47
  <AppContext value={{ state, dispatch: (action) => actions.push(action) }}>
46
- <LayoutMarketplaceGallery pluginRegistry={registry} onClose={() => { closed = true; }} />
48
+ <PaneFooterProvider>
49
+ {(footer) => (
50
+ <Box width={100} height={24} flexDirection="column">
51
+ <Box width={100} height={23}>
52
+ <LayoutMarketplaceGallery
53
+ pluginRegistry={registry}
54
+ width={100}
55
+ height={23}
56
+ onClose={() => { closed = true; }}
57
+ />
58
+ </Box>
59
+ <PaneFooterBar footer={footer} focused width={100} />
60
+ </Box>
61
+ )}
62
+ </PaneFooterProvider>
47
63
  </AppContext>,
48
64
  { width: 100, height: 24 },
49
65
  );
@@ -65,6 +81,54 @@ test("lists owned layouts before Discover and details the selected layout", asyn
65
81
  expect(frame).toContain("Portfolio");
66
82
  expect(frame).toContain("Ticker Research");
67
83
  expect(frame).toContain("3 docked");
84
+
85
+ const searchLine = frame.split("\n").find((line) => line.includes("Search layouts and panes"));
86
+ expect(searchLine?.startsWith("/ Search layouts and panes")).toBe(true);
87
+ expect(frame).toContain("[/]search");
88
+ expect(frame).toContain("[n]ew");
89
+ expect(frame).toContain("[o]pen");
90
+ expect(frame).toContain("[r]ename");
91
+ expect(frame).toContain("[c]opy");
92
+ expect(frame).toContain("[d]elete");
93
+ expect(frame).toContain("[p]ublish");
94
+ });
95
+
96
+ test("search Enter returns to the list before activating the filtered layout", async () => {
97
+ const { actions, isClosed } = await renderGallery();
98
+
99
+ await act(async () => {
100
+ testSetup!.mockInput.pressKey("/");
101
+ await testSetup!.renderOnce();
102
+ });
103
+ await act(async () => {
104
+ await testSetup!.mockInput.typeText("Research");
105
+ testSetup!.mockInput.pressEnter();
106
+ await testSetup!.renderOnce();
107
+ });
108
+
109
+ expect(isClosed()).toBe(false);
110
+ expect(actions.some((action) => action.type === "SWITCH_LAYOUT")).toBe(false);
111
+ expect(testSetup!.captureCharFrame()).toContain("/ Research");
112
+
113
+ await act(async () => {
114
+ testSetup!.mockInput.pressEnter();
115
+ await testSetup!.renderOnce();
116
+ });
117
+
118
+ expect(actions).toContainEqual({ type: "SWITCH_LAYOUT", index: 1 });
119
+ expect(isClosed()).toBe(true);
120
+ });
121
+
122
+ test("n opens the new layout workflow", async () => {
123
+ const { isClosed } = await renderGallery();
124
+
125
+ await act(async () => {
126
+ testSetup!.mockInput.pressKey("n");
127
+ await testSetup!.renderOnce();
128
+ });
129
+
130
+ expect(isClosed()).toBe(false);
131
+ expect(testSetup!.captureCharFrame()).toContain("Create Layout");
68
132
  });
69
133
 
70
134
  test("j/k move the selection and Enter switches to the layout and closes", async () => {
@@ -1,4 +1,10 @@
1
1
  import type { NormalizedPaneFunctionOptions } from "../../../cli/pane-functions/capabilities";
2
+ import {
3
+ CHART_RESOLUTIONS,
4
+ TIME_RANGES,
5
+ type ChartResolution,
6
+ type TimeRange,
7
+ } from "../../../time-series/range";
2
8
  import {
3
9
  coerceSeriesTransformForStyle,
4
10
  normalizeChartSpec,
@@ -7,20 +13,17 @@ import type { ChartSeriesSpec, ChartSpec, SeriesPeriod } from "../../../time-ser
7
13
 
8
14
  const PRICE_CAPABILITIES = new Set(["price-chart", "intraday-price-chart", "price-comparison"]);
9
15
 
10
- function chartRange(options: NormalizedPaneFunctionOptions): ChartSpec["viewport"]["range"] | null {
16
+ function chartRange(options: NormalizedPaneFunctionOptions): TimeRange | null {
11
17
  const value = options.rangePreset ?? options.range;
12
- return value === "1D" || value === "1W" || value === "1M" || value === "3M"
13
- || value === "6M" || value === "1Y" || value === "5Y" || value === "ALL"
14
- ? value
18
+ return typeof value === "string" && (TIME_RANGES as readonly string[]).includes(value)
19
+ ? value as TimeRange
15
20
  : null;
16
21
  }
17
22
 
18
- function chartResolution(options: NormalizedPaneFunctionOptions): ChartSpec["viewport"]["resolution"] | null {
23
+ function chartResolution(options: NormalizedPaneFunctionOptions): ChartResolution | null {
19
24
  const value = options.chartResolution ?? options.resolution;
20
- return value === "auto" || value === "1m" || value === "5m" || value === "15m"
21
- || value === "30m" || value === "45m" || value === "1h" || value === "1d"
22
- || value === "1wk" || value === "1mo"
23
- ? value
25
+ return typeof value === "string" && (CHART_RESOLUTIONS as readonly string[]).includes(value)
26
+ ? value as ChartResolution
24
27
  : null;
25
28
  }
26
29
 
@@ -1,4 +1,5 @@
1
1
  import type { ChartResolution, TimeRange } from "../../../components/chart/core/types";
2
+ import { CHART_RESOLUTIONS, TIME_RANGES as CHART_RANGES } from "../../../time-series/range";
2
3
  import type {
3
4
  PaneSettingField,
4
5
  PaneSettingOption,
@@ -33,19 +34,7 @@ import {
33
34
  parseChartSpecOr,
34
35
  } from "./chart-spec";
35
36
 
36
- export const CHART_RANGES: TimeRange[] = ["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"];
37
- export const CHART_RESOLUTIONS: ChartResolution[] = [
38
- "auto",
39
- "1m",
40
- "5m",
41
- "15m",
42
- "30m",
43
- "45m",
44
- "1h",
45
- "1d",
46
- "1wk",
47
- "1mo",
48
- ];
37
+ export { CHART_RANGES, CHART_RESOLUTIONS };
49
38
 
50
39
  export const CHART_STUDY_OPTIONS: Array<PaneSettingOption & { value: BuiltinStudySelection }> = [
51
40
  { value: "volume", label: "Volume", description: "Volume columns in a lower panel." },
@@ -29,6 +29,7 @@ import { scannerModule } from "./scanner";
29
29
  import { sectorsModule } from "./sectors";
30
30
  import { treasuryAuctionsModule } from "./treasury-auctions";
31
31
  import { worldIndicesModule } from "./world-indices";
32
+ import { worldVenueMapModule } from "./world-venue-map";
32
33
  import { yieldCurveModule } from "./yield-curve";
33
34
 
34
35
  const macroSharedResourcesModule = {
@@ -75,6 +76,7 @@ export const marketOverviewPlugin = composeBuiltinPlugin({
75
76
  modules: [
76
77
  correlationModule,
77
78
  worldIndicesModule,
79
+ worldVenueMapModule,
78
80
  marketHeatmapModule,
79
81
  marketMoversModule,
80
82
  marketHaltsModule,
@@ -253,6 +253,7 @@ export const earningsModule: PluginModule = {
253
253
  defaultPosition: "right",
254
254
  defaultMode: "floating",
255
255
  defaultFloatingSize: { width: 85, height: 25 },
256
+ tableExport: true,
256
257
  settings: earningsSettings,
257
258
  },
258
259
  ],
@@ -399,6 +399,7 @@ export const economicCalendarModule: PluginModule = {
399
399
  defaultPosition: "right",
400
400
  defaultMode: "floating",
401
401
  defaultFloatingSize: { width: 100, height: 30 },
402
+ tableExport: true,
402
403
  }],
403
404
  paneTemplates: [{
404
405
  id: "econ-calendar-pane",
@@ -250,6 +250,7 @@ export const futuresModule: PluginModule = {
250
250
  defaultPosition: "right",
251
251
  defaultMode: "floating",
252
252
  defaultFloatingSize: { width: 76, height: 34 },
253
+ tableExport: true,
253
254
  // Resolved per open so the dialog shows the full default set until the
254
255
  // user saves a narrower selection.
255
256
  settings: (context) => ({
@@ -169,6 +169,7 @@ export const fxMatrixModule: PluginModule = {
169
169
  defaultPosition: "right",
170
170
  defaultMode: "floating",
171
171
  defaultFloatingSize: { width: 105, height: 14 },
172
+ tableExport: true,
172
173
  settings: (context) => ({
173
174
  title: "FX Cross Rates Settings",
174
175
  values: {
@@ -40,6 +40,7 @@ export const ipoCalendarModule: PluginModule = {
40
40
  defaultPosition: "right",
41
41
  defaultMode: "floating",
42
42
  defaultFloatingSize: { width: 110, height: 28 },
43
+ tableExport: true,
43
44
  },
44
45
  ],
45
46
 
@@ -12,6 +12,7 @@ export const marketHaltsModule: PluginModule = {
12
12
  defaultPosition: "right",
13
13
  defaultMode: "floating",
14
14
  defaultFloatingSize: { width: 124, height: 26 },
15
+ tableExport: true,
15
16
  },
16
17
  ],
17
18
 
@@ -324,6 +324,7 @@ export const marketMoversModule: PluginModule = {
324
324
  defaultPosition: "right",
325
325
  defaultMode: "floating",
326
326
  defaultFloatingSize: { width: 100, height: 36 },
327
+ tableExport: true,
327
328
  quickSettings: [LIVE_STREAMING_QUICK_SETTING],
328
329
  settings: (context) => withLiveStreamingSetting({
329
330
  title: "Market Movers Settings",
@@ -0,0 +1,83 @@
1
+ import { expect, test } from "bun:test";
2
+ import type { OptionContract, OptionsChain, PricePoint } from "../../../types/financials";
3
+ import {
4
+ calculateOptionGreeks,
5
+ calculateOptionsSummary,
6
+ historicalVolatility30d,
7
+ } from "./analytics";
8
+
9
+ function contract(
10
+ strike: number,
11
+ impliedVolatility: number,
12
+ volume: number,
13
+ openInterest: number,
14
+ ): OptionContract {
15
+ return {
16
+ contractSymbol: String(strike),
17
+ strike,
18
+ currency: "USD",
19
+ lastPrice: 1,
20
+ change: 0,
21
+ percentChange: 0,
22
+ volume,
23
+ openInterest,
24
+ bid: 0.95,
25
+ ask: 1.05,
26
+ impliedVolatility,
27
+ inTheMoney: false,
28
+ expiration: Date.UTC(2027, 0, 15) / 1000,
29
+ lastTradeDate: 0,
30
+ };
31
+ }
32
+
33
+ function priceHistory(): PricePoint[] {
34
+ const points: PricePoint[] = [{ date: new Date(Date.UTC(2026, 0, 1)), close: 100 }];
35
+ for (let index = 0; index < 30; index += 1) {
36
+ points.push({
37
+ date: new Date(Date.UTC(2026, 0, index + 2)),
38
+ close: points.at(-1)!.close * Math.exp(index % 2 === 0 ? 0.01 : -0.01),
39
+ });
40
+ }
41
+ return points;
42
+ }
43
+
44
+ test("annualizes the latest 30 daily log returns, including persisted dates", () => {
45
+ const expected = Math.sqrt((30 * 0.01 ** 2 / 29) * 252);
46
+ expect(historicalVolatility30d(priceHistory())).toBeCloseTo(expected, 10);
47
+ expect(historicalVolatility30d(priceHistory().map((point) => ({
48
+ ...point,
49
+ date: point.date.toISOString() as unknown as Date,
50
+ })))).toBeCloseTo(expected, 10);
51
+ expect(historicalVolatility30d(priceHistory().slice(1))).toBeNull();
52
+ });
53
+
54
+ test("summarizes the selected expiration without presenting it as whole-chain volume", () => {
55
+ const chain: OptionsChain = {
56
+ underlyingSymbol: "AAPL",
57
+ expirationDates: [Date.UTC(2027, 0, 15) / 1000],
58
+ calls: [contract(100, 0.2, 100, 200), contract(105, 0.3, 200, 400)],
59
+ puts: [contract(100, 0.22, 150, 180), contract(105, 0.4, 300, 420)],
60
+ };
61
+ const summary = calculateOptionsSummary(chain, 101, priceHistory());
62
+
63
+ expect(summary.atmImpliedVolatility).toBeCloseTo(0.21, 10);
64
+ expect(summary.expirationVolume).toBe(750);
65
+ expect(summary.putCallVolumeRatio).toBe(1.5);
66
+ expect(summary.putCallOpenInterestRatio).toBe(1);
67
+ expect(summary.impliedHistoricalRatio).toBeCloseTo(
68
+ summary.atmImpliedVolatility! / summary.historicalVolatility30d!,
69
+ 10,
70
+ );
71
+ });
72
+
73
+ test("derives call and put Greeks from the chain IV", () => {
74
+ const option = contract(100, 0.25, 0, 0);
75
+ const now = Date.UTC(2026, 11, 15);
76
+ const call = calculateOptionGreeks(option, "call", 100, 0, now);
77
+ const put = calculateOptionGreeks(option, "put", 100, 0, now);
78
+
79
+ expect(call?.delta).toBeGreaterThan(0);
80
+ expect(put?.delta).toBeLessThan(0);
81
+ expect(call?.gamma).toBeCloseTo(put!.gamma, 10);
82
+ expect(call?.vegaPerPoint).toBeCloseTo(put!.vegaPerPoint, 10);
83
+ });
@@ -0,0 +1,110 @@
1
+ import type { OptionContract, OptionsChain, PricePoint } from "../../../types/financials";
2
+ import {
3
+ DEFAULT_OPTION_CALC_DRAFT,
4
+ daysToExpiryFrom,
5
+ valueOption,
6
+ type OptionSide,
7
+ type OptionValuation,
8
+ } from "../options-calculator/model";
9
+
10
+ const TRADING_DAYS_PER_YEAR = 252;
11
+ const HISTORICAL_VOLATILITY_SESSIONS = 30;
12
+
13
+ export interface OptionsSummary {
14
+ atmImpliedVolatility: number | null;
15
+ historicalVolatility30d: number | null;
16
+ impliedHistoricalRatio: number | null;
17
+ expirationVolume: number;
18
+ putCallVolumeRatio: number | null;
19
+ putCallOpenInterestRatio: number | null;
20
+ }
21
+
22
+ function positive(value: number | undefined): value is number {
23
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
24
+ }
25
+
26
+ function ratio(numerator: number, denominator: number): number | null {
27
+ return denominator > 0 ? numerator / denominator : null;
28
+ }
29
+
30
+ function sum(contracts: readonly OptionContract[], field: "volume" | "openInterest"): number {
31
+ return contracts.reduce((total, contract) => {
32
+ const value = contract[field];
33
+ return total + (Number.isFinite(value) && value > 0 ? value : 0);
34
+ }, 0);
35
+ }
36
+
37
+ function atmImpliedVolatility(chain: OptionsChain, spot: number | undefined): number | null {
38
+ if (!positive(spot)) return null;
39
+ const contracts = [...chain.calls, ...chain.puts].filter((contract) => positive(contract.impliedVolatility));
40
+ if (contracts.length === 0) return null;
41
+ const distance = Math.min(...contracts.map((contract) => Math.abs(contract.strike - spot)));
42
+ const atTheMoney = contracts
43
+ .filter((contract) => Math.abs(Math.abs(contract.strike - spot) - distance) < 1e-8)
44
+ .map((contract) => contract.impliedVolatility);
45
+ return atTheMoney.reduce((total, value) => total + value, 0) / atTheMoney.length;
46
+ }
47
+
48
+ /** Annualized standard deviation of the latest 30 daily log returns. */
49
+ export function historicalVolatility30d(points: readonly PricePoint[]): number | null {
50
+ const closes = points
51
+ .map((point) => ({
52
+ close: point.close,
53
+ time: point.date instanceof Date ? point.date.getTime() : Date.parse(String(point.date)),
54
+ }))
55
+ .filter((point) => positive(point.close) && Number.isFinite(point.time))
56
+ .sort((a, b) => a.time - b.time)
57
+ .slice(-(HISTORICAL_VOLATILITY_SESSIONS + 1))
58
+ .map((point) => point.close);
59
+ if (closes.length < HISTORICAL_VOLATILITY_SESSIONS + 1) return null;
60
+
61
+ const returns = closes.slice(1).map((close, index) => Math.log(close / closes[index]!));
62
+ const mean = returns.reduce((total, value) => total + value, 0) / returns.length;
63
+ const variance = returns.reduce((total, value) => total + (value - mean) ** 2, 0) / (returns.length - 1);
64
+ return Math.sqrt(variance * TRADING_DAYS_PER_YEAR);
65
+ }
66
+
67
+ export function calculateOptionsSummary(
68
+ chain: OptionsChain,
69
+ spot: number | undefined,
70
+ priceHistory: readonly PricePoint[],
71
+ ): OptionsSummary {
72
+ const atmIv = atmImpliedVolatility(chain, spot);
73
+ const historicalVolatility = historicalVolatility30d(priceHistory);
74
+ const callVolume = sum(chain.calls, "volume");
75
+ const putVolume = sum(chain.puts, "volume");
76
+ const callOpenInterest = sum(chain.calls, "openInterest");
77
+ const putOpenInterest = sum(chain.puts, "openInterest");
78
+
79
+ return {
80
+ atmImpliedVolatility: atmIv,
81
+ historicalVolatility30d: historicalVolatility,
82
+ impliedHistoricalRatio: atmIv != null && historicalVolatility != null && historicalVolatility > 0
83
+ ? atmIv / historicalVolatility
84
+ : null,
85
+ expirationVolume: callVolume + putVolume,
86
+ putCallVolumeRatio: ratio(putVolume, callVolume),
87
+ putCallOpenInterestRatio: ratio(putOpenInterest, callOpenInterest),
88
+ };
89
+ }
90
+
91
+ export function calculateOptionGreeks(
92
+ contract: OptionContract | undefined,
93
+ side: OptionSide,
94
+ spot: number | undefined,
95
+ dividendYield: number | undefined,
96
+ now: number = Date.now(),
97
+ ): OptionValuation | undefined {
98
+ if (!contract || !positive(spot) || !positive(contract.strike) || !positive(contract.impliedVolatility)) {
99
+ return undefined;
100
+ }
101
+ return valueOption({
102
+ ...DEFAULT_OPTION_CALC_DRAFT,
103
+ side,
104
+ spot,
105
+ strike: contract.strike,
106
+ daysToExpiry: daysToExpiryFrom(contract.expiration, now),
107
+ volatility: contract.impliedVolatility,
108
+ dividendYield: Number.isFinite(dividendYield) ? dividendYield! : 0,
109
+ });
110
+ }
@@ -6,11 +6,26 @@ import {
6
6
  LIVE_STREAMING_QUICK_SETTING,
7
7
  withLiveStreamingSetting,
8
8
  } from "../shared/live-streaming";
9
+ import { OPTION_FIELD_DEFS, resolveOptionFieldIds } from "./table";
9
10
 
10
- function optionsSettings(): PaneSettingsDef {
11
+ function optionsSettings(settings: Record<string, unknown>): PaneSettingsDef {
11
12
  return {
12
13
  title: "Options Settings",
14
+ values: {
15
+ optionColumnIds: resolveOptionFieldIds(settings.optionColumnIds),
16
+ },
13
17
  fields: [
18
+ {
19
+ key: "optionColumnIds",
20
+ label: "Columns",
21
+ description: "Choose and order the fields mirrored around the strike.",
22
+ type: "ordered-multi-select",
23
+ options: OPTION_FIELD_DEFS.map((field) => ({
24
+ value: field.id,
25
+ label: field.label,
26
+ description: field.description,
27
+ })),
28
+ },
14
29
  {
15
30
  key: "chainRefreshMinutes",
16
31
  label: "Chain refresh",
@@ -38,7 +53,7 @@ export const optionsModule: PluginModule = {
38
53
  defaultMode: "floating",
39
54
  defaultFloatingSize: { width: 112, height: 28 },
40
55
  quickSettings: [LIVE_STREAMING_QUICK_SETTING],
41
- settings: (context) => withLiveStreamingSetting(optionsSettings(), context.settings),
56
+ settings: (context) => withLiveStreamingSetting(optionsSettings(context.settings), context.settings),
42
57
  },
43
58
  ],
44
59