Desktop trading journal that turns broker CSVs into closed positions, charts, and analytics — built with Kotlin & Compose Multiplatform.
eJournal is a free, open-source, local-first desktop trading journal. Import your broker's transaction CSV and it matches every fill into round-trip trades (FIFO), computes realized P&L, and gives you a performance dashboard, a P&L calendar, a sortable trade log, and a per-trade candlestick chart with your entries and exits plotted on it. eJournal operates no application server; journal data stays local except for the documented market-data and broker requests made to services you use.
| Dashboard | P&L Calendar |
|---|---|
![]() |
![]() |
| Trade Log | Trade Analysis |
|---|---|
![]() |
![]() |
- Automatic FIFO trade matching — groups individual fills into round-trip trades; handles longs, shorts, scale-ins/outs, and position flips. Closed positions are recomputed from your transactions (never stored), so editing or deleting a transaction just works — no sync to manage.
- Performance dashboard — net & gross P&L, win rate, profit factor, expectancy, reward:risk, average win/loss, streaks, average hold time, top/worst trades, and an equity curve — all filterable by date range.
- P&L calendar — a month grid color-coded by daily profit/loss with Monday-through-Sunday weekly totals; click a day or week to drill into its trades.
- Per-trade analysis — candlestick chart (10-second eTape bars by default for sub-minute Positions when available, 1/5/15-minute intraday, or daily/weekly for swing trades) with your entries and exits plotted, a VWAP toggle, a transaction breakdown, and arrow-key navigation between trades.
- Sortable, filterable trade log — every closed position with entry/exit times & prices, shares, P&L, fees, and hold duration. Click through to the chart.
- Drag-and-drop import — drop a CSV, let eJournal auto-detect the broker, and preview parsed transactions before committing.
- Opt-in market data — Yahoo Finance and Alpaca requests are disabled by default and can be enabled in Settings; optional local eTape data remains available without network access.
- Direct Alpaca synchronization — read-only import of executed US stock fills from a Paper or Live trading account, including partial fills.
- Direct Moomoo OpenD synchronization — read-only, localhost-only import of live US stock orders, executions, and exact order fees.
- Local-first & private — everything lives in a single SQLite file under
~/.ejournal; API keys are stored with owner-only permissions on your machine. - Light / dark / system themes.
| Broker | Import format | Status |
|---|---|---|
| Alpaca | Trading API direct sync | ✅ Supported |
| TradeZero | TradeHistory CSV export (plus optional API sync) | ✅ Supported |
| Moomoo | OpenD direct sync or order history CSV export | ✅ Supported |
| Charles Schwab | Transaction history CSV (web "History" export) | ✅ Supported |
| E*TRADE | Transaction history CSV (classic DownloadTxnHistory.csv) |
✅ Supported |
| Robinhood | Account activity report CSV | ✅ Supported |
| Webull | Order history CSV (Webull_Orders_Records.csv) |
✅ Supported |
| Fidelity | Accounts "History" transaction CSV | ✅ Supported |
| Interactive Brokers | Activity Statement CSV (Trades section) | ✅ Supported |
| Tastytrade | Transactions CSV export | ✅ Supported |
| eToro | Account statement XLSX (Account Activity sheet) | ✅ Supported |
| Generic CSV | datetime, symbol, action, price, shares, fees |
✅ Manual fallback |
Every file is auto-detected on drop (eToro by its workbook sheets, the rest by their CSV header). Only buy/sell rows are imported; option legs and non-trade rows (dividends, interest, transfers, cancelled orders, eToro deposits/withdrawals/overnight fees) are detected and skipped, with the skipped count shown in the import summary. Most US web exports are date-only — same-day trades land at midnight — except Webull, Interactive Brokers, Tastytrade, and eToro, which carry intraday execution times. eToro charges no per-trade commission (its cost is the spread), so imported eToro trades carry no fees.
Don't see your broker? Use the Generic CSV importer with any file that has the columns above, or open an issue/PR to add a parser.
Public Windows artifacts do not bundle the Moomoo SDK or OpenD. The Moomoo integration speaks the documented JSON OpenD protocol directly and requires a separately installed OpenD service.
Grab the latest build from the Releases page:
- Windows —
.msiinstaller, or the portable.zip(both bundle their own Java runtime).
The Windows release is currently unsigned pending SignPath acceptance. The built-in updater checks GitHub at most once per day when enabled and opens the release page; it never downloads or installs an MSI automatically. Windows users can install or upgrade the MSI through normal Windows Apps settings, or replace the extracted portable folder.
Linux and macOS packaging is intentionally dormant while Windows signing eligibility is completed. The previous workflow is preserved under .github/workflows-disabled/ and makes no support claim.
eJournal is applying for SignPath Foundation sponsorship. Upon acceptance: Free code signing provided by SignPath.io, certificate by SignPath Foundation. Current releases must be treated as unsigned unless their release page explicitly states that the signing workflow completed successfully. See the full code signing policy and privacy policy.
- MSI installation: Open Windows Settings → Apps → Installed apps, find eJournal, and choose Uninstall.
- Portable build: Close eJournal and delete the extracted portable folder.
Uninstalling preserves journal data so a later installation can reuse it. To remove all local eJournal data and saved broker credentials, close the application and delete %USERPROFILE%\.ejournal. This cannot be undone.
Charts and unrealized P&L use OHLCV data fetched per imported trade — daily bars for swing trades, 1-minute bars for day trades, and optional native 10-second bars for sub-minute US stock day trades. Analysis defaults to 10-second bars for those Positions when complete data is available, then falls back to 1-minute bars. Three sources:
- Yahoo Finance (opt-in, no setup). Full daily history for daily bars after the user enables online market data or confirms a one-shot fetch.
- Alpaca (optional, free). The same Key ID and Secret Key unlock 1-minute market-data history and read-only trading-account synchronization. In Settings → Alpaca, select the matching Paper or Live trading environment; Paper and Live credentials are different. Create a free account at alpaca.markets (Paper/data keys need no funding), then follow steps 1 and 2 of Alpaca's guide.
- eTape (optional, local). When
~/.eTape/etape.dbexists, eJournal copies the complete available Position date of nativebars_10srows into its own database during market-data sync. Use Settings → Sync → Choose eTape database… for another file; the selected path is remembered. Imports are read-only, idempotent, and never synthesize bars from 1-minute data.
Alpaca synchronization only reads /v2/account, /v2/assets, and legacy /v2/account/activities/FILL plus FEE data. It never places, modifies, or cancels orders. Phase 1 imports US stock fills only; options and crypto fills are skipped. Each execution, including partial fills, remains a separate eJournal transaction. Alpaca's aggregate CAT, REG, and TAF Broker Fees are preserved and allocated as eJournal-derived Fee Allocation across matching Alpaca Transactions: REG by sell notional, TAF by sell shares, and CAT by all executed shares. Fee Dates use Alpaca's UTC activity boundary, debits become positive journal fees, fractional cents are retained, and unsupported, contaminated, malformed, or unmatched buckets remain unapplied with warnings. The first sync reconciles full history; later syncs replay an inclusive 95-day window so recent corrections replace prior allocations. Startup synchronization is opt-in per Portfolio and runs every eligible Portfolio before market-data synchronization; when it changes the selected Portfolio, the Dashboard refreshes after synchronization completes.
Keys are stored only on your machine in ~/.ejournal/credentials.json (owner-only permissions) and are sent to no one but Alpaca. Online market-data requests are off by default. Enable Settings → Sync → Allow automatic online market data for automatic requests, or use Fetch online data once for a one-shot confirmed request. Local eTape imports still run with online access disabled.
Direct Moomoo import requires the separately installed Moomoo OpenD application. eJournal does not bundle OpenD, OpenD native libraries, or the Moomoo SDK; it sends the minimum documented JSON protocol over localhost.
- Install and start OpenD using Moomoo's official instructions, sign in, and authorize the live trading account you want to journal.
- Keep OpenD on the same computer as eJournal. eJournal always connects to
127.0.0.1; remote hosts are not accepted. - In eJournal, add or edit a US Stocks portfolio, choose Moomoo, enter OpenD's listening port (
11111by default), and select Discover accounts. - Select an eligible account, save the portfolio, then use Import Transactions → Sync Moomoo OpenD. Startup synchronization is available but is opt-in for each portfolio.
Only active REAL, normal/non-master accounts authorized for the US market are selectable. The integration is read-only: it requests the account list, historical orders, historical deals, and exact order fees; it contains no order placement, modification, cancellation, or trade-unlock flow. OpenD credentials and passwords are never requested or stored. The selected account label, account ID, security firm, port, binding, and resumable sync cursor live in the portfolio settings database.
The first sync reads the last 365 days of live US stock history in oldest-first 90-day windows. Later syncs resume from the completed checkpoint with a three-day overlap, clamped to that same one-year boundary. Filled and partially-filled-cancelled orders are aggregated to one transaction per order using the earliest execution time and exact total fee. Options, combos, prediction contracts, other markets, unsupported sides, zero fills, and malformed rows are skipped and reported.
eJournal is a Kotlin Multiplatform project targeting Desktop (JVM only). Building requires JDK 25 (the Gradle toolchain resolves it automatically via foojay).
./gradlew :desktopApp:run # run the app
./gradlew :desktopApp:hotRun --auto # run with Compose hot reload
./gradlew :shared:jvmTest # run all tests
./gradlew build # full build
./gradlew :desktopApp:packageMsi -PappVersion=1.2.3 -PofficialRelease=true -Pdistribution=msi # WindowsThe public Windows package is built on GitHub-hosted Windows runners by the release workflow. The archived Linux/macOS workflow is not active.
Run a single JUnit test class or method with --tests:
./gradlew :shared:jvmTest --tests "io.earlisreal.ejournal.domain.FifoMatcherTest"
./gradlew :shared:jvmTest --tests "io.earlisreal.ejournal.domain.FifoMatcherTest.partial fill across multiple lots"Almost all code lives in shared/ (UI + business logic); desktopApp/ is a thin launcher. Contributor architecture and repository conventions are in AGENTS.md.
Learn more about Kotlin Multiplatform and Compose Multiplatform.
Released under the MIT License.



