Skip to content

IBFlex: Import Flex Queries via HTTP API#5352

Open
lmb wants to merge 1 commit intoportfolio-performance:masterfrom
lmb:ibflex-web-service-update
Open

IBFlex: Import Flex Queries via HTTP API#5352
lmb wants to merge 1 commit intoportfolio-performance:masterfrom
lmb:ibflex-web-service-update

Conversation

@lmb
Copy link
Copy Markdown
Contributor

@lmb lmb commented Jan 19, 2026

This is an implementation of the Flex Web Service API to automatically fetch Flex Queries. Please see the individual commit descriptions for details.

Disclosure: I used Claude Code to draft this PR and have done manual code review on it. I think the code is OK, maybe a bit verbose. Please let me know if I've missed something or if I can somehow remove some code.

Fixes: #5246

@lmb
Copy link
Copy Markdown
Contributor Author

lmb commented Mar 7, 2026

@buchen is this something you would consider merging? Happy to try and slim this down more if it is too much to review.

@lmb lmb force-pushed the ibflex-web-service-update branch from d2bbc0c to 0bf0746 Compare March 8, 2026 11:08
@lmb lmb force-pushed the ibflex-web-service-update branch from 0bf0746 to 6af9bb5 Compare March 30, 2026 08:10
@buchen
Copy link
Copy Markdown
Member

buchen commented Mar 30, 2026

Sorry @lmb it was just not top on my mind (and a lot of other pull requests have run up).
I think there is nothing in principle against this. The only thing is that I avoid storing secrets in the XML file itself. Here I see it is attached to as a property.

Besides that, I haven't looked in detail but hope to be able to do so over public holidays.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Interactive Brokers Flex Web Service (HTTP API) integration to fetch Flex Query statements directly from IBKR and import new transactions into Portfolio Performance, including UI for configuring credentials and tracking last import time.

Changes:

  • Added IBFlexWebServiceClient to request and retrieve Flex statements via the IBKR Flex Web Service with retry/cancellation handling.
  • Extended IBFlexStatementExtractor to optionally filter extracted items by a cutoff date (used for incremental imports).
  • Added UI commands/handlers, configuration dialog, and persisted client properties (token/queryId/lastImportDate), plus supporting i18n strings and tests.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
name.abuchen.portfolio/src/name/abuchen/portfolio/messages.properties Adds core (non-UI) IB Flex Web Service error strings.
name.abuchen.portfolio/src/name/abuchen/portfolio/Messages.java Exposes new core message keys.
name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexWebServiceClient.java New HTTP client for IBKR Flex Web Service (send request + get statement) with retries and XML parsing.
name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java Adds optional import-date cutoff filtering to support incremental imports.
name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/messages.properties Adds UI strings for IB Flex configuration/import UX.
name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/Messages.java Exposes new UI message keys.
name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/OpenIBFlexSettingsHandler.java Adds handler to open IB Flex settings dialog for active client.
name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/handlers/ImportIBFlexWebServiceHandler.java Adds handler to fetch statement via web service, extract/import items, and persist last import date.
name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/dialogs/IBFlexConfigurationDialog.java New dialog/model to store token/query ID and show/clear last import date.
name.abuchen.portfolio.ui.tests/src/name/abuchen/portfolio/ui/dialogs/IBFlexModelTest.java Tests for IBFlexModel property persistence and lastImportDate round-tripping.
name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexWebServiceClientTest.java Unit tests for web service client parsing and basic fetch flow with mock executor.
name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java Adds tests validating cutoff-date filtering behavior.
name.abuchen.portfolio.bootstrap/OSGI-INF/l10n/bundle.properties Adds menu/command labels for the new Flex Web Service import/settings actions (EN).
name.abuchen.portfolio.bootstrap/OSGI-INF/l10n/bundle_de.properties Adds menu/command labels for the new Flex Web Service import/settings actions (DE).
name.abuchen.portfolio.bootstrap/Application.e4xmi Wires new menu entries, commands, and handlers into the Eclipse e4 application model.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +208 to +211
link.addListener(SWT.Selection, e -> DesktopAPI.browse(
"https://www.interactivebrokers.com/campus/ibkr-api-page/flex-web-service/")); //$NON-NLS-1$
}

Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The browse handler hardcodes the documentation URL instead of reusing the value shown to the user (Messages.IBFlexDocumentationLink). This can lead to inconsistencies (e.g., if the link text is translated/changed but the opened URL is not). Consider deriving the URL from the message (or storing the URL separately as a constant) and using the same source for both display and navigation.

Suggested change
link.addListener(SWT.Selection, e -> DesktopAPI.browse(
"https://www.interactivebrokers.com/campus/ibkr-api-page/flex-web-service/")); //$NON-NLS-1$
}
link.addListener(SWT.Selection, e -> DesktopAPI.browse(Messages.IBFlexDocumentationLink));
}

Copilot uses AI. Check for mistakes.
Comment on lines +134 to +136
String message = lastImportDate != null
? MessageFormat.format(Messages.IBFlexNoNewItemsExtracted, lastImportDate)
: Messages.IBFlexNoItemsExtracted;
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "no new items" info message formats {0} with a raw LocalDateTime (lastImportDate), which will render in ISO form (e.g., 2025-01-01T00:00) and may not match the UI’s usual date/time formatting. Consider formatting lastImportDate with the same formatter used elsewhere in the dialog (or a shared UI date-time format) before passing it to MessageFormat.

Copilot uses AI. Check for mistakes.
@lmb lmb force-pushed the ibflex-web-service-update branch from 6af9bb5 to 0bf497a Compare April 8, 2026 11:21
IBKR allows retrieving preconfigured Flex Queries via a rudimentary HTTP interface.
Doing so requires a query ID which identifies the Flex Query, and a secret token.
The secret token can be obtained from the web interface an expires after a while.

Add a new menu item which allows importing from such a Flex Web Service. This saves
users from logging in, manually selecting the query, executing it, etc.

The Flex Web Service always runs a Flex Query with a fixed date range configured when
creating the query. This means that we can't choose what date range to fetch.
The easiest thing for the user is to configure the query with the maximum duration
(currently 365 days). The downside of this is that it populates the import wizard with
a lot of duplicate items after the first import.

Filter extracted items to exclude ones we've likely already imported based on the
transaction date. The user can clear the date we're using for this via the settings
dialog.
@lmb lmb force-pushed the ibflex-web-service-update branch from 0bf497a to 938b288 Compare April 8, 2026 11:23
@lmb
Copy link
Copy Markdown
Contributor Author

lmb commented Apr 8, 2026

@buchen reworked this to use a new preference dialog to store tokens + query cutoff times. Also improved cancellation of the web fetch.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +145 to +156
private static TreeMap<String, String> loadLastImportDates(Client client)
{
var json = client.getProperty(PROPERTY_LAST_IMPORT_DATES);
if (json == null || json.isBlank())
return new TreeMap<>();

Type mapType = new TypeToken<TreeMap<String, String>>()
{
}.getType();
TreeMap<String, String> result = GSON.fromJson(json, mapType);
return result != null ? result : new TreeMap<>();
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IBFlexConfigurationDialog.loadLastImportDates() calls GSON.fromJson() without handling malformed/corrupted JSON stored in the client property. A JsonSyntaxException here would propagate into UI code and can break importing / preference pages. Consider catching runtime JSON parsing errors (e.g., JsonSyntaxException), logging once, and returning an empty TreeMap as a safe fallback.

Copilot uses AI. Check for mistakes.
Comment on lines +189 to +199
public static List<Credential> deserialize(String value)
{
if (value == null || value.isBlank())
return new ArrayList<>();

Type listType = new TypeToken<ArrayList<Credential>>()
{
}.getType();
List<Credential> result = GSON.fromJson(value, listType);
return result != null ? result : new ArrayList<>();
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IBFlexConfigurationDialog.deserialize() calls GSON.fromJson() without guarding against invalid JSON in the preference value (e.g., user-edited/corrupted workspace prefs). This can throw JsonSyntaxException during menu enablement/visibility evaluation and break the UI. Suggest catching JSON parsing exceptions, logging, and returning an empty list as a fallback.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +143
String message = lastImportDate != null
? MessageFormat.format(Messages.IBFlexNoNewItemsExtracted, lastImportDate)
: Messages.IBFlexNoItemsExtracted;
MessageDialog.openInformation(shell, Messages.LabelInfo, message);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user-facing message for “no new items” formats lastImportDate via MessageFormat with a raw LocalDateTime argument, which will render in ISO form (e.g., 2025-01-01T00:00) and is inconsistent with other UI date/time formatting. Consider formatting lastImportDate with the existing UI formatter (e.g., Values.DateTime.format(...)) before inserting it into the message.

Copilot uses AI. Check for mistakes.
Comment on lines +219 to +220
var dialog = new MessageDialog(shell, Messages.IBFlexFetching, null,
Messages.IBFlexQueryId, MessageDialog.QUESTION, 0, labels);
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectCredential() uses Messages.IBFlexFetching as the dialog title and Messages.IBFlexQueryId as the dialog message. This results in a confusing question dialog (title implies an in-progress fetch, message is just “Query ID”). Consider introducing a dedicated message like “Select which Flex Query to import” and using a stable title (e.g., the command name / “Interactive Brokers”).

Suggested change
var dialog = new MessageDialog(shell, Messages.IBFlexFetching, null,
Messages.IBFlexQueryId, MessageDialog.QUESTION, 0, labels);
var dialog = new MessageDialog(shell, "Interactive Brokers", null,
"Select which Flex Query to import", MessageDialog.QUESTION, 0, labels);

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +247
// Exponential backoff, capped at MAX_DELAY_MS
delay = Math.min(delay * 2, MAX_DELAY_MS);
}
}

throw new IBFlexException(ERROR_STATEMENT_GENERATING, Messages.IBFlexMsgErrorStatementNotReady);
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchStatementWithRetry() throws an IBFlexException with errorCode ERROR_STATEMENT_GENERATING (1019) when the local retry loop times out. Because isStatementGenerating() is derived from the errorCode, callers may treat a local timeout as a retriable “still generating” response and keep retrying indefinitely. Consider using a distinct local error code (or a dedicated flag/exception subtype) for the timeout case so isStatementGenerating() remains accurate.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

IB Flex: automatically fetch flex queries via web api

3 participants