This guide enumerates the actual commands and flags trader (the CLI in
cmd/trader) currently supports, verified directly against --help output
from a real build — not aspirational. Trader is under active development
(Project Status); commands and flags will
change as milestones land. See the
Developer's Guide for the packages behind each
command.
go build -o trader ./cmd/traderEvery command accepts these, inherited from the root command:
| Flag | Default | Meaning |
|---|---|---|
--log-format |
text |
text or json |
--log-level |
INFO |
DEBUG, INFO, WARN, or ERROR |
--log-output |
stderr |
stderr, stdout, or a file path |
--version, -v |
— | print the version and exit (see Versioning below) |
Trader's version is derived automatically from git tags at build time (ADR-064) — no source file is bumped by hand before a release.
trader --version/trader -v prints the compact form, e.g. trader version v0.3.0 for a build made exactly at tag v0.3.0, or trader version v0.3.0-12-gabc1234-dirty for a development build 12 commits
past that tag with uncommitted local changes.
trader version prints a fuller, multi-line report:
trader v0.3.0
commit: abc1234def012
commit-time: 2026-09-15T18:00:00Z
(commit-time is that commit's own timestamp, not when this
particular binary happened to be compiled — Trader records no
separate build-wall-clock timestamp.)
Both forms require a build made via make build/make install (which
inject git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]*' --always --dirty output at build time — restricted to Trader's own release-tag
namespace, so an unrelated repository tag can never become Trader's
own reported version) to report the exact git-describe version. A
plain go build/go install ./cmd/trader run outside make, from a
local git checkout, instead falls back to Go's own module-version
inference (already a real, usable value on a modern toolchain) or,
failing that, a devel+<revision> placeholder — still traceable to
the exact commit, just not necessarily the exact git-describe string.
A version-qualified module install (go install .../trader@v0.3.0)
reports that exact version alone, with no commit info, since Go does
not stamp VCS metadata for that install form. See
version's own
doc comment for the complete precedence/fallback chain.
Trader follows semantic versioning while remaining at v0 (ADR-011,
ADR-064 in docs/arch/adr-decisions.org);
see CONTRIBUTING.org's "Releasing" section for
how a version is tagged and released.
trader
├── data historical market-data commands
│ ├── bars read canonical historical bars
│ ├── build build and publish canonical data from raw data
│ ├── coverage report canonical/raw coverage and gaps
│ ├── plan report the work required to make a dataset available
│ ├── sync acquire raw data required to make a dataset available
│ └── update plan, sync, and build a dataset in one step
├── broker simulated broker account inspection and order submission
│ ├── accounts list the simulated broker's accounts
│ ├── snapshot show the simulated account's current snapshot
│ └── submit submit an order to the simulated broker
├── execution execution/risk pipeline inspection and order submission
│ ├── evaluate size, plan, and risk-evaluate an intent (no submission)
│ └── submit size, plan, risk-evaluate, and submit an intent
├── backtest run backtests and inspect their results
│ ├── run run a backtest and render/persist its result
│ └── show render a previously run backtest's persisted result
└── version print Trader's version and build metadata (see Versioning above)
trader completion also exists (standard Cobra shell-completion
boilerplate) and isn't covered further here.
internal/marketdata.Manager's CLI surface: query canonical bars, inspect coverage,
and plan/sync/build/update a dataset. Every data subcommand takes the same
two positional arguments and shares the same flag set.
Usage: trader data <subcommand> INSTRUMENT INTERVAL --from ... --to ...
INSTRUMENT is a plain symbol. For the default oanda provider (or any FX
provider) it is a 6-letter FX pair, e.g. EURUSD. For a non-FX provider such
as alpaca it is an equity or ETF ticker, e.g. AAPL or SPY — --exchange
and --kind are then both required, since a bare ticker does not name
its own listing exchange or asset kind the way an FX pair's symbol does.
INTERVAL is one of the values listed under backtest run below.
| Flag | Default | Meaning |
|---|---|---|
--from |
— | range start (YYYY-MM-DD or RFC3339), required |
--to |
— | range end (YYYY-MM-DD or RFC3339), required |
--format |
table |
table or json |
--provider |
oanda |
canonical dataset provider name (e.g. oanda, alpaca) |
--raw-root |
$XDG_DATA_HOME/trader/raw/<provider> |
raw provider archive root |
--store-root |
$XDG_DATA_HOME/trader/data |
canonical data store root |
--oanda-base-url |
— | OANDA API base URL; required only for sync/update |
--alpaca-base-url |
https://data.alpaca.markets |
Alpaca Market Data API base URL; only used with --provider alpaca |
--exchange |
— | listing exchange (e.g. ARCA, NASDAQ); required for a non-FX provider |
--kind |
— | equity or etf; required for a non-FX provider |
The OANDA API token itself is never a flag — set the TRADER_OANDA_TOKEN
environment variable instead. Likewise, Alpaca's key ID and secret key are
never flags — set TRADER_ALPACA_KEY_ID and TRADER_ALPACA_SECRET_KEY
instead. Neither provider's secret belongs in shell history or a process
command line.
Reads canonical historical bars for the given range.
trader data bars EURUSD H1 --from 2024-01-01 --to 2024-02-01Reports what work (if any) is required to make the requested dataset available — read-only, performs no acquisition or building.
Acquires raw provider data for the requested range. Requires
--oanda-base-url and TRADER_OANDA_TOKEN for the default oanda
provider, or TRADER_ALPACA_KEY_ID/TRADER_ALPACA_SECRET_KEY (plus
--exchange/--kind) for --provider alpaca:
trader data sync SPY D1 --provider alpaca --exchange ARCA --kind etf \
--from 2024-01-01 --to 2024-02-01Builds and publishes canonical data from raw data already present under
--raw-root — never fetches from a live provider.
Runs plan, then sync, then build, as each step actually requires — the one-command path to "make sure this dataset is current."
Reports canonical/raw coverage and any gaps for the dataset over the given range.
Inspect and submit orders against a fresh, in-memory simulated broker.
Every invocation builds a new simulator; nothing persists between
separate trader invocations.
| Flag | Default | Meaning |
|---|---|---|
--account-id |
freshly generated | account id to use |
--currency |
USD |
account currency |
--starting-cash |
10000 |
starting account cash amount |
Lists the simulated broker's accounts. --format table\|json.
Shows the simulated account's current snapshot (equity, positions, open
orders). --format table\|json.
Submits one order directly to the simulated broker (no sizing or risk
evaluation — see trader execution submit for that).
| Flag | Default | Meaning |
|---|---|---|
--symbol |
— | instrument symbol, e.g. EURUSD, required |
--side |
— | buy or sell, required |
--quantity |
— | order quantity, required |
--type |
market |
market, limit, stop, or stop-limit |
--price |
— | fill price, required for --type market |
--limit-price |
— | required for --type limit or stop-limit |
--stop-price |
— | required for --type stop or stop-limit |
--tif |
GTC |
time in force: GTC, DAY, IOC, or FOK |
--tick-size |
0.00001 |
simulator tick size |
--quantity-increment |
1 |
simulator quantity increment |
--multiplier |
1 |
simulator contract multiplier |
--format |
table |
table or json |
trader broker submit --symbol EURUSD --side buy --quantity 1000 \
--type market --price 1.10050Drives one order.Intent through the real sizing → planning → risk →
(optionally) submission pipeline (pipeline.Pipeline) against a fresh
simulated broker — the same path a backtest or a future live runtime uses.
Shares broker's --account-id/--currency/--starting-cash flags.
Sizes, plans, and risk-evaluates an intent without submitting it.
| Flag | Default | Meaning |
|---|---|---|
--symbol |
— | instrument symbol, required |
--side |
— | buy or sell, required |
--adverse-distance |
— | adverse price distance used for sizing, required |
--risk-fraction |
0.01 |
fraction of account equity to risk (1%) |
--reference-price |
— | valuation price for value-based risk rules (optional) |
--tick-size |
0.00001 |
simulator tick size |
--quantity-increment |
1 |
simulator quantity increment |
--multiplier |
1 |
simulator contract multiplier |
--format |
table |
table or json |
Same as evaluate, plus an actual submission if risk approves. Adds:
| Flag | Default | Meaning |
|---|---|---|
--price |
— | fill price for the resulting market order, required |
trader execution submit --symbol EURUSD --side buy \
--adverse-distance 0.0050 --risk-fraction 0.01 --price 1.10050Runs a backtest over the M5 application service (service/backtest) and
persists/renders its result. Canonical market data must already exist
under --data-store-root/--data-raw-root (via trader data build /
trader data sync) — run never syncs from a live provider itself.
There are three strategy paths:
- Without
--config/--strategy-exec: a provisional demo strategy — one buy-and-hold entry per instrument's first bar.--symbolmay be repeated for a multi-instrument run (one shared account/pipeline, not a per-symbol engine). - With
--config: an in-process strategy selected bystrategy.namein the YAML file (see below). Any explicit flag still overrides its corresponding config-file value. - With
--strategy-exec: an out-of-tree strategy executable, launched and driven over Strategy Protocol v1 — see External strategies below. Mutually exclusive with--config.
| Flag | Default | Meaning |
|---|---|---|
--symbol |
— | instrument symbol, repeatable, required (or via --config) |
--interval |
H1 |
M1, H1, H4, D1, or W1 |
--from |
— | replay range start, required (or via --config) |
--to |
— | replay range end, required (or via --config) |
--currency |
USD |
account currency |
--starting-cash |
10000 |
starting account cash amount |
--risk-fraction |
0.01 |
fraction of account equity to risk |
--adverse-distance |
— | adverse price distance for sizing, required (or via --config) |
--warmup-bars |
0 |
warm-up bars before the demo strategy may trade (ignored with --config) |
--data-raw-root |
— | raw archive root (required, or supplied by backtest.data_raw_root) |
--data-store-root |
/srv/trading/data/canonical* |
canonical data store root; an explicit empty value opts into a fresh temp dir per run |
--provider |
oanda |
market data provider name; backtest.provider in --config may supply it |
--config |
— | YAML file supplying backtest/strategy parameters (see below) |
--strategy-name |
— | in-process strategy name selected by --config |
--fast-period |
— | EMA fast period for ema-cross |
--slow-period |
— | EMA slow period for ema-cross |
--allowed-side |
both |
restrict ema-cross: both, long-only, or short-only |
--strategy-exec |
— | path to an out-of-tree strategy executable; mutually exclusive with --config |
--strategy-args |
— | extra argument passed to --strategy-exec's own executable, unmodified; repeatable |
--strategy-config |
— | path to a config file for --strategy-exec's own executable (see below) |
--journal |
— | optional path to write a durable JSONL audit trail; path must not already exist |
--output-dir |
./backtest-runs |
where run snapshots are written / show reads from |
--format |
table |
table, json, or org |
* The /srv/trading/data/canonical default is this repository's own local
operational choice (issue #268) — a fresh clone on another machine should
pass --data-store-root explicitly, or rely on the automatic temporary-
directory fallback by passing an explicit empty value.
trader backtest run \
--symbol EURUSD --interval H1 --from 2024-01-01 --to 2024-06-01 \
--starting-cash 10000 --risk-fraction 0.01 --adverse-distance 0.0050 \
--data-raw-root /path/to/raw/oanda --format tablebacktest:
symbol: EURUSD # required
interval: H1 # default H1
from: 2015-01-01T00:00:00Z # required
to: 2025-01-01T00:00:00Z # required
currency: USD # default USD
starting_capital: 10000 # default 10000
risk_fraction: 0.01 # default 0.01
adverse_distance: 0.0050 # required
data_raw_root: /path/to/raw/oanda # required
data_store_root: /path/to/canonical # default /srv/trading/data/canonical
provider: oanda # default oanda; e.g. stooq
strategy:
name: buy-and-hold # registered in-process strategy; also supports ema-cross
fast_period: 20 # used when name is ema-cross
slow_period: 50 # used when name is ema-cross
allowed_side: both # used when name is ema-crossPrecedence for each of the fields shown above (the ones with a config:
tag backing them — see Environment Variables)
is: explicit CLI flag > --config file value > TRADER_BACKTEST_*/
TRADER_STRATEGY_* environment variable > the default shown above.
--journal, --output-dir, --format,
and --warmup-bars are plain CLI flags with no --config/environment-
variable backing at all — see the flag table above for which is which.
--strategy-exec runs an out-of-tree strategy executable instead of an
in-tree one, launched by trader itself and driven over Strategy Protocol
v1 — a gRPC protocol over a Unix-domain socket (ADR-062, ADR-063). The
executable's own Describe() — not --symbol/--interval — determines
the actual replay universe; --symbol/--interval only control what
canonical data run publishes beforehand, which must still cover whatever
the executable will request.
Import sdk and implement its Strategy
interface (Describe/Start/OnBar, plus the optional FillHandler
capability). sdk.Serve(yourStrategy) is normally the entire body
of main():
func main() {
if err := sdk.Serve(NewMyStrategy(cfg)); err != nil {
log.Fatal(err)
}
}See examples/sdk-minimal for
the smallest complete, compiling example, and
examples/sma-long-hold for a real,
non-trivial one (SMA/indicator state, a ratcheting protective stop,
decision-evidence signals) that is proven byte-for-byte equivalent to its
in-tree counterpart, strategy/smatrend, by
cmd/trader/backtest/sma_long_hold_equivalence_test.go.
An external strategy never receives a broker handle, never evaluates risk,
and never submits an order directly — it only describes intents, exactly
like an in-tree strategy.Strategy. sdk itself, and every
strategy built on it, is architecturally barred from importing Trader's
strategy, backtest, service, cmd, adapters, broker, execution,
risk, or pipeline packages.
go build -o /tmp/my-strategy ./cmd/my-strategy
trader backtest run \
--strategy-exec /tmp/my-strategy \
--strategy-config my-strategy.json \
--symbol EURUSD --interval H1 --from 2024-01-01 --to 2024-06-01 \
--adverse-distance 0.0050 \
--data-raw-root /path/to/raw/oanda--strategy-config's path is forwarded to the child process via the
TRADER_STRATEGY_CONFIG environment variable — a trader-owned
convention, not part of Strategy Protocol v1 itself, so a config file's
own schema is entirely the strategy author's choice (json.Unmarshal a
struct, parse YAML, whatever the strategy needs). --strategy-args passes
additional arguments straight through to the executable, unmodified.
trader launches the executable, creates a fresh Unix-domain socket per
run, and waits for it to complete Strategy Protocol v1's Handshake before
the backtest replay begins; a child that fails to start, or exits before
completing Handshake, fails the run immediately with a clear error rather
than hanging. The child's own environment is inherited from trader's own
process (PATH, HOME, credentials, etc.), not a stripped one. The
child's stderr is captured and logged as structured warning records under
external strategy stderr; its stdout is not touched. On success, trader
sends a normal-completion SessionEnd before terminating the process; on
any other exit path the process still receives SIGTERM, escalating to
SIGKILL after a grace period. A child that exits unexpectedly mid-run —
even between two strategy callbacks that would not otherwise have
surfaced the crash — is detected and reported as a run failure, never
silently treated as a successful backtest.
trader and the strategy negotiate a Strategy Protocol version and a
capability set (for example, whether the strategy implements
FillHandler) during Handshake. A version the host does not recognize, or
a capability the strategy's own guest-side runtime requires but the host's
accepted response omits, fails the Handshake explicitly — run reports
this as a clear startup error, never a silent degradation to a subset of
behavior.
Every external run's persisted report records unambiguous provenance
under run.strategy_parameters, alongside the strategy's own
run.strategy_name/run.strategy_version (from its Handshake
Descriptor, the same fields any in-tree strategy's manifest carries):
{
"mode": "external",
"strategy_name": "my-strategy",
"strategy_version": "1.0.0",
"protocol_version": "v1",
"transport": "unix",
"exec": "/tmp/my-strategy",
"exec_digest": "sha256:...",
"args": [],
"config": "/home/you/my-strategy.json",
"config_digest": "sha256:..."
}exec_digest/config_digest are content digests of the executable file
and the --strategy-config file itself, both computed immediately before
launch — they distinguish two different builds behind the identical
--strategy-exec path, or two different config files behind the identical
--strategy-config path (for example, the same path edited in place
between two runs), neither of which a path/name alone can. config_digest
is empty when --strategy-config is not given. The ephemeral Unix-domain
socket path trader generates for that one run is never recorded anywhere
in this provenance: it has no reproducibility meaning and is specific to
that single process's lifetime.
- Only a local executable form is supported (
--strategy-exec /path/to/binaryplus--strategy-args);unix:///grpc://remote endpoint forms are not implemented in v1. - Exactly one strategy executable per run; there is no multi-strategy or multi-process orchestration.
- No sandboxing beyond normal OS process isolation — an external strategy
executable runs with the same OS-level privileges as the
traderprocess that launches it (though never with broker/risk/execution access at the protocol level, per Strategy Protocol v1's own design). - Config-file schema is entirely the strategy author's own responsibility;
tradernever parses or validates it.
Renders a persisted run snapshot written by a prior run — no
recomputation, byte-identical to what run itself rendered.
| Flag | Default | Meaning |
|---|---|---|
--output-dir |
./backtest-runs |
directory the run's snapshot was written to |
--format |
table |
table, json, or org |
trader backtest show run_01HKK5WY00D5982ACAHT01Q80K --format orgNot every flag documented above is settable as an environment
variable. Only fields actually loaded through config.Load (visible
by their own config: struct tag in the command's source) get an
environment-variable form; every other flag is a plain Cobra flag with
no config-file or environment-variable backing at all, no matter how
important it looks. Where a field is config-backed, the variable name
follows config's naming convention: prefix TRADER_, then the dotted
config path, uppercased with . replaced by _ — see the
config package doc comment for the full rule.
Concretely, per command:
trader backtest run— only the fields shown in the--configYAML reference above are env-backed:TRADER_BACKTEST_SYMBOL,_INTERVAL,_FROM,_TO,_CURRENCY,_STARTING_CAPITAL,_RISK_FRACTION,_ADVERSE_DISTANCE,_DATA_STORE_ROOT,_DATA_RAW_ROOT, andTRADER_STRATEGY_NAME,_FAST_PERIOD,_SLOW_PERIOD,_ALLOWED_SIDE.--provider,--journal,--output-dir,--format, and--warmup-barsare not env-backed — flag only.trader data(all subcommands) — only the parent command's persistent flags are env-backed:TRADER_STORE_ROOT,TRADER_RAW_ROOT,TRADER_ARCHIVE_ROOT,TRADER_PROVIDER,TRADER_OANDA_BASE_URL. Each leaf subcommand's own--from/--to/--formatare flag only.trader broker/trader execution— only the shared--starting-cash/--currency/--account-idflags are env-backed (TRADER_STARTING_CASH,TRADER_CURRENCY,TRADER_ACCOUNT_ID). Every leaf-specific flag (--symbol,--side,--quantity,--type,--price,--adverse-distance,--format, ...) is flag only.
These credentials are environment-only and have no flag at all:
| Variable | Meaning |
|---|---|
TRADER_OANDA_TOKEN |
OANDA API token, required for trader data sync/update |
TRADER_ALPACA_KEY_ID |
Alpaca API key ID, required (with the secret key below) for trader data sync/update --provider alpaca |
TRADER_ALPACA_SECRET_KEY |
Alpaca API secret key, required (with the key ID above) for trader data sync/update --provider alpaca |
Converts one native Stooq daily ZIP into Trader managed raw partitions and canonical D1 bars. The command imports the source before checking/building, so refreshed ZIP contents participate in the normal raw fingerprint and stale-data semantics.
bin/stq2bars SPY
bin/stq2bars SPY --from 2010-01-01 --to 2020-01-01
bin/stq2bars SPY --rebuildThe provider defaults to stooq when no provider is configured, and the range
defaults to the archive's actual first and last dates. Set TRADER_ARCHIVE_ROOT
or pass --archive-root to locate native ZIPs; --archive selects one ZIP
explicitly. SPY, QQQ, and AAPL have reference listing metadata. Other symbols
must provide --exchange and --kind. Extraction is temporary and source ZIPs
are never modified.