gloomberb 0.18.0 → 0.18.2
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/package.json +1 -1
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +6 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/types.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/fred-series.ts +15 -5
- package/src/cli/commands/market.ts +290 -159
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/overview.ts +31 -6
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +105 -52
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +4 -1
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +37 -2
- package/src/cli/pane-functions/catalog.ts +3 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +9 -4
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +31 -7
- package/src/cli/pane-functions/headless.ts +6 -5
- package/src/cli/pane-functions/index.ts +3 -2
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +2 -1
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +51 -16
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +22 -7
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +64 -16
- package/src/components/chart/composite/price-axis-labels.tsx +2 -1
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/command-bar/workflow/collection-actions.ts +4 -0
- package/src/components/index.ts +2 -0
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/onboarding/wizard-positions.ts +8 -0
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/status.tsx +17 -3
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +57 -22
- package/src/i18n/es.ts +9 -3
- package/src/i18n/ja.ts +9 -3
- package/src/i18n/ko.ts +9 -3
- package/src/i18n/zh-cn.ts +10 -4
- package/src/i18n/zh-tw.ts +10 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/analytics/risk-client.ts +44 -11
- package/src/plugins/builtin/analytics/risk-fx.ts +61 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +10 -1
- package/src/plugins/builtin/analytics/risk-model.ts +79 -16
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/analytics/risk-test-data.ts +3 -0
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +182 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +505 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +573 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +3 -2
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +31 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +10 -3
- package/src/plugins/builtin/cloud/team/pane.tsx +30 -9
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +36 -5
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +26 -12
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/custom-view/loader.ts +3 -2
- package/src/plugins/builtin/custom-view/pane.tsx +5 -4
- package/src/plugins/builtin/custom-view/view-spec.ts +1 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +38 -2
- package/src/plugins/builtin/econ/fred-catalog.ts +6 -2
- package/src/plugins/builtin/econ/fred-series-map.ts +12 -1
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +20 -5
- package/src/plugins/builtin/equity-screener/model.ts +40 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +24 -21
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/executives/data.ts +24 -8
- package/src/plugins/builtin/executives/index.tsx +2 -18
- package/src/plugins/builtin/executives/pane.tsx +34 -10
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +37 -16
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +72 -0
- package/src/plugins/builtin/insider/headless.ts +29 -4
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +2 -2
- package/src/plugins/builtin/market-movers/index.tsx +0 -3
- package/src/plugins/builtin/market-movers/session-body.tsx +27 -11
- package/src/plugins/builtin/market-movers/session.ts +25 -10
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +33 -13
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/letters/client.ts +1 -0
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +6 -3
- package/src/plugins/builtin/news/wire/news/detail-view.tsx +2 -1
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -1
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/feed-xml.ts +6 -1
- package/src/plugins/builtin/news/wire/rss/managed-feed.ts +1 -0
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +33 -8
- package/src/plugins/builtin/notes/export.ts +49 -3
- package/src/plugins/builtin/notes/files.ts +14 -3
- package/src/plugins/builtin/notes/index.tsx +3 -3
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +45 -10
- package/src/plugins/builtin/notes/store.ts +13 -3
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/view.tsx +77 -22
- package/src/plugins/builtin/options-calculator/pane.tsx +4 -2
- package/src/plugins/builtin/options-scenario/pane.tsx +41 -6
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -7
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +166 -15
- package/src/plugins/builtin/portfolio-list/cli/render.ts +69 -15
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +2 -2
- package/src/plugins/builtin/portfolio-list/headless.ts +3 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +44 -17
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -1
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +42 -7
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +105 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-feature-wall.tsx +9 -4
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +62 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/thirteenf/api.ts +4 -1
- package/src/plugins/builtin/thirteenf/data.ts +5 -1
- package/src/plugins/builtin/thirteenf/headless.ts +3 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +8 -3
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -2
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +61 -0
- package/src/portfolio/mutations.ts +16 -2
- package/src/portfolio/position-metrics.ts +45 -4
- package/src/portfolio/position-quantity.ts +43 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +39 -7
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +6 -0
- package/src/renderers/dom/styles.css +12 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +12 -0
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +27 -2
- package/src/sources/gloomberb-cloud/index.ts +50 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +63 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/team/collections.ts +2 -0
- package/src/tickers/listing-fate.ts +27 -8
- package/src/tickers/metadata.ts +10 -0
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +12 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/ticker.ts +9 -0
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +2 -1
- package/src/utils/currency-units.ts +41 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Button, ChoiceDialog, confirmDialog, DataTableView, EmptyState, PageStackView, PaneStatusBody, QueryBar, StatGrid, statGridRows,
|
|
2
|
+
import { splitDelayedStatus, Button, ChoiceDialog, confirmDialog, DataTableView, EmptyState, PageStackView, PaneStatusBody, QueryBar, StatGrid, statGridRows,
|
|
3
3
|
usePaneFooter, usePaneNoticeFooter, usePaneTabs, type DataTableColumn, type SelectControl, type StatItem } from "../../../components";
|
|
4
4
|
import { useAsyncResource, useInputCapture, usePaneInstance, usePaneSettingValue, usePaneTicker,
|
|
5
5
|
usePluginAppActions, usePluginPaneState, usePluginState, useShortcut } from "../../../public/react";
|
|
6
|
+
import { usePaneAppConfig } from "../../../state/app/context";
|
|
7
|
+
import { usePluginBrokerActions, usePluginTickerActions } from "../../runtime";
|
|
8
|
+
import { brokerCanTrade, brokerCanTradeStrategy, openTradeTicket } from "../broker-trading/command";
|
|
9
|
+
import { MAX_STRATEGY_LEGS, strategyFromLegs } from "../broker-trading/strategy";
|
|
10
|
+
import { optionContractName } from "../../../utils/options";
|
|
6
11
|
import { Box } from "../../../ui";
|
|
7
12
|
import { useDialogState } from "../../../ui/dialog";
|
|
8
13
|
import { useDialog, type PromptContext } from "../../../ui/dialog";
|
|
@@ -72,13 +77,16 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
|
|
|
72
77
|
const [consumedSeed, setConsumedSeed] = usePluginPaneState<string | null>("consumedSeed", null);
|
|
73
78
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
74
79
|
const controller = useRef<AbortController | null>(null);
|
|
80
|
+
const brokerInstrument = target?.instrument ?? null;
|
|
75
81
|
const explicitSpot = Number(settings.spot);
|
|
76
82
|
const loadMarket = useCallback(async (force: boolean) => {
|
|
77
83
|
controller.current?.abort();
|
|
78
84
|
const abort = new AbortController(); controller.current = abort;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
// The chain OMON reads for this ticker, from its broker when it is linked to one: one chain, one source, one cache entry.
|
|
86
|
+
return loadScenarioMarket({ instrument: { symbol: underlying, exchange, brokerId: brokerInstrument?.brokerId,
|
|
87
|
+
brokerInstanceId: brokerInstrument?.brokerInstanceId, instrument: brokerInstrument },
|
|
88
|
+
expiration: expiration ?? undefined, forceRefresh: force, signal: abort.signal });
|
|
89
|
+
}, [underlying, exchange, expiration, brokerInstrument]);
|
|
82
90
|
// Fully specified calculations and screenshot snapshots never need a quote refresh.
|
|
83
91
|
const needsMarket = !frozen && (!["spot", "rate", "dividendYield"].every((key) => settings[key] != null && settings[key] !== "") || detail === "chain");
|
|
84
92
|
const resource = useAsyncResource(underlying && needsMarket ? loadMarket : null);
|
|
@@ -240,6 +248,32 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
|
|
|
240
248
|
};
|
|
241
249
|
// Freezing keeps the marks shown at this moment as the scenario's inputs.
|
|
242
250
|
const freeze = () => { if (live) setPosition(live.position); setFollow(false); };
|
|
251
|
+
// With a broker that takes them, the position trades as one order: a
|
|
252
|
+
// spread on the underlying's Trade tab, or one contract's own ticket. The
|
|
253
|
+
// live mids, else the entry prices, say whether it opens for a debit or a credit.
|
|
254
|
+
const { pinTicker } = usePluginTickerActions();
|
|
255
|
+
const { getBrokerAdapter } = usePluginBrokerActions();
|
|
256
|
+
const { brokerInstances } = usePaneAppConfig();
|
|
257
|
+
const tradeIntent = useMemo(() => {
|
|
258
|
+
const legs = position?.legs ?? [];
|
|
259
|
+
if (frozen || !underlying || !legs.length || legs.length > MAX_STRATEGY_LEGS || legs.some((leg) => leg.multiplier !== 100 || !legSymbols.get(leg.id))) return null;
|
|
260
|
+
const intent = strategyFromLegs(legs.map((leg) => {
|
|
261
|
+
const quote = live?.quotes.get(leg.id);
|
|
262
|
+
return { contractSymbol: legSymbols.get(leg.id)!, quantity: leg.quantity, price: quote ? optionMid(quote) ?? leg.price : leg.price };
|
|
263
|
+
}));
|
|
264
|
+
if (!intent) return null;
|
|
265
|
+
const tradable = intent.legs.length === 1 ? brokerCanTrade(brokerInstances, getBrokerAdapter, intent.legs[0]!.contractSymbol, "OPT")
|
|
266
|
+
: brokerCanTradeStrategy(brokerInstances, getBrokerAdapter, underlying, intent.legs.length);
|
|
267
|
+
return tradable ? intent : null;
|
|
268
|
+
}, [frozen, underlying, position?.legs, legSymbols, live?.quotes, brokerInstances, getBrokerAdapter]);
|
|
269
|
+
const trade = () => {
|
|
270
|
+
if (!tradeIntent) return;
|
|
271
|
+
const [only] = tradeIntent.legs;
|
|
272
|
+
if (tradeIntent.legs.length === 1 && only) {
|
|
273
|
+
openTradeTicket({ pinTicker }, only.contractSymbol, { action: only.action, quantity: tradeIntent.quantity * only.ratio },
|
|
274
|
+
{ instrument: null, listing: { name: optionContractName(only.contractSymbol) ?? only.contractSymbol, exchange: "", currency: position?.currency ?? "USD", type: "OPT" } });
|
|
275
|
+
} else openTradeTicket({ pinTicker }, symbol ?? underlying, tradeIntent);
|
|
276
|
+
};
|
|
243
277
|
const hints = detail === "chain" ? [{ id: "expiry", key: "d", label: "ate", onPress: () => chainExpiryControl.current?.open() }] : detail || volActive ? [] : [
|
|
244
278
|
{ id: "add", key: "a", label: "dd leg", onPress: addTyped },
|
|
245
279
|
{ id: "chain", key: "c", label: "hain", onPress: () => setDetail("chain") },
|
|
@@ -250,6 +284,7 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
|
|
|
250
284
|
{ id: "vol", key: "v", label: "ol shift", onPress: () => setVolActive(true) },
|
|
251
285
|
{ id: "save", key: "s", label: "ave", onPress: () => setDetail("save") }] : []),
|
|
252
286
|
{ id: "load", key: "b", label: "rowse saved", onPress: () => void loadSaved() },
|
|
287
|
+
...(tradeIntent ? [{ id: "trade", key: "t", label: "rade", onPress: trade }] : []),
|
|
253
288
|
...(live ? [{ id: "follow", key: "f", label: "reeze", onPress: freeze }]
|
|
254
289
|
: follow || frozen || frozenMarket || !storedPosition ? [] : [{ id: "follow", key: "f", label: "ollow market", onPress: () => setFollow(true) }]),
|
|
255
290
|
];
|
|
@@ -270,8 +305,8 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
|
|
|
270
305
|
...(resource.loading ? [{ id: "loading", parts: [{ text: "loading chain", tone: "muted" as const }] }] : []),
|
|
271
306
|
...(error ? [{ id: "error", parts: [{ text: error, tone: "warning" as const }] }] : []),
|
|
272
307
|
...(hedge.error ? [{ id: "hedge-error", parts: [{ text: hedge.error, tone: "warning" as const }] }] : []),
|
|
273
|
-
...(live ?
|
|
274
|
-
variants: [[{ text: shortSpotText, tone: "muted"
|
|
308
|
+
...(live ? splitDelayedStatus({ id: "asof", parts: [{ text: `${live.basis} · ${spotText} · ${new Date(live.observedAt).toISOString().slice(11, 19)} UTC`, tone: "muted" }],
|
|
309
|
+
variants: [[{ text: shortSpotText, tone: "muted" }]] })
|
|
275
310
|
: position ? [{ id: "asof", parts: [{ text: `${spotText} · ${formatSpotTime(position.asOf)} · ${market?.source ? "market" : "input assumptions"}${midVolatility}`, tone: "muted" as const }],
|
|
276
311
|
variants: [[{ text: shortSpotText, tone: "muted" as const }]] }] : []),
|
|
277
312
|
], hints }), [hints, position, market?.source, resource.loading, error, hedge.error, live?.basis, live?.observedAt, midVolatility, spotText, shortSpotText]);
|
|
@@ -7,6 +7,8 @@ import { isFiniteNumber } from "../../../utils/guards";
|
|
|
7
7
|
export interface PortfolioAccountMetrics {
|
|
8
8
|
dailyPnl: number;
|
|
9
9
|
dailyPnlPct: number;
|
|
10
|
+
/** The day is the broker's own, measured from its account's prior close rather than each quote's. */
|
|
11
|
+
brokerDay?: true;
|
|
10
12
|
unrealizedPnl: number;
|
|
11
13
|
unrealizedPnlPct: number;
|
|
12
14
|
realizedPnl?: number;
|
|
@@ -196,6 +198,7 @@ export function resolvePortfolioAccountMetrics(
|
|
|
196
198
|
return {
|
|
197
199
|
dailyPnl,
|
|
198
200
|
dailyPnlPct,
|
|
201
|
+
...(brokerDailyPnl != null ? { brokerDay: true as const } : {}),
|
|
199
202
|
unrealizedPnl,
|
|
200
203
|
unrealizedPnlPct,
|
|
201
204
|
realizedPnl: isFiniteNumber(account?.realizedPnl) ? convertAccountValue(account.realizedPnl) : undefined,
|
|
@@ -37,7 +37,7 @@ export const FILE_OPTION = {
|
|
|
37
37
|
export const SYMBOL_LIST_HELP = {
|
|
38
38
|
title: "Several symbols",
|
|
39
39
|
lines: [
|
|
40
|
-
"add and remove take any number of symbols split by spaces, commas or newlines, a --file, or piped input (or -). An entry of a list with commas or newlines that has spaces in it is a company's name when search finds one, read as its US listing first with a line under it saying which, and otherwise the symbols it holds. A CSV is read from its Symbol, Ticker or Code column,
|
|
40
|
+
"add and remove take any number of symbols split by spaces, commas or newlines, a --file, or piped input (or -). An entry of a list with commas or newlines that has spaces in it is a company's name when search finds one, read as its US listing first with a line under it saying which, and otherwise the symbols it holds. A CSV is read from its Symbol, Ticker or Code column. Without that header every cell is a symbol, unless names, prices or weights sit beside them: then the first column is read and the cells left out are named. Quote a list name with spaces.",
|
|
41
41
|
"Each symbol gets a line, then a summary. Exit status 0 when at least one symbol was added (or removed) or already was, 1 when none resolved or the usage is wrong.",
|
|
42
42
|
"A bare US-style symbol is not added when the listing it matched stopped trading, is abroad (DFS as DFS:LSE) or was taken by another company in the last two years (BRKL), or when nothing lists under it now and its company trades as another. Those are listed under Needs a choice, with the add for what their company trades as now and the listing that matched, which naming keeps; one symbol alone fails with the same. A symbol another company took longer ago says so under its line.",
|
|
43
43
|
"add --dry-run looks every symbol up and prints the same lines without saving.",
|
|
@@ -115,14 +115,15 @@ interface CollectionRef {
|
|
|
115
115
|
/**
|
|
116
116
|
* One line per symbol and the summary under them; `--json` and `--csv` give
|
|
117
117
|
* the rows, with the summary in the JSON metadata and on standard error beside
|
|
118
|
-
* CSV. A separator dropped from a symbol of the list
|
|
119
|
-
*
|
|
118
|
+
* CSV. A separator dropped from a symbol of the list, and the cells a table
|
|
119
|
+
* read by one column left out, are named as warnings. The command exits
|
|
120
|
+
* non-zero when no symbol ended up as asked.
|
|
120
121
|
*/
|
|
121
122
|
export function printSymbolRows(
|
|
122
123
|
ctx: CliCommandContext,
|
|
123
124
|
rows: SymbolRow[],
|
|
124
|
-
{ verb, collection, repeated,
|
|
125
|
-
verb: Verb; collection: CollectionRef; repeated: number;
|
|
125
|
+
{ verb, collection, repeated, notes, retry, dryRun = false }: {
|
|
126
|
+
verb: Verb; collection: CollectionRef; repeated: number; notes: readonly string[];
|
|
126
127
|
/** The add of some listings, for the line that adds what the symbols needing a choice trade as now. */
|
|
127
128
|
retry?: (keys: string) => string;
|
|
128
129
|
dryRun?: boolean;
|
|
@@ -137,7 +138,7 @@ export function printSymbolRows(
|
|
|
137
138
|
ctx.printResult({
|
|
138
139
|
data: rows,
|
|
139
140
|
metadata: { [collection.kind]: collection.name, ...counts, repeated, summary, ...dryRun ? { dryRun } : {} },
|
|
140
|
-
...
|
|
141
|
+
...notes.length > 0 ? { warnings: [...notes] } : {},
|
|
141
142
|
}, {
|
|
142
143
|
text: () => [
|
|
143
144
|
...rows.filter((row) => row.status !== "needs_choice").map((row) => rowLines(row, verb)),
|
|
@@ -215,7 +216,7 @@ export async function requireSymbolList(
|
|
|
215
216
|
file: string | undefined,
|
|
216
217
|
usage: string,
|
|
217
218
|
readEntries?: (entries: string[]) => Promise<string[]>,
|
|
218
|
-
): Promise<{ symbols: string[]; repeated: number;
|
|
219
|
+
): Promise<{ symbols: string[]; repeated: number; notes: string[]; single: string | null }> {
|
|
219
220
|
try {
|
|
220
221
|
let typed: string[] = [];
|
|
221
222
|
const list = await readSymbolList({ args, file, readEntries: async (entries) => {
|
|
@@ -47,7 +47,14 @@ import { importSource, notionalPositions, rankThirteenFLines, type ThirteenFImpo
|
|
|
47
47
|
import { importedFromText } from "../imported-from";
|
|
48
48
|
import { savedListingCoinNote } from "../../../../cli/commands/crypto-hints";
|
|
49
49
|
import { findNamedCollection } from "../../../../portfolio/collection-name";
|
|
50
|
-
import {
|
|
50
|
+
import { wholeUnitQuantityProblem } from "../../../../portfolio/position-quantity";
|
|
51
|
+
import { CostFxError, loadCostFxRate, type CostFxHistoryClient } from "../../../../portfolio/cost-fx";
|
|
52
|
+
import { lotCostFxRate, sameCurrency } from "../../../../portfolio/position-metrics";
|
|
53
|
+
import { formatFxRate } from "../../../../cli/fx-pair";
|
|
54
|
+
import { createBaseConverter } from "../../../../cli/base-converter";
|
|
55
|
+
import { currencyUnitLabel, heldCurrencyUnit } from "../../../../utils/currency-units";
|
|
56
|
+
import { parseTypedNumber, readCurrencyAmount, readTypedNumber } from "../../../../utils/decimal-comma";
|
|
57
|
+
import { DATE_OPTION_EXAMPLES, dateOptionError, readCalendarDate } from "../../../../utils/calendar-date";
|
|
51
58
|
import {
|
|
52
59
|
failPortfolioCommand,
|
|
53
60
|
isCashArgument,
|
|
@@ -66,6 +73,7 @@ import { readNameEntries } from "../../../../cli/name-entries";
|
|
|
66
73
|
import { DRIP_DEFAULT_YEARS, DRIP_MAX_YEARS, parseDripYears, parseWithholding } from "../income";
|
|
67
74
|
|
|
68
75
|
const ADD_USAGE = "Usage: gloomberb portfolio add <portfolio> <symbols...> [--file <path>]";
|
|
76
|
+
const POSITION_USAGE = "Usage: gloomberb portfolio position set <portfolio> <ticker> <shares|contracts> <avg-cost> [currency] [--date <purchase date> | --cost-fx <rate>]";
|
|
69
77
|
const REMOVE_USAGE = "Usage: gloomberb portfolio remove <portfolio> <symbols...> [--file <path>]";
|
|
70
78
|
const CASH_USAGE = "Usage: gloomberb portfolio cash set <portfolio> <amount> [currency] | portfolio cash clear <portfolio>";
|
|
71
79
|
const TARGET_USAGE = "Usage: gloomberb portfolio target set <portfolio> <symbol|CASH> <weight%> | target clear <portfolio> [symbol] | target show <portfolio>";
|
|
@@ -207,7 +215,7 @@ async function addToPortfolioCommand(args: string[], file: string | undefined, e
|
|
|
207
215
|
retry,
|
|
208
216
|
add: (ticker) => addTickerToPortfolio(ticker, portfolio.id),
|
|
209
217
|
});
|
|
210
|
-
printSymbolRows(ctx, rows, { verb: "add", collection: { kind: "portfolio", name: portfolio.name }, repeated: list.repeated,
|
|
218
|
+
printSymbolRows(ctx, rows, { verb: "add", collection: { kind: "portfolio", name: portfolio.name }, repeated: list.repeated, notes: list.notes, retry, dryRun });
|
|
211
219
|
});
|
|
212
220
|
}
|
|
213
221
|
|
|
@@ -274,7 +282,7 @@ async function removeFromPortfolioCommand(args: string[], file: string | undefin
|
|
|
274
282
|
},
|
|
275
283
|
});
|
|
276
284
|
if (nextConfig !== context.config) await saveConfig(nextConfig);
|
|
277
|
-
printSymbolRows(ctx, rows, { verb: "remove", collection: { kind: "portfolio", name: portfolio.name }, repeated: list.repeated,
|
|
285
|
+
printSymbolRows(ctx, rows, { verb: "remove", collection: { kind: "portfolio", name: portfolio.name }, repeated: list.repeated, notes: list.notes });
|
|
278
286
|
});
|
|
279
287
|
}
|
|
280
288
|
|
|
@@ -286,6 +294,8 @@ async function setPositionCommand(
|
|
|
286
294
|
rawCurrency: string | undefined,
|
|
287
295
|
exchange: string | undefined,
|
|
288
296
|
ctx: CliCommandContext,
|
|
297
|
+
/** `--date` (an ISO day) and `--cost-fx`, read already; the client reads the date's FX closes. */
|
|
298
|
+
cost: { date?: string; costFx?: number; history?: CostFxHistoryClient } = {},
|
|
289
299
|
futuresTerms?: (symbol: string) => Promise<FuturesContractTermsPayload | null>,
|
|
290
300
|
) {
|
|
291
301
|
await withMarketData(ctx, async ({ config, store, dataProvider }) => {
|
|
@@ -298,31 +308,56 @@ async function setPositionCommand(
|
|
|
298
308
|
const avgCost = parseAmountArg(avgCostValue, "Average cost");
|
|
299
309
|
// The notes say which listing a bare symbol went to, and that a bare coin ticker is not the coin, as `portfolio add` does.
|
|
300
310
|
const { ticker, notes } = await resolveCliTicker(symbol, store, dataProvider, exchange);
|
|
311
|
+
// `1.250` on a listing held in whole lots asks which was meant, as `1,250` did above.
|
|
312
|
+
const lots = wholeUnitQuantityProblem(sharesValue, ticker, { listing: savedListingName(ticker).key });
|
|
313
|
+
if (lots) throw new PortfolioCliError(lots);
|
|
301
314
|
// A future is held in contracts of the size its terms give, as `quote` states it, and a cost typed as
|
|
302
315
|
// corn is quoted counts cents.
|
|
303
316
|
const future = isFuturesTicker(ticker);
|
|
304
317
|
const size = await loadFuturesEntrySize(ticker, futuresTerms);
|
|
305
318
|
const currency = resolveManualPositionCurrency(rawCurrency, ticker, portfolio, config.baseCurrency, size?.currency);
|
|
319
|
+
// The first position of an empty manual portfolio sets its currency; the cost converts into the currency
|
|
320
|
+
// `portfolio show` values the portfolio in once it is set, which is the base currency when that is not USD.
|
|
321
|
+
const firstPosition = !hasOpenPortfolioPositions(portfolio.id, await store.loadAllTickers());
|
|
322
|
+
const adopted = firstPosition ? adoptFirstPositionCurrency(config, portfolio.id, currency) : null;
|
|
323
|
+
const valued = adopted ?? config;
|
|
324
|
+
const portfolioCurrency = resolvePortfolioTotalsCurrency(
|
|
325
|
+
valued.portfolios.find((entry) => entry.id === portfolio.id) ?? portfolio, valued.baseCurrency);
|
|
326
|
+
const toPortfolio = createBaseConverter(dataProvider, heldCurrencyUnit(portfolioCurrency).currency);
|
|
327
|
+
const costFx = await positionCostFx({
|
|
328
|
+
...cost, currency, portfolioCurrency, listing: savedListingName(ticker).key,
|
|
329
|
+
// Today's rate, in the units the lot is held in: what a typed rate is checked against.
|
|
330
|
+
todaysRate: async () => (await toPortfolio(1, heldCurrencyUnit(currency).currency))
|
|
331
|
+
/ heldCurrencyUnit(currency).divisor * heldCurrencyUnit(portfolioCurrency).divisor,
|
|
332
|
+
});
|
|
306
333
|
const result = setManualPortfolioPosition(ticker, portfolio.id, {
|
|
307
334
|
shares,
|
|
308
335
|
avgCost,
|
|
309
336
|
currency,
|
|
310
337
|
...(size ? { multiplier: size.multiplier } : {}),
|
|
338
|
+
...(cost.date ? { dateAcquired: cost.date } : {}),
|
|
339
|
+
...(costFx ? { costFx: costFx.costFx } : {}),
|
|
311
340
|
});
|
|
312
|
-
const
|
|
341
|
+
const lot = result.ticker.metadata.positions.find((position) => position.portfolio === portfolio.id);
|
|
342
|
+
const multiplier = lot?.multiplier;
|
|
313
343
|
if (future && !multiplier) {
|
|
314
344
|
notes.push(`No contract size is known for ${savedListingName(result.ticker).label}, so its value and P&L are not shown.`);
|
|
315
345
|
}
|
|
316
|
-
const firstPosition = !hasOpenPortfolioPositions(portfolio.id, await store.loadAllTickers());
|
|
317
346
|
await store.saveTicker(result.ticker);
|
|
318
|
-
const adopted = firstPosition ? adoptFirstPositionCurrency(config, portfolio.id, currency) : null;
|
|
319
347
|
if (adopted) await saveConfig(adopted);
|
|
320
348
|
const saved = savedListingName(result.ticker);
|
|
321
349
|
const quantity = formatMarketQuantity(shares, { assetCategory: result.ticker.metadata.assetCategory });
|
|
350
|
+
// A cost in another currency converts at the rate it was bought at when the lot has one, else at today's, and says so.
|
|
351
|
+
const foreign = !sameCurrency(currency, portfolioCurrency);
|
|
352
|
+
const boughtRate = lot ? lotCostFxRate(lot, currency, portfolioCurrency) : null;
|
|
353
|
+
// The purchase date shows where it was given or sets the rate; a lot in the portfolio's currency otherwise prints as it did.
|
|
354
|
+
const purchaseDate = lot?.dateAcquired && (cost.date || foreign) ? lot.dateAcquired : null;
|
|
322
355
|
ctx.printResult({
|
|
323
356
|
data: {
|
|
324
357
|
changed: true, ...listingFields(saved), portfolio: portfolio.name, shares, avgCost, currency,
|
|
325
358
|
...(future ? { contractMultiplier: multiplier ?? null, contractSize: size?.size ?? null } : {}),
|
|
359
|
+
...(purchaseDate ? { dateAcquired: purchaseDate } : {}),
|
|
360
|
+
...(foreign ? { costFxRate: boughtRate, costFxCurrency: portfolioCurrency, costFxDate: costFx?.date ?? null } : {}),
|
|
326
361
|
},
|
|
327
362
|
warnings: notes.length > 0 ? notes : undefined,
|
|
328
363
|
}, {
|
|
@@ -334,7 +369,14 @@ async function setPositionCommand(
|
|
|
334
369
|
: ["Shares", quantity],
|
|
335
370
|
["Average Cost", formatMarketCostWithCurrency(avgCost, currency, { assetCategory: result.ticker.metadata.assetCategory })],
|
|
336
371
|
["Currency", currency],
|
|
372
|
+
...(purchaseDate ? [["Purchase Date", purchaseDate] as [string, string]] : []),
|
|
373
|
+
...(boughtRate !== null
|
|
374
|
+
? [["Cost FX", `${formatFxRate(boughtRate)} ${portfolioCurrency} per ${currency}${costFx?.date ? ` (${costFx.date} close)` : ""}`] as [string, string]]
|
|
375
|
+
: []),
|
|
337
376
|
]),
|
|
377
|
+
...(foreign && boughtRate === null
|
|
378
|
+
? [cliStyles.muted(`Cost converted at today's rate into ${portfolioCurrency}; add --date or --cost-fx to use the rate when bought.`)]
|
|
379
|
+
: []),
|
|
338
380
|
].join("\n"),
|
|
339
381
|
});
|
|
340
382
|
} catch (error) {
|
|
@@ -343,6 +385,102 @@ async function setPositionCommand(
|
|
|
343
385
|
});
|
|
344
386
|
}
|
|
345
387
|
|
|
388
|
+
/**
|
|
389
|
+
* The rate a position's cost was bought at, from `--cost-fx` as typed or from
|
|
390
|
+
* the FX closes of its `--date`, for a position held in another currency
|
|
391
|
+
* than the portfolio's; undefined when neither was given or the position is
|
|
392
|
+
* in the portfolio's currency (a date alone then only dates the lot).
|
|
393
|
+
* `--cost-fx` on a position in the portfolio's own currency, a typed rate
|
|
394
|
+
* that reads as a mistake against today's, and a date with no FX close fail
|
|
395
|
+
* saying what to give instead.
|
|
396
|
+
*/
|
|
397
|
+
async function positionCostFx({ date, costFx, history, currency, portfolioCurrency, listing, todaysRate }: {
|
|
398
|
+
date?: string; costFx?: number; history?: CostFxHistoryClient; currency: string; portfolioCurrency: string; listing: string;
|
|
399
|
+
/** Today's rate, one unit of `currency` in `portfolioCurrency`; NaN when it cannot be read. */
|
|
400
|
+
todaysRate: () => Promise<number>;
|
|
401
|
+
}): Promise<{ costFx: { rate: number; currency: string }; date: string | null } | undefined> {
|
|
402
|
+
const foreign = !sameCurrency(currency, portfolioCurrency);
|
|
403
|
+
const insteadHint = `Give the rate it was bought at instead: --cost-fx <${portfolioCurrency} per ${currency}>.`;
|
|
404
|
+
if (costFx != null) {
|
|
405
|
+
if (!foreign) {
|
|
406
|
+
const held = currency === portfolioCurrency ? currency : `${currency}, a unit of ${portfolioCurrency}`;
|
|
407
|
+
throw new PortfolioCliError(`--cost-fx converts a cost in another currency; ${listing} is held in ${held}, the portfolio's currency.`);
|
|
408
|
+
}
|
|
409
|
+
const problem = implausibleCostFx(costFx, await todaysRate().catch(() => Number.NaN), { currency, portfolioCurrency, listing });
|
|
410
|
+
if (problem) throw new PortfolioCliError(problem, "Or give the purchase --date, and its daily FX close is used.");
|
|
411
|
+
return { costFx: { rate: costFx, currency: portfolioCurrency }, date: null };
|
|
412
|
+
}
|
|
413
|
+
if (!date || !foreign) return undefined;
|
|
414
|
+
try {
|
|
415
|
+
const reading = await loadCostFxRate(currency, portfolioCurrency, date, history ?? apiClient);
|
|
416
|
+
return { costFx: { rate: reading.rate, currency: portfolioCurrency }, date: reading.date };
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (error instanceof CostFxError) {
|
|
419
|
+
throw new PortfolioCliError(`${error.message}, so the ${currency} cost cannot be converted at the rate of ${date}.`, insteadHint);
|
|
420
|
+
}
|
|
421
|
+
// Offline, signed out or a server error: say what was being read, and the way that needs no network.
|
|
422
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
423
|
+
throw new PortfolioCliError(`Could not read the ${currency} and ${portfolioCurrency} FX closes of ${date}: ${reason}.`, insteadHint);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** How far from today's rate a typed one may be: a currency that lost 99% since purchase still fits. */
|
|
428
|
+
const COST_FX_MAX_RATIO = 100;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Why a typed `--cost-fx` reads as a mistake against today's rate, else null:
|
|
432
|
+
* a rate per pound for a lot held in pence, the inverse of the pair (0.0319
|
|
433
|
+
* for 31.37 TRY per USD), or more than a hundred times off either way. A
|
|
434
|
+
* rate is only called inverted when today's is far from 1 and the typed one
|
|
435
|
+
* lies on the other side of it, so EUR per USD near parity is never refused.
|
|
436
|
+
* Null when today's rate is not known.
|
|
437
|
+
*/
|
|
438
|
+
function implausibleCostFx(
|
|
439
|
+
rate: number,
|
|
440
|
+
today: number,
|
|
441
|
+
{ currency, portfolioCurrency, listing }: { currency: string; portfolioCurrency: string; listing: string },
|
|
442
|
+
): string | null {
|
|
443
|
+
if (!Number.isFinite(today) || today <= 0) return null;
|
|
444
|
+
const pair = `${portfolioCurrency} per ${currency}`;
|
|
445
|
+
const typed = formatFxRate(rate);
|
|
446
|
+
const todays = `today's is ${formatFxRate(today)} ${pair}`;
|
|
447
|
+
const unit = heldCurrencyUnit(currency);
|
|
448
|
+
if (unit.divisor !== 1 && rate / today >= unit.divisor / 5 && rate / today <= unit.divisor * 5) {
|
|
449
|
+
const name = currency.toUpperCase() === "USX" ? "US cents" : currencyUnitLabel(currency).replace(/^\S+ \((.+)\)$/, "$1");
|
|
450
|
+
return `--cost-fx ${typed} looks like ${portfolioCurrency} per ${unit.currency}; ${listing} is held in ${currency} (${name}), `
|
|
451
|
+
+ `so the rate is ${pair}, 1/${unit.divisor} of that: ${todays}.`;
|
|
452
|
+
}
|
|
453
|
+
if ((today >= 3 && rate < 1) || (today <= 1 / 3 && rate > 1)) {
|
|
454
|
+
return `--cost-fx ${typed} looks inverted: it is the ${portfolioCurrency} paid for one ${currency}, and ${todays}.`;
|
|
455
|
+
}
|
|
456
|
+
if (rate / today > COST_FX_MAX_RATIO || today / rate > COST_FX_MAX_RATIO) {
|
|
457
|
+
return `--cost-fx ${typed} is more than ${COST_FX_MAX_RATIO} times off today's rate (${formatFxRate(today)} ${pair}); check the rate.`;
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** `--date`: the day the position was bought, as an ISO day, no later than today (UTC). */
|
|
463
|
+
function parsePurchaseDate(value: string, ctx: CliCommandContext): string {
|
|
464
|
+
const date = readCalendarDate(value);
|
|
465
|
+
if (!date) ctx.fail(dateOptionError(value, "--date", { subject: "the purchase date" }), POSITION_USAGE);
|
|
466
|
+
if (date > new Date().toISOString().slice(0, 10)) ctx.fail(`--date ${value.trim()} is after today; it is the day the position was bought.`, POSITION_USAGE);
|
|
467
|
+
return date;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** `--cost-fx`: one unit of the position's currency in the portfolio's when it was bought, a positive number (32,15 reads as 32.15). */
|
|
471
|
+
function parseCostFx(value: string, ctx: CliCommandContext): number {
|
|
472
|
+
let rate = Number.NaN;
|
|
473
|
+
try {
|
|
474
|
+
rate = readTypedNumber(value, "--cost-fx");
|
|
475
|
+
} catch (error) {
|
|
476
|
+
ctx.fail(error instanceof Error ? error.message : String(error), POSITION_USAGE);
|
|
477
|
+
}
|
|
478
|
+
if (!Number.isFinite(rate) || rate <= 0) {
|
|
479
|
+
ctx.fail(`--cost-fx is the rate the cost was bought at, a positive number such as 32.15 (TRY per USD); got "${value.trim()}".`, POSITION_USAGE);
|
|
480
|
+
}
|
|
481
|
+
return rate;
|
|
482
|
+
}
|
|
483
|
+
|
|
346
484
|
const CURRENCY_CODE = /^[A-Za-z]{3}$/;
|
|
347
485
|
|
|
348
486
|
/**
|
|
@@ -775,6 +913,8 @@ const POSITION_HELP = {
|
|
|
775
913
|
lines: [
|
|
776
914
|
"position set replaces the portfolio's lots of the symbol with one: a negative size is a short, and 0 is refused (portfolio remove drops the symbol).",
|
|
777
915
|
"A future's size is in contracts, at a price per unit of its contract (GC=F: per troy ounce); the contract size is read from its terms, as quote shows them.",
|
|
916
|
+
"The cost is in the currency named after it, else the listing's. A holding in another currency than the portfolio's (a US stock in a TRY portfolio) can be entered at its cost in the portfolio's currency (position set Lira BRK-B 10 20310 TRY), so P&L is in what was paid.",
|
|
917
|
+
"Entered at its cost in its own currency, its P&L converts the cost at today's rate unless --date gives the purchase date, whose daily FX close is used, or --cost-fx gives the rate paid (portfolio currency per unit of the holding's: 32.15 TRY per USD); then the P&L carries the currency's move since. portfolio show names the holdings whose cost converts at today's rate.",
|
|
778
918
|
],
|
|
779
919
|
};
|
|
780
920
|
|
|
@@ -792,7 +932,7 @@ const AMOUNTS_HELP = {
|
|
|
792
932
|
lines: [
|
|
793
933
|
"Numbers take either decimal mark: 1.234,5, 1234,5 and 1,234.5 are 1234.5, and 18,50 is 18.5.",
|
|
794
934
|
"Cash and --notional are amounts of a currency (cash in the portfolio's unless named, --notional in USD), so 500,000 and 1.234 are thousands, a third decimal being below a cent. In a currency with three decimals (KWD, BHD, OMR) they could be either, and the command asks which.",
|
|
795
|
-
"Shares, contracts and average cost read a lone dot as a decimal point (123.456) and ask which a lone comma before three digits meant (1,234).",
|
|
935
|
+
"Shares, contracts and average cost read a lone dot as a decimal point (123.456) and ask which a lone comma before three digits meant (1,234). On a listing held in whole lots (shares listed outside the US, a future's contracts) a lone dot before three digits (1.250) asks too.",
|
|
796
936
|
],
|
|
797
937
|
};
|
|
798
938
|
|
|
@@ -811,7 +951,7 @@ export const portfolioCliCommand: CliCommandDef = {
|
|
|
811
951
|
"portfolio delete <name>",
|
|
812
952
|
"portfolio add <portfolio> <symbols...> [--file <path>]",
|
|
813
953
|
"portfolio remove <portfolio> <symbols...> [--file <path>]",
|
|
814
|
-
"portfolio position set <portfolio> <symbol> <shares|contracts> <avg-cost> [currency]",
|
|
954
|
+
"portfolio position set <portfolio> <symbol> <shares|contracts> <avg-cost> [currency] [--date <purchase date> | --cost-fx <rate>]",
|
|
815
955
|
"portfolio cash set <portfolio> <amount> [currency]",
|
|
816
956
|
"portfolio cash clear <portfolio>",
|
|
817
957
|
"portfolio target set <portfolio> <symbol|CASH> <weight%>",
|
|
@@ -831,6 +971,8 @@ export const portfolioCliCommand: CliCommandDef = {
|
|
|
831
971
|
{ flags: "--name <portfolio>", description: "portfolio import: the portfolio to create or fill (default: the manager's name and 13F)" },
|
|
832
972
|
{ flags: "--notional <amount>", description: "portfolio import: the USD total the shares add up to at the filing's prices (default 100,000)" },
|
|
833
973
|
{ flags: "--replace", description: "portfolio import: replace the positions of an existing portfolio of that name" },
|
|
974
|
+
{ flags: "--date <purchase date>", description: `position set: the day it was bought, as ${DATE_OPTION_EXAMPLES}; a holding in another currency converts its cost at that day's FX close` },
|
|
975
|
+
{ flags: "--cost-fx <rate>", description: "position set: the rate a holding in another currency was bought at, in the portfolio's currency per unit of the holding's (32.15 TRY per USD)" },
|
|
834
976
|
{ flags: "--explain", description: "portfolio show: follow the report with what its terms mean (Weight, Target, Drift, Trade); gloomberb catalog glossary lists every term" },
|
|
835
977
|
],
|
|
836
978
|
examples: [
|
|
@@ -841,6 +983,9 @@ export const portfolioCliCommand: CliCommandDef = {
|
|
|
841
983
|
"portfolio add Research --file holdings.csv --dry-run",
|
|
842
984
|
"portfolio position set Research ASML 10 800 EUR",
|
|
843
985
|
"portfolio position set Hedge GC=F -10 4216.30",
|
|
986
|
+
"portfolio position set Lira BRK-B 10 20310 TRY",
|
|
987
|
+
"portfolio position set Lira BRK-B 10 410 USD --date 2024-03-01",
|
|
988
|
+
"portfolio position set Lira KO 40 62.50 USD --cost-fx 32.15",
|
|
844
989
|
"portfolio cash set Research 25000 USD",
|
|
845
990
|
"portfolio target set Research ASML 12.5%",
|
|
846
991
|
"portfolio target set Research CASH 10",
|
|
@@ -889,6 +1034,16 @@ export const portfolioCliCommand: CliCommandDef = {
|
|
|
889
1034
|
ctx.fail(error instanceof Error ? error.message : String(error), dripUsage);
|
|
890
1035
|
}
|
|
891
1036
|
const basis = close ? "regular-close" as const : "latest-print" as const;
|
|
1037
|
+
// A position's purchase date and the rate its cost was bought at, read before anything is looked up.
|
|
1038
|
+
const hasDate = args.some((arg) => arg === "--date" || arg.startsWith("--date="));
|
|
1039
|
+
const dateText = hasDate ? takeOption(args, "--date") ?? "" : undefined;
|
|
1040
|
+
const hasCostFx = args.some((arg) => arg === "--cost-fx" || arg.startsWith("--cost-fx="));
|
|
1041
|
+
const costFxText = hasCostFx ? takeOption(args, "--cost-fx") ?? "" : undefined;
|
|
1042
|
+
if ((hasDate || hasCostFx) && action !== "position") {
|
|
1043
|
+
ctx.fail(`${hasDate ? "--date" : "--cost-fx"} is an option of portfolio position set, not portfolio ${action ?? "list"}.`, POSITION_USAGE);
|
|
1044
|
+
}
|
|
1045
|
+
const purchaseDate = dateText != null ? parsePurchaseDate(dateText, ctx) : undefined;
|
|
1046
|
+
const costFx = costFxText != null ? parseCostFx(costFxText, ctx) : undefined;
|
|
892
1047
|
const hasFile = args.some((arg) => arg === "--file" || arg.startsWith("--file="));
|
|
893
1048
|
const file = hasFile ? takeOption(args, "--file") ?? "" : undefined;
|
|
894
1049
|
if (file != null && action !== "add" && action !== "remove") ctx.fail(`--file is an option of portfolio add and remove, not portfolio ${action ?? "list"}.`, ADD_USAGE);
|
|
@@ -936,15 +1091,11 @@ export const portfolioCliCommand: CliCommandDef = {
|
|
|
936
1091
|
|
|
937
1092
|
if (action === "position") {
|
|
938
1093
|
const subaction = args[1];
|
|
939
|
-
if (subaction !== "set")
|
|
940
|
-
ctx.fail("Usage: gloomberb portfolio position set <portfolio> <ticker> <shares|contracts> <avg-cost> [currency]");
|
|
941
|
-
}
|
|
1094
|
+
if (subaction !== "set") ctx.fail(POSITION_USAGE);
|
|
942
1095
|
const [, , ...rest] = args;
|
|
943
1096
|
const currency = rest[4];
|
|
944
|
-
if (rest.length < 4)
|
|
945
|
-
|
|
946
|
-
}
|
|
947
|
-
await setPositionCommand(rest[0]!, rest[1]!, rest[2]!, rest[3]!, currency, exchange, ctx);
|
|
1097
|
+
if (rest.length < 4) ctx.fail(POSITION_USAGE);
|
|
1098
|
+
await setPositionCommand(rest[0]!, rest[1]!, rest[2]!, rest[3]!, currency, exchange, ctx, { date: purchaseDate, costFx });
|
|
948
1099
|
return;
|
|
949
1100
|
}
|
|
950
1101
|
|