tj — a trading journal that tells you what your mistakes cost
A single-binary command-line trading journal. It records trades, checks them
against rules you define, and answers one question a spreadsheet cannot:
in dollars, what has breaking your own rules cost you?
$ tj stats --by compliance
RULES TRADES P&L WIN% EXPECTANCY AVG R AVG HOLD
Followed 3 +$1,353.00 66.7% +$451.00 +2.14R 31m │██████
Broken 5 -$2,011.60 0.0% -$402.32 -1.01R 18m ███████│
Breaking the rules has cost $2,011.60 across 5 trade(s).
Per trade: +$451.00 clean, -$402.32 broken.
Built to replace a multi-sheet Excel tracker. Along the way it found four bugs
in that spreadsheet, each of which had been quietly corrupting the numbers it
was trusted for — see DECISIONS.md.
Install
go install github.com/henryikoh/trading-journal/cmd/tj@latest
Or build it:
git clone https://github.com/henryikoh/trading-journal
cd trading-journal
go build -o tj ./cmd/tj
No CGO, no runtime dependencies. CGO_ENABLED=0 go build gives a static binary
you can copy to a server.
The journal lives in a single SQLite file at ~/.tj/journal.db. Nothing else is
required. Point --db at a postgres:// URL if you would rather it lived on a
server.
Quick start
tj account new --name "My Account" --balance 10000 --instrument XAUUSD
tj open # asks for everything; no flags needed
tj close # lists what is open, asks for the exit
tj stats # what it all came to
The daily loop
tj today # briefing: the session timeline, your allowance, open positions
tj open # log a trade
tj close # close one
tj note "..." # the day's reflection
tj today opens with the trading day drawn as a bar in your own timezone, then
your remaining trade allowance at a size you cannot skim past:
6a 9a 12p 3p 6p 9p 12a 3a
····························································████████████
▲
1:07 am
██ Asia 2:00 am-6:00 am ░░ New York 2:00 pm-5:00 pm (Mon-Fri)
███ ███ ┃ ┃ ░░░ 2 of 4 left
win los next
Trades past the limit are drawn as ╳╳╳ and keep accumulating, because the
point is to make overtrading visible while it is happening rather than in a
review a week later.
What makes it different
It never guesses
Grade and emotional state are blank if you do not give them, not defaulted.
Imported trades with no stop have no R multiple, not zero. A trade the rule
engine has never examined is reported as unchecked, not as clean. Every figure
on screen is either measured or absent.
The checklist is half derived
Four of the pre-trade checks are facts the journal already holds — whether a
session is open, whether the trade limit is spent, whether the risk budget
covers the trade, whether anything else is blocking. --checklist all cannot
assert them. Only what a human can know is asked.
PRE-TRADE CHECK
✓ Inside the trading window New York
✓ Within the trade limit 0 of 4 used
✗ Within the daily risk $12.40 left of $250.00
✓ Status allows the trade nothing else blocking
Rules are recorded, never enforced
A journal that refuses entries gets bypassed — the trade goes into the broker
directly and the log goes blind. So it logs the trade and records the breach.
The value is in --by compliance being able to price the habit later.
Your systems are yours
A playbook is one setup's checks, in the order you work through them:
tj playbook new "Range Fade" \
--check "Price at the range boundary" \
--check "Rejection wick into the level" \
--check "Volume falling into the edge"
Each trade stores the questions it was actually asked, so editing a playbook
never rewrites history. tj stats --by playbook then answers which of your
systems makes money.
Commands
Trading
|
|
tj open |
log a trade — asks for everything, or takes flags |
tj close [n] |
close a trade, or list what is open |
tj preview |
size a trade without logging it |
tj edit <n> |
revise the notes or grade |
tj delete <n> |
remove a mistyped trade |
Review
|
|
tj status |
balances, risk budget, whether you may trade |
tj today |
the day briefing |
tj portfolio |
every active account, with capital allocation |
tj show <n> |
one trade in full, including its checklist at entry |
tj log |
the trade log, filterable |
tj week |
this week, a bar per day |
tj stats [--by …] |
performance, broken down eleven ways |
tj streak |
days in a row with every rule kept |
tj export |
the trade log as CSV |
Setup and money
|
|
tj account list | new | show | edit | use |
|
tj account archive | close | reopen |
accounts holding trades are never deleted |
tj playbook |
your systems and their checks |
tj instruments |
what each contract is worth |
tj deposit | withdraw |
funding and payouts, each removable if mistyped |
tj import <file> |
load a broker statement or a spreadsheet |
tj recheck |
judge trades the rule engine never saw |
tj config |
journal path, timezone, 12- or 24-hour clock |
tj help <command> explains any of them.
Breakdowns
tj stats --by <what>:
|
|
compliance |
what following the rules earns against what breaking them costs |
duration |
hold time — where the disposition effect shows up |
day |
each trading day against your daily trade limit |
index |
1st, 2nd, 3rd trade of the day, banded past the limit |
checklist |
which pre-trade item is expensive to skip |
playbook |
which of your systems actually makes money |
session · grade · emotional · hour · account |
|
Importing
tj import statement.html -a my-account --tz America/New_York --dry-run
Reads CSV, TSV, and the HTML reports MT4/MT5 produce (including UTF-16). It also
reads a hand-kept spreadsheet: headings are normalised before matching, so
P&L $, Profit (USD) and pnl are one column, and values keep their meaning
with the currency symbols stripped.
- The broker's profit is authoritative — it already includes spread,
commission and swap, which prices alone cannot reproduce.
- Direction is taken in order of trustworthiness: the broker's side column,
then the exit price against the realised profit, then the stop. That middle
rule matters — a hand-kept sheet gets the stop backwards more often than you
would think, and every disagreement is reported.
- Rows are keyed on the broker's ticket, or a stable id derived from the trade
when there is none, so re-importing a corrected file updates rather than
duplicates.
- Imported trades are put through the rule engine afterwards, because a trade
nobody examined is not a clean one.
--tz matters: statement timestamps carry no timezone, and reading them in the
wrong one files every trade under the wrong session.
Two clocks, and a trading day that is not a date
Session windows belong to the market (America/New_York). Every time on
screen is rendered in yours. They are separate because you are probably not
in New York.
The trading day is not the calendar date either. It opens with New York and
closes when the Asia session that started the previous evening ends — so a fill
at 00:20 belongs to the day that began the morning before. Read from Lagos, the
day rolls over at 6:00 am local, and shifts to 7:00 am when New York leaves
daylight saving, because the sessions are anchored to the market rather than to
a wall clock.
Architecture
cmd/tj/ the CLI
internal/
domain/ types: Account, Trade, Instrument, Checklist, Playbook
calc/ the maths — pure, no I/O, no clock
risk/ budgets, gating status, violation detection
prop/ drawdown, consistency ceiling, payout eligibility
store/ SQLite and PostgreSQL from one set of queries
journal/ service layer: loads state, calls the engines, builds views
tui/ terminal rendering
config/ journal path, active account, display preferences
calc, risk and prop are pure functions over data the service layer
supplies. Nothing in them opens a database or reads a clock, which is why they
can be tested against real historical fills with known broker-reported P&L.
All derived values are produced in journal, so the CLI and any future HTTP API
cannot disagree about a number.
Dependencies, four, deliberately:
modernc.org/sqlite pure-Go SQLite — CGO_ENABLED=0 gives a static binary
github.com/lib/pq PostgreSQL
github.com/google/uuid
golang.org/x/term raw mode for the arrow-key pickers
Testing
go test ./...
105 test functions. The calculation tests use real historical fills with
known broker-reported P&L, so a regression fails against reality rather than
against an assumption.
Status
Working and in daily use: accounts, the calculation engine, risk and prop rules,
playbooks, storage, the CLI, and importing.
Not built: an HTTP API and web dashboard. The service layer already returns the
structs an API would serialise, so what is missing is transport and auth.
Open questions are listed at the end of DECISIONS.md, including
one about the daily risk formula that is still under review.
A word of caution
This tool measures your trading. It does not improve it, predict anything, or
know whether a setup is good.
Nothing here is financial advice. It ships with no warranty of any kind —
see the licence. If a number matters to you, check it against your
broker statement. A journal is only ever as honest as what you put into it.
Licence
MIT.