Skip to content

Import XTB xStation .xlsx account workbooks - #46

Merged
ChristopherDownie merged 2 commits into
mainfrom
feat/xtb-xlsx-import
Oct 13, 2026
Merged

ChristopherDownie merged 2 commits into
mainfrom
feat/xtb-xlsx-import

Conversation

@ChristopherDownie

Copy link
Copy Markdown
Contributor

Fixes #42.

Builds on @zahei's contributed importer (first commit, authored by them): XTB exports one .xlsx per account; closed positions become an entry and an exit fill, open lots become entry fills keyed by XTB position id, and a later workbook that closes the position adds the exit. Cash operations are skipped. The .xlsx reader has no new dependency.

Review fixes (second commit)

Running the patch as submitted showed:

Found Fix
pnpm typecheck fails in packages/importers (no DOM typings for TextDecoder/DecompressionStream; strict index access on tradeToExecutions) DOM lib for the package (it already promises browser support); tradeToExecutions typed as an entry/exit pair
The patch's tests fail against its own samples and assert account numbers from real exports; the real workbooks also carry personal metadata Synthetic workbooks generated in tests in the layout of xStation exports (packages/importers/tests/xtb-fixture.ts); no account data is committed
A held lot followed by a later close with a different size (for example a partial close), or ms-level timing drift, silently left a phantom open position server/xtb-import.ts checks incoming XTB entries against stored holdings by position id: rounding-level differences (under 1 s, price within 0.1%) are the same lot and are not inserted again; a changed size or side is refused before any writes, with a message naming the position
Holdings lost their asset class: XTB leaves Category blank on lot rows Lots inherit the category of their ticker's rollup row
Clean workbooks reported 2–3 "skipped rows" (totals, rollups, the reducer note) Only unknown trade types are counted as skipped
The workbook was reduced in the browser and the server trusted the resulting text; the ZIP reader had no bounds or size limits The page uploads the .xlsx as base64 (encoding: "xlsx", 5 MB cap) and the server reads it; every ZIP offset is bounds-checked; ZIP64, multi-part, encrypted members and legacy .xls are refused with clear messages; entry count and expanded size are capped and inflation stops past a member's declared size

Verification

  • packages/importers/tests/xtb.test.ts (13 tests): closed position with account-currency result, holdings keyed by position id, later close reproducing the holding's entry exactly, fees, mixed accounts, non-XTB workbooks, and reader limits (stored vs deflated, .xls, truncated, declared-size overrun, oversize, encrypted).
  • apps/web/tests/xtb-import.test.ts (5 tests): base64 preview/commit and re-import dedupe; holding closed by a later workbook; rounding drift treated as the same lot; partial close refused with nothing saved; AI/mapping/oversized/non-workbook uploads refused.
  • apps/web/tests/xtb-import-ui.test.ts (2 tests): the page sends base64 with encoding: "xlsx" for preview and commit, and blocks AI parsing and oversized files before any request.
  • Both real workbooks attached to XTB broker .xlsx statement import (closed positions and open holdings) #42 parse correctly locally (not committed).
  • Full suite 586/586, typecheck, format, license check, package builds and production build pass locally.
  • Checked in the browser with synthetic workbooks: export A stores two open holdings; a later export B closes one as a +45 win, leaves the other open, and adds no duplicate.

Known limits

Partial closes are refused until a real partial-close export shows how XTB reports them. Prices stay in the instrument currency, so a cross-currency position's result is in the account currency but its entry notional is not (documented in docs/importers.md).

Note: this PR and #45 both touch api/import/route.ts and the README format count; whichever merges second needs a small rebase (the count becomes 16).

zahei and others added 2 commits October 13, 2026 18:10
XTB exports one workbook per account with Closed Positions, Open Positions
and Cash Operations sheets. Closed rows become an entry and an exit fill;
open lots become entry fills tagged with their XTB position id so a later
workbook that closes the position adds the exit. Cash operations are
skipped. Includes a dependency-free .xlsx reader (ZIP + worksheet XML).

Contributed in #42.
Review fixes on top of the contributed importer (#42):

- Typecheck: give the importers package DOM lib typings for TextDecoder and
  DecompressionStream, and type tradeToExecutions as an entry/exit pair
- Decode workbooks on the server: the page uploads the .xlsx as base64 with
  encoding "xlsx" (5 MB cap), so preview and commit parse the same
  server-side text and the reader stays out of the client bundle
- Bound the ZIP reader: check every offset, refuse ZIP64, multi-part,
  encrypted members and legacy .xls with a clear message, cap entries and
  expanded size, and stop inflating past a member's declared size
- Refuse a later close whose lot differs from the stored holding (for example
  a partial close) instead of booking a phantom open position; tolerate
  sub-second and rounding differences for the same lot
- Open lots inherit the category of their ticker's rollup row, so holdings
  keep their asset class when a later workbook closes them
- Count only unknown trade types as skipped; totals, rollups and notes are
  layout rows
- Replace the real-export fixtures with synthetic workbooks generated in the
  layout of xStation exports; no account data is committed

Fixes #42
@ChristopherDownie
ChristopherDownie merged commit 033eb99 into main Oct 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XTB broker .xlsx statement import (closed positions and open holdings)

2 participants