trader

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 27, 2026 License: BSD-2-Clause Imports: 34 Imported by: 0

README

Trader

A Go FX backtesting and live paper-trading engine with OANDA integration, a REST/WebUI, and Claude MCP tools.


Install

git clone https://github.com/rustyeddy/trader
cd trader
make build          # → bin/trader
make install        # install to $GOPATH/bin

Requires Go 1.22+. No C dependencies.


Quick Start

Backtest
# Run a pre-built config against cached historical data
trader backtest --config testdata/configs/eurusd-h1-2024-ema-cross.yml

# Run all regression configs and write reports
trader backtest regress --config testdata/configs/
Live Paper Trading
export OANDA_TOKEN=your-practice-api-token

# Dry-run: print resolved config and exit
trader live run --config testdata/configs/pulse-demo.yml --dry-run

# Run the pulse strategy against a practice account
trader live run --config testdata/configs/pulse-demo.yml
Web UI + REST API
trader serve                         # REST on :9999, live journal, embedded UI
trader serve --addr :8080            # custom port
trader serve --log-level debug

Open http://localhost:9999 for the dashboard.


CLI Commands

Command Description
trader backtest Run backtests against historical candles
trader backtest regress Batch regression: run all configs, write JSON + org reports
trader data sync Download ticks (Dukascopy) and build OHLC candles
trader data oanda Download candles from OANDA into the candle store
trader live run Run a live strategy against an OANDA practice/live account
trader live journal Subscribe to OANDA transaction stream and journal closed trades
trader order Place/close orders on a live OANDA account
trader serve Long-running daemon: REST API + live journal + embedded UI
trader replay Replay a dataset through the sim engine
trader mcp Expose trader as typed Claude tools over stdio (MCP protocol)

All commands accept --help.


Backtesting

Backtests are driven by YAML config files. See testdata/configs/ for a full library of examples.

# testdata/configs/eurusd-h1-2024-ema-cross.yml (excerpt)
defaults:
  capital: 10000
  risk_pct: 1.0
  data_dir: /data/candles

runs:
  - instrument: EUR_USD
    start_date: 2024-01-01
    end_date:   2024-12-31
    strategy:
      name: emacross
      fast: 9
      slow: 21

Results are printed to stdout and optionally written to reports/ as JSON.


Live Trading

Live trading uses OANDA's REST API. A practice account is free at oanda.com.

Authentication — set one of:

export OANDA_TOKEN=<your-token>        # env var
echo <token> > ~/.config/oanda/pat.txt # file fallback

Config (testdata/configs/pulse-demo.yml):

instrument: EUR_USD
env: practice
tick_interval: 60s
max_positions: 1
risk_pct: 0.1
max_units: 5000         # hard cap on position size

strategy:
  kind: pulse
  params:
    trade_every: 5      # open every N ticks
    hold_bars: 15       # close after N ticks
    side: long
    stop_pips: 20
    risk_pct: 0.1

The runner polls prices every tick_interval, calls the strategy, executes closes then opens, and logs every action. --log-level debug adds per-trade tick counts and unrealized P/L each bar.


Strategies

Strategy Description
pulse Mechanical open/close on fixed tick schedule — useful for pipeline testing
emacross EMA crossover (fast/slow)
emacrossadx EMA crossover filtered by ADX trend strength
donchian Donchian channel breakout
noop Does nothing — baseline / benchmark
fake Scripted actions for deterministic testing
lifecycle Exercises the full open → modify-stop → close lifecycle
tmpl Strategy template for new strategy development

Data Management

Historical data comes from two sources:

Dukascopy (tick data, free) — download and build candles:

trader data sync --instruments EUR_USD,GBP_USD --from 2022-01 --to 2024-12

OANDA (candles, requires token):

trader data oanda --instrument EUR_USD --granularity H1 --from 2024-01-01

Candle data is stored under $DATA_DIR (default /data/candles) in a hierarchy:

/data/candles/<source>/<INSTRUMENT>/<YYYY>/<MM>/

testdata/candles/ contains small fixtures used by unit tests — do not use for real backtests.


Architecture

The core backtest loop:

Config (YAML)
  → DataManager  (loads / caches OHLC candles)
  → Backtest     (iterates candles bar by bar)
  → Strategy     (returns StrategyPlan each bar)
  → Broker       (fills orders, emits Events)
  → Account      (updates equity, margin, P/L)
  → Journal      (records closed trades — CSV or SQLite)

Numeric types — all prices and money are fixed-point integers, never floats:

Type Scale Notes
Price (int32) 100,000 1.16177 → 116177
Money (int64) 1,000,000 avoids float rounding
Units 1 position size in micro-lots

Accounting invariants (must hold after every operation):

  • Equity = Balance + UnrealizedPL
  • FreeMargin = Equity − MarginUsed
  • BUY: open at ask, close at bid; SELL: open at bid, close at ask
  • Stop/take-profit evaluated on every bar (inclusive)
  • Forced liquidation when FreeMargin < 0

Testing

make test           # unit tests
make test-blackbox  # unit + REST API + MCP integration tests
make cover          # coverage report (stdout)
make cover-html     # coverage report (browser)

# Run a single test
go test -run TestName ./...

# Enable Dukascopy download tests (hits network)
TRADER_RUN_DUKASCOPY_TESTS=1 go test ./...

Every code change must ship with tests — see CLAUDE.md for conventions.


Project Layout

cmd/            CLI entry points (Cobra)
api/rest/       REST handlers
api/mcp/        Claude MCP tool server
brokers/oanda/  OANDA REST client
service/        Business logic (orders, live runner, journal)
strategies/     Strategy implementations
data/           Candle loading, Dukascopy parser
ui/             Embedded SvelteKit frontend
testdata/       Config fixtures and candle fixtures
ROADMAP.md      Planned features and known gaps

Roadmap

See ROADMAP.md for planned features including walk-forward testing, multi-instrument live runner, external/plugin strategies, and more.

Documentation

Overview

pkg/indicators/ema.go

Package indicators provides technical analysis indicators for trading

journal/journal.go

journal/csv.go

Package trader provides structured logging for the trader application using Go's standard log/slog library. It supports multiple concurrent output destinations (stdout, a log file, and syslog) and named module loggers so that log records can be filtered by subsystem (data, backtest, indicator, replay, …).

Typical usage:

// initialise once at startup (e.g. from main or cmd layer)
Setup(LogConfig{Level: "debug", Format: "text", File: "trader.log"})

// package-level helpers
Info("server started", "port", 8080)
Debug("tick received", "instrument", "EURUSD")

// module-scoped logger
logger := Module("data")
logger.Info("inventory built", "files", 42)

// or use the pre-wired module variables
Data.Info("download complete", "key", key)
Backtest.Warn("end of data reached")

Index

Constants

View Source
const (
	SourceDukascopy = "dukascopy"
	SourceOanda     = "oanda"
	SourceCandles   = "candles"
)
View Source
const (
	FillNone fillStatus = iota
	FillComplete
	FillPartial
	FillCanceled
	FillFailed
)
View Source
const (
	GapMinor      gapKind = "minor"
	GapWeekend    gapKind = "weekend"
	GapSuspicious gapKind = "suspicious"
)
View Source
const (
	EUR_USD symbol = "EUR_USD"
	GBP_USD symbol = "GBP_USD"
	USD_JPY symbol = "USD_JPY"
	USD_CHF symbol = "USD_CHF"
	AUD_USD symbol = "AUD_USD"
	USD_CAD symbol = "USD_CAD"
	NZD_USD symbol = "NZD_USD"
)
View Source
const (
	LotNone lotState = iota
	LotOpenRequested
	LotOpen
	LotCloseRequested
	LotClosed
)
View Source
const (
	PriceScale Scale6 = 100_000
	MoneyScale Scale7 = 1_000_000
)
View Source
const (
	OrderNone orderType = iota
	OrderMarket
	OrderLimit
	OrderStop
	OrderStopLimit
	OrderTrailingStop
)
View Source
const (
	OrderStatusNone orderStatus = iota
	OrderPending
	OrderAccepted
	OrderFilled
	OrderRejected
	OrderCanceled
)
View Source
const (
	CloseUnknown closeCause = iota
	CloseManual
	CloseStopLoss
	CloseTakeProfit
	CloseBrokerLiquidation
)
View Source
const (
	SecondInMS  timemilli = 1_000
	MinuteInSec Timestamp = 60
	MinuteInMS  timemilli = 60_000
	HourInSec   Timestamp = 3_600
	HourInMS    timemilli = 3_600_000
)
View Source
const TestDataDir = "testdata"

TestDataDir is the testdata directory path relative to workspace root.

Variables

View Source
var (

	// Pre-wired module loggers.  They are initialised to the default logger
	// in init() and remain valid across Setup calls.
	L            *slog.Logger
	Data         *slog.Logger
	BacktestLog  *slog.Logger
	IndicatorLog *slog.Logger
	Strat        *slog.Logger
	Replay       *slog.Logger
)
View Source
var DefaultStrategyPlan = StrategyPlan{
	Reason: "hold",
}
View Source
var ErrKeyNotFound = errors.New("Key not found")
View Source
var Instruments = map[string]*Instrument{
	"EURUSD": {
		Name:                "EURUSD",
		BaseCurrency:        "EUR",
		QuoteCurrency:       "USD",
		PipLocation:         -4,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20_000),
	},
	"GBPUSD": {
		Name:                "GBPUSD",
		BaseCurrency:        "GBP",
		QuoteCurrency:       "USD",
		PipLocation:         -4,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20000),
	},
	"USDJPY": {
		Name:                "USDJPY",
		BaseCurrency:        "USD",
		QuoteCurrency:       "JPY",
		PipLocation:         -2,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20_000),
	},
	"USDCHF": {
		Name:                "USDCHF",
		BaseCurrency:        "USD",
		QuoteCurrency:       "CHF",
		PipLocation:         -4,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20_000),
	},
	"AUDUSD": {
		Name:                "AUDUSD",
		BaseCurrency:        "AUD",
		QuoteCurrency:       "USD",
		PipLocation:         -4,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20_000),
	},
	"USDCAD": {
		Name:                "USDCAD",
		BaseCurrency:        "USD",
		QuoteCurrency:       "CAD",
		PipLocation:         -4,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20_000),
	},
	"NZDUSD": {
		Name:                "NZDUSD",
		BaseCurrency:        "NZD",
		QuoteCurrency:       "USD",
		PipLocation:         -4,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(20_000),
	},
	"XAUUSD": {
		Name:                "XAUUSD",
		BaseCurrency:        "XAU",
		QuoteCurrency:       "USD",
		PipLocation:         -2,
		TradeUnitsPrecision: 0,
		MinimumTradeSize:    1,
		MarginRate:          Rate(50_000),
	},
}
View Source
var Version = "dev"

Version is the current build version. Set at build time via:

go build -ldflags="-X github.com/rustyeddy/trader.Version=v1.2.3"

Functions

func BenchmarkSyntheticCandleGeneration

func BenchmarkSyntheticCandleGeneration(b *testing.B)

BenchmarkSyntheticCandleGeneration benchmarks how fast we can generate candles

func BenchmarkSyntheticCandleIteration

func BenchmarkSyntheticCandleIteration(b *testing.B)

BenchmarkSyntheticCandleIteration benchmarks iteration speed

func BenchmarkYearGeneration

func BenchmarkYearGeneration(b *testing.B)

BenchmarkYearGeneration benchmarks full year generation

func ClearEntries

func ClearEntries()

ClearEntries discards all entries held in the in-memory stack.

func Debug

func Debug(msg string, args ...any)

Debug logs at LevelDebug.

func Error

func Error(msg string, args ...any)

Error logs at LevelError.

func Fatal

func Fatal(msg string, args ...any)

Fatal logs at LevelError and terminates the process with os.Exit(1).

func FormatTradeOrg

func FormatTradeOrg(t TradeRecord) string

FormatTradeOrg renders a TradeRecord as an Org-mode block suitable for pasting into a journal. It purposely includes narrative placeholders (Thesis/Execution/Review) while keeping all structured facts in a PROPERTIES drawer for easy search.

func FormatTradesOrg

func FormatTradesOrg(trades []TradeRecord) string

FormatTradesOrg renders multiple trades separated by blank lines.

func GenerateSyntheticYearTestData

func GenerateSyntheticYearTestData(basedir string, instrument string, year int, timeframe Timeframe) ([]string, error)

GenerateSyntheticYearTestData generates a full year of synthetic test data.

func GetBoolParam

func GetBoolParam(m map[string]any, key string) (bool, bool, error)

GetBoolParam extracts a bool param, or returns ok=false if missing.

func GetFloat64Param

func GetFloat64Param(m map[string]any, key string) (float64, bool, error)

GetFloat64Param extracts a float64 param, or returns ok=false if missing.

func GetInt32Param

func GetInt32Param(m map[string]any, key string) (int32, bool, error)

GetInt32Param extracts an int32 param, or returns ok=false if missing.

func Info

func Info(msg string, args ...any)

Info logs at LevelInfo.

func InstrumentPositions

func InstrumentPositions(lb *LotBook) map[string]*Position

InstrumentPositions derives per-instrument Position from all open lots.

func IsForexMarketClosed

func IsForexMarketClosed(t time.Time) bool

IsForexMarketClosed is the exported form of isForexMarketClosed for use by sibling packages (e.g. data/dukascopy).

func Module

func Module(name string) *slog.Logger

Module returns a *slog.Logger pre-populated with the attribute "module"=name. The same logger is returned on subsequent calls with the same name.

func NewCSV

func NewCSV(tradesPath, equityPath string) (*csvJournal, error)

func NewDownloader

func NewDownloader() *downloader

func NewULID

func NewULID() string

New returns a ULID string (time-sortable identifier).

ULIDs are lexicographically sortable by generation time, which makes them ideal for journaling/trading records and SQLite indexes.

func NormalizeInstrument

func NormalizeInstrument(sym string) string

func PrintBacktest

func PrintBacktest(w io.Writer, r BacktestResult)

PrintBacktest writes a formatted backtest result to w. NOTE: this function is currently a stub; the print logic is commented out pending a BacktestResult restructure.

func PrintSummary

func PrintSummary(w io.Writer, s BacktestReportSummary)

PrintSummary writes a human-readable backtest report to w.

func RR

func RR(entry, stop, takeProfit float64) float64

RR returns the reward-to-risk ratio for a trade setup as a plain float. A ratio of 2.0 means the potential reward is twice the risk.

func RegisterStrategy

func RegisterStrategy(ctor StrategyConstructor, names ...string)

RegisterStrategy adds a strategy constructor under one or more names. Typically called from an implementation package's init() function. Multiple aliases are supported (e.g. "donchian", "donchian-breakout").

func RegisteredStrategies

func RegisteredStrategies() []string

RegisteredStrategies returns the sorted list of registered strategy names. Useful for help text and validation.

func SetDataDir

func SetDataDir(dir string)

SetDataDir overrides the global store's base directory. Call from main before any data operations.

func Setup

func Setup(cfg LogConfig) error

Setup initialises (or re-initialises) the logging system according to cfg. It is safe to call multiple times; subsequent calls replace the active handler and close previously opened sinks.

func StrategyBarIndex

func StrategyBarIndex(ctx context.Context) int

func StrategyGapBars

func StrategyGapBars(ctx context.Context) int

func StrategyInstrument

func StrategyInstrument(ctx context.Context) string

func String

func String(c candleTime) string

func SwapStore

func SwapStore(s *Store) (restore func())

SwapStore replaces the global Store with the given one and returns a function that restores the previous Store. Useful in tests for sibling packages that need to point the global at a temp directory.

func TestTraderTimeoutDetection

func TestTraderTimeoutDetection(t *testing.T)

TestTraderTimeoutDetection verifies we can detect infinite loops with timeouts. This ensures that if the infinite loop still exists, the test will fail decisively.

func TestTraderWithDifferentSeeds

func TestTraderWithDifferentSeeds(t *testing.T)

TestTraderWithDifferentSeeds verifies reproducibility

func TestTraderWithHighVolatilitySynthetic

func TestTraderWithHighVolatilitySynthetic(t *testing.T)

TestTraderWithHighVolatilitySynthetic tests with extreme volatility to ensure the trader handles edge cases.

func TestTraderWithYearOfSyntheticDaily

func TestTraderWithYearOfSyntheticDaily(t *testing.T)

TestTraderWithYearOfSyntheticDaily tests with daily data (fewer candles).

func TestTraderWithYearOfSyntheticHourly

func TestTraderWithYearOfSyntheticHourly(t *testing.T)

TestTraderWithYearOfSyntheticHourly tests that trader can process a full year of hourly candles without infinite loops. This is useful for reproducible testing of the infinite loop issue on CI/CD systems.

func Warn

func Warn(msg string, args ...any)

Warn logs at LevelWarn.

func WriteOrgIndex

func WriteOrgIndex(w io.Writer, summaries []BacktestReportSummary)

WriteOrgIndex writes a single comparison table across all summaries to w.

func WriteOrgReport

func WriteOrgReport(w io.Writer, s BacktestReportSummary)

WriteOrgReport writes a full per-run org-mode report to w.

Types

type ADX

type ADX struct {
	// contains filtered or unexported fields
}

ADX computes the Average Directional Index (Wilder) over candle OHLC.

Pricing note: - trader.Candle prices are scaled integers. - ADX outputs float64 (0..100-ish) and uses float math internally. - Pass the same scale used to build your CandleSet (e.g. 1_000_000 for Dukascopy).

Readiness / warmup: - ADX needs:

  1. N periods to build initial smoothed TR/+DM/-DM
  2. N DX values to seed the initial ADX (average of first N DX)

- Practically, that's about 2N "periods" (differences between candles), plus the first candle. - We expose Warmup() as 2N to keep it simple/consistent with your other indicators.

func NewADX

func NewADX(period int, scale Scale6) *ADX

func (*ADX) DX

func (a *ADX) DX() float64

func (*ADX) Float64

func (a *ADX) Float64() float64

func (*ADX) MinusDI

func (a *ADX) MinusDI() float64

func (*ADX) Name

func (a *ADX) Name() string

func (*ADX) PlusDI

func (a *ADX) PlusDI() float64

Optional: expose DI values if you want them in strategies/debugging.

func (*ADX) Ready

func (a *ADX) Ready() bool

func (*ADX) Reset

func (a *ADX) Reset()

func (*ADX) Update

func (a *ADX) Update(c Candle)

Update consumes the next closed candle.

func (*ADX) Warmup

func (a *ADX) Warmup() int

type ATR

type ATR struct {
	// contains filtered or unexported fields
}

ATR computes the Average True Range (Wilder) over candle OHLC.

Warmup: needs N candle-to-candle periods (N+1 candles) before Ready() is true. Output: Float64() returns ATR in price units (same float scale as EMA).

func NewATR

func NewATR(period int, scale Scale6) *ATR

func (*ATR) Float64

func (a *ATR) Float64() float64

func (*ATR) Name

func (a *ATR) Name() string

func (*ATR) Period

func (a *ATR) Period() int

func (*ATR) Ready

func (a *ATR) Ready() bool

func (*ATR) Reset

func (a *ATR) Reset()

func (*ATR) Update

func (a *ATR) Update(c Candle)

func (*ATR) Warmup

func (a *ATR) Warmup() int

type Account

type Account struct {
	ID          string
	Name        string
	Currency    string // account denomination (e.g. "USD")
	Balance     Money  // realised cash; updated on every close
	Equity      Money  // Balance + sum of unrealised P/L across open lots
	MarginUsed  Money  // sum of margin reserved by open lots
	FreeMargin  Money  // Equity − MarginUsed
	MarginLevel Money  // Equity / MarginUsed × MoneyScale (0 when flat)
	RiskPct     Rate   // fraction of equity risked per trade (e.g. 0.005 = 0.5 %)

	Lots    LotBook
	Matcher CloseMatcher
	Trades  []*Trade // closed trades, appended by CloseLot
}

Account holds the financial state for a single trading account. All monetary values are scaled integers (Money = int64 × MoneyScale). Invariants that must hold after every operation:

  • Equity = Balance + UnrealizedPL
  • FreeMargin = Equity − MarginUsed

func NewAccount

func NewAccount(name string, deposit Money) *Account

NewAccount creates an Account with the given name and opening deposit. Currency defaults to "USD"; RiskPct defaults to 0.5 %; Matcher to FIFO.

func StrategyAccount

func StrategyAccount(ctx context.Context) *Account

func (*Account) AddLot

func (act *Account) AddLot(ctx context.Context, lot *Lot) error

AddLot registers a newly opened lot with the account and immediately revalues all open positions at the lot's entry price.

func (*Account) CloseLot

func (act *Account) CloseLot(lot *Lot, trade *Trade) error

CloseLot realizes P/L for the lot, appends the trade to the account's Trades history, removes the lot from the LotBook, and revalues remaining open lots at the exit price.

func (*Account) Print

func (act *Account) Print()

Print writes a debug dump of the account to stdout.

func (*Account) QuoteToAccount

func (act *Account) QuoteToAccount(inst string, price Price) (Rate, error)

QuoteToAccount returns the current conversion rate from an instrument's quote currency into the account's base currency.

It is used for position sizing and risk calculations when a price move denominated in quote currency must be expressed in account currency.

Examples for a USD account:

  • EURUSD -> 1.0
  • USDJPY -> 1 / USDJPY
  • EURGBP -> GBPUSD, or 1 / USDGBP if only the inverse exists

The returned Rate is scaled by RateScale.

func (*Account) RealizePNL

func (act *Account) RealizePNL(lot *Lot, trade *Trade) (Money, error)

RealizePNL closes out a lot's unrealised P/L into the account Balance. It updates Balance and resets Equity to the new Balance (caller must call ResolveWithMarks to account for any remaining open lots afterwards). Returns the realised P/L amount.

func (*Account) Resolve

func (act *Account) Resolve() error

Resolve recomputes Equity, MarginUsed, FreeMargin, and MarginLevel using each lot's last known entry price as its mark.

func (*Account) ResolveWithMarks

func (act *Account) ResolveWithMarks(marks map[string]Price) error

ResolveWithMarks recomputes all account-level derived fields (Equity, MarginUsed, FreeMargin, MarginLevel) using the provided mark prices. If a lot's instrument has no entry in marks, the lot's EntryPrice is used. Pass nil to revalue everything at entry (same as Resolve).

func (*Account) SizePosition

func (acct *Account) SizePosition(req *OpenRequest) error

SizePosition computes and sets req.Units as the lesser of:

  • the units allowed by the risk budget (unitsByRisk)
  • the units allowed by available margin (unitsByMargin)

Returns an error if the computed size is below the instrument's minimum trade size or if any input is invalid.

func (*Account) TradeMargin

func (act *Account) TradeMargin(units Units, price Price, inst string) (Money, error)

TradeMargin returns the margin required to hold a position of the given size at the given price for the named instrument, expressed in account currency (Money-scaled). It uses the instrument's MarginRate and the account's QuoteToAccount conversion.

type AccountManager

type AccountManager struct {
	// contains filtered or unexported fields
}

AccountManager is a simple registry of named accounts. It is used by the Trader to look up accounts by name or ID during order processing.

func NewAccountManager

func NewAccountManager() *AccountManager

NewAccountManager returns an empty AccountManager.

func (*AccountManager) Add

func (am *AccountManager) Add(act *Account)

Add registers an existing account, keyed by its ID.

func (*AccountManager) CreateAccount

func (am *AccountManager) CreateAccount(name string, b int64) *Account

CreateAccount creates a new Account with the given name and a deposit of b whole currency units (i.e. b × MoneyScale micro-units), stores it by name, and returns it.

func (*AccountManager) Get

func (am *AccountManager) Get(name string) *Account

Get returns the account registered under name, or nil if not found.

type Asset

type Asset struct {
	Key        Key
	Path       string
	Range      TimeRange
	Exists     bool
	Complete   bool
	Buildable  bool
	Size       int64
	UpdatedAt  time.Time
	SourceAge  time.Time // optional: mtime of prerequisite/source
	Descriptor string
	Flags      AssetFlags

	MissingInputs int
	Reason        string
}

type AssetFlags

type AssetFlags uint32
const (
	FlagUsable AssetFlags = 1 << iota
	FlagKnownClosed
	FlagDoNotDownload
	FlagDownloadFailed
	FlagManualSkip
)

type BA

type BA struct {
	Bid Price
	Ask Price
}

type Backtest

type Backtest struct {
	ID        string
	RunConfig RunConfig // original config snapshot before transformation

	*BacktestRequest
	*BacktestRun
	*BacktestResult
}

Backtest is the top-level unit of work for a single backtesting run. It composes a request (what to run), a mutable run-state (open lots, execution cost counters), and an immutable result (produced at the end). RunConfig is the original config snapshot; it is carried through to the summary so every report is self-describing.

func GetBacktests

func GetBacktests(cfg *Config) ([]Backtest, error)

GetBacktests converts a loaded Config into a slice of ready-to-run Backtest values. Defaults from cfg.Defaults (balance, risk, stop/take pips, slippage, max spread) are merged into each run. Returns an error if the config resolves to zero runs or any run is misconfigured.

func (*Backtest) BuildBacktestResult

func (run *Backtest) BuildBacktestResult(acct *Account) *BacktestResult

BuildBacktestResult snapshots the account state into a BacktestResult and stores it on the run. It computes wins/losses/flat counts, NetPL, ReturnPct, and WinRate from the account's closed trades. Returns nil if run or acct is nil.

func (*Backtest) Summary

func (run *Backtest) Summary() BacktestReportSummary

Summary builds a fully-populated BacktestReportSummary from the run's request and result fields. It is safe to call after BuildBacktestResult. Returns a zero-value summary if any required field is nil.

type BacktestReportSummary

type BacktestReportSummary struct {
	Name       string `json:"name"`
	Kind       string `json:"kind"`
	Strategy   string `json:"strategy"`
	Instrument string `json:"instrument"`
	Timeframe  string `json:"timeframe"`
	Dataset    string `json:"dataset"`
	Start      string `json:"start"`
	End        string `json:"end"`

	Trades int `json:"trades"`
	Wins   int `json:"wins"`
	Losses int `json:"losses"`

	StartBalance float64 `json:"start_balance"`
	EndBalance   float64 `json:"end_balance"`
	NetPL        float64 `json:"net_pl"`

	// Stored as human-friendly percentages, e.g. 12.34 means 12.34%
	ReturnPct float64 `json:"return_pct"`
	WinRate   float64 `json:"win_rate"`
	RiskPct   float64 `json:"risk_pct"`

	Stop      string `json:"stop"`
	Regime    string `json:"regime"`
	MaxSpread string `json:"max_spread,omitempty"`
	Slippage  string `json:"slippage,omitempty"`

	// Execution cost stats
	AvgSpreadPips  float64 `json:"avg_spread_pips"`
	SpreadFiltered int     `json:"spread_filtered"`
	RR             float64 `json:"rr"`
	MaxDrawdown    float64 `json:"max_drawdown"` // largest peak-to-trough drop in dollars (negative)
	AvgWinner      float64 `json:"avg_winner"`
	AvgLoser       float64 `json:"avg_loser"` // negative

	TradeDetails []BacktestReportTrade `json:"trade_details,omitempty"`

	// Provenance — always populated; links this report back to its origin.
	ConfigHash  string    `json:"config_hash"`  // 8-char SHA256 prefix of the run config params
	GeneratedAt string    `json:"generated_at"` // RFC3339 UTC timestamp of when the run completed
	Config      RunConfig `json:"config"`       // full config snapshot that produced this result
}

BacktestReportSummary is a normalized machine-readable summary used for committed regression baselines and generated comparison artifacts. The Config and ConfigHash fields make every report self-describing: you can open any JSON file and see exactly what params produced it.

func LoadOrgIndexSummaries

func LoadOrgIndexSummaries(dir string) ([]BacktestReportSummary, error)

LoadOrgIndexSummaries scans dir for *.json files and returns all summaries found.

func NewBacktestReportSummary

func NewBacktestReportSummary(r *BacktestResult) BacktestReportSummary

NewBacktestReportSummary constructs a BacktestReportSummary from a result. NOTE: currently returns a zero-value summary; full mapping is pending a BacktestResult restructure.

type BacktestReportTrade

type BacktestReportTrade struct {
	ID              string  `json:"id"`
	Instrument      string  `json:"instrument"`
	Side            string  `json:"side"`
	Units           int64   `json:"units"`
	OpenPrice       float64 `json:"open_price"`
	ClosePrice      float64 `json:"close_price"`
	OpenTime        string  `json:"open_time"`
	CloseTime       string  `json:"close_time"`
	PNL             float64 `json:"pnl"`
	StopPrice       float64 `json:"stop_price,omitempty"`
	TakeProfitPrice float64 `json:"take_profit_price,omitempty"`
}

BacktestReportTrade is a JSON-serialisable record of a single closed trade used inside BacktestReportSummary.TradeDetails.

type BacktestRequest

type BacktestRequest struct {
	Name       string
	ConfigHash string // 8-char SHA256 prefix of the RunConfig params (set by GetBacktests)

	StartingBalance Money
	RiskPct         Rate // fraction of equity risked per trade (e.g. 0.005 = 0.5 %)

	DefaultStopPips Pips // fallback stop distance when the strategy doesn't supply one
	DefaultTakePips Pips // fallback take-profit distance
	SlippagePips    Pips // extra adverse fill adjustment applied on every open/close
	MaxSpreadPips   Pips // opens are skipped when the candle spread exceeds this

	Source     string // data source identifier (e.g. "candles", "dukascopy")
	Instrument string // FX pair (e.g. "EUR_USD")
	Strategy
	Exit   ExitStrategy
	Regime RegimeFilter
	TimeRange
}

BacktestRequest holds all the static inputs needed to execute one backtest run. It is populated from Config/RunConfig before the run loop starts and is not modified during execution.

type BacktestResult

type BacktestResult struct {
	Balance Money // final account balance (realised only)
	Equity  Money // final equity including any open positions at run end

	Trades int // total closed trades
	Wins   int // trades with PNL > 0
	Losses int // trades with PNL < 0
	Flat   int // trades with PNL == 0

	Start Timestamp
	End   Timestamp

	// Derived fields — populated by BuildBacktestResult.
	NetPL        Money // Balance − StartingBalance
	ReturnPct    Rate  // NetPL / StartingBalance, RateScale-scaled
	WinRate      Rate  // Wins / Trades, RateScale-scaled
	ProfitFactor Rate  // gross wins / gross losses (not yet implemented)
	MaxDDPct     Rate  // maximum peak-to-trough drawdown % (not yet implemented)
}

BacktestResult is a lightweight, immutable summary produced at the end of a backtest run. All derived fields are computed by Backtest.BuildBacktestResult.

type BacktestRun

type BacktestRun struct {
	Lots   *LotBook
	Trades []*Trade

	// Execution cost tracking — populated by the run loop.
	SpreadFiltered int   // opens suppressed by the max-spread filter
	SpreadOpened   int   // opens that went through (for avg spread calc)
	SpreadSum      Price // sum of candle.AvgSpread at each accepted open
}

BacktestRun holds mutable state accumulated during a single backtest execution: the live lot book, the list of closed trades, and execution-cost counters updated by the run loop.

func (*BacktestRun) BuildBacktestResult

func (run *BacktestRun) BuildBacktestResult(acct *Account)

BuildBacktestResult copies the account's closed trades into the run. Full result computation (win/loss counts, P/L) is done by Backtest.BuildBacktestResult.

func (*BacktestRun) GetTrades

func (run *BacktestRun) GetTrades() []*Trade

GetTrades returns the run's closed trade list, or nil if run is nil.

type Broker

type Broker struct {
	ID string
	*Account
	OpenOrders // should Account own OpenOrders?
	// contains filtered or unexported fields
}

func NewBroker

func NewBroker(name string) *Broker

func (*Broker) Events

func (b *Broker) Events() <-chan *Event

func (*Broker) SubmitClose

func (b *Broker) SubmitClose(ctx context.Context, req *CloseRequest) error

func (*Broker) SubmitOpen

func (b *Broker) SubmitOpen(ctx context.Context, req *OpenRequest) (*openResult, error)

func (*Broker) SubmitOrder

func (b *Broker) SubmitOrder(ctx context.Context, ord *order) (*Lot, error)

type BrokerInterface

type BrokerInterface interface {
	SubmitOpen(ctx context.Context, req *OpenRequest) error
	SubmitClose(ctx context.Context, req *CloseRequest) error
	Events() <-chan *Event
}

type BuildDecision

type BuildDecision struct {
	Key
	Status   BuildStatus
	Required []Key
	Missing  []Key
	Reason   string
}

type BuildKind

type BuildKind string

BuildKind identifies the aggregation step to perform.

const (
	BuildM1 BuildKind = "m1_from_ticks" // aggregate tick data into M1 candles
	BuildH1 BuildKind = "h1_from_m1"    // aggregate M1 candles into H1 candles
	BuildD1 BuildKind = "d1_from_h1"    // aggregate H1 candles into D1 candles
)

type BuildStatus

type BuildStatus int
const (
	BuildUnknown BuildStatus = iota
	BuildReady
	BuildBlocked
	BuildExistsComplete
)

type BuildTask

type BuildTask struct {
	Key
	// Range  TimeRange
	Inputs []Key
	Kind   BuildKind
}

BuildTask represents a single candle-aggregation job: build the candles identified by Key from the listed input Keys using the specified Kind.

type CSVTicksFeed

type CSVTicksFeed struct {
	// contains filtered or unexported fields
}

CSVTicksFeed reads canonical tick CSV rows:

time,instrument,bid,ask[,event...]

where time is RFC3339 or RFC3339Nano.

It optionally filters ticks to [From, To) if provided. Header row ("time,...") is allowed. Empty/short rows are skipped.

func NewCSVTicksFeed

func NewCSVTicksFeed(path string, from, to Timestamp) (*CSVTicksFeed, error)

NewCSVTicksFeed opens the CSV file at path and returns a feed that yields only ticks whose timestamp falls within [from, to). Pass zero Timestamps to disable filtering.

func (*CSVTicksFeed) Close

func (f *CSVTicksFeed) Close() error

Close releases the underlying file handle.

func (*CSVTicksFeed) Next

func (f *CSVTicksFeed) Next() (Tick, bool, error)

Next advances the feed and returns the next in-range Tick. Returns (Tick{}, false, nil) at EOF and (Tick{}, false, err) on parse errors.

type Candle

type Candle struct {
	Open      Price
	High      Price
	Low       Price
	Close     Price
	AvgSpread Price
	MaxSpread Price
	Ticks     int32 // number of ticks per candle
}

func (*Candle) FullString

func (c *Candle) FullString() string

func (*Candle) IsZero

func (c *Candle) IsZero() bool

func (*Candle) String

func (c *Candle) String() string

type CandleIndicator

type CandleIndicator interface {
	// Name returns a stable identifier like "EMA(20)" or "RSI(14)".
	Name() string

	// Warmup returns how many updates are needed before Ready() can be true.
	// (Some indicators may become ready earlier; that's fine.)
	Warmup() int

	// Reset clears all internal state.
	Reset()

	// Update consumes the next *closed* candle and updates internal state.
	Update(c Candle)

	// Ready reports whether Value() is meaningful (warmup completed).
	Ready() bool
}

CandleIndicator computes a single streaming value from candles. It is deterministic and safe to use in live, replay, and backtests.

type CandleRequest

type CandleRequest struct {
	Source     string
	Instrument string
	Range      TimeRange
	Strict     bool
}

func (CandleRequest) Key

func (cr CandleRequest) Key() Key

type CandleTime

type CandleTime = candleTime

type ChandelierExit

type ChandelierExit struct {
	// contains filtered or unexported fields
}

ChandelierExit trails the stop from the highest-high (long) or lowest-low (short) seen since entry, offset by N×ATR. The stop only ever moves in the profitable direction — it never moves against the position.

Per-position extreme tracking lives on Lot.ExtremePrice so multiple concurrent lots each maintain their own watermark.

func NewChandelierExit

func NewChandelierExit(atrPeriod int, multiplier float64, scale Scale6) *ChandelierExit

func (*ChandelierExit) InitialStop

func (c *ChandelierExit) InitialStop(side Side, entry Price, candle Candle) Price

func (*ChandelierExit) Name

func (c *ChandelierExit) Name() string

func (*ChandelierExit) Ready

func (c *ChandelierExit) Ready() bool

func (*ChandelierExit) Tick

func (c *ChandelierExit) Tick(candle Candle)

func (*ChandelierExit) UpdateStop

func (c *ChandelierExit) UpdateStop(side Side, currentStop Price, _ Price, extreme Price, candle Candle) Price

type ChoppinessFilter

type ChoppinessFilter struct {
	// contains filtered or unexported fields
}

ChoppinessFilter gates entries using the Choppiness Index. When CI < threshold the market is trending; entries are allowed. When CI >= threshold the market is ranging; new opens are suppressed. The conventional threshold is 61.8.

func NewChoppinessFilter

func NewChoppinessFilter(period int, threshold float64, scale Scale6) *ChoppinessFilter

func (*ChoppinessFilter) Name

func (f *ChoppinessFilter) Name() string

func (*ChoppinessFilter) Ready

func (f *ChoppinessFilter) Ready() bool

func (*ChoppinessFilter) Tick

func (f *ChoppinessFilter) Tick(c Candle)

func (*ChoppinessFilter) Trending

func (f *ChoppinessFilter) Trending() bool

func (*ChoppinessFilter) Value

func (f *ChoppinessFilter) Value() float64

Value exposes the raw CI value for logging/debugging.

type ChoppinessIndex

type ChoppinessIndex struct {
	// contains filtered or unexported fields
}

ChoppinessIndex measures whether price action is trending or ranging.

Formula: 100 × log10(Σ TR(1,N) / (HH(N) − LL(N))) / log10(N)

Values near 100 = choppy/consolidating; near 0 = strongly trending. Conventional threshold: 61.8 (trending below, ranging above).

func NewChoppinessIndex

func NewChoppinessIndex(period int, scale Scale6) *ChoppinessIndex

func (*ChoppinessIndex) Name

func (c *ChoppinessIndex) Name() string

func (*ChoppinessIndex) Ready

func (c *ChoppinessIndex) Ready() bool

func (*ChoppinessIndex) Reset

func (c *ChoppinessIndex) Reset()

func (*ChoppinessIndex) Update

func (c *ChoppinessIndex) Update(candle Candle)

func (*ChoppinessIndex) Value

func (c *ChoppinessIndex) Value() float64

func (*ChoppinessIndex) Warmup

func (c *ChoppinessIndex) Warmup() int

type CloseMatcher

type CloseMatcher interface {
	Match(lots []*Lot, units Units) ([]LotMatch, error)
}

type CloseRequest

type CloseRequest struct {
	Request
	*Lot
	CloseCause closeCause
}

type Config

type Config struct {
	Version  int         `json:"version" yaml:"version"`
	Defaults RunDefaults `json:"defaults" yaml:"defaults"`
	Runs     []RunConfig `json:"runs" yaml:"runs"`
}

Config is the top-level structure parsed from a YAML or JSON config file. It carries a set of defaults that are merged into each RunConfig before the run is executed.

func LoadConfig

func LoadConfig(path string) (*Config, error)

LoadConfig reads and parses a YAML or JSON config file from path. The file extension determines the parser (.yaml/.yml → YAML; .json → JSON). Returns an error if the file is missing, unparseable, or contains no runs.

type DataConfig

type DataConfig struct {
	Source     string `json:"source" yaml:"source"`
	Instrument string `json:"instrument" yaml:"instrument"`
	Timeframe  string `json:"timeframe" yaml:"timeframe"`
	From       string `json:"from" yaml:"from"`
	To         string `json:"to" yaml:"to"`
	Strict     *bool  `json:"strict" yaml:"strict"`
}

DataConfig specifies the data source, instrument, timeframe, and date range for a run.

type DataKind

type DataKind uint8
const (
	KindUnknown DataKind = iota
	KindTick
	KindCandle
)

func (DataKind) String

func (k DataKind) String() string

type DataManager

type DataManager struct {
	Start       time.Time
	End         time.Time
	Instruments []string
	// contains filtered or unexported fields
}

DataManager is responsible for identifing data files that are missing accross all instruments. For missing datasets, ensure they are downloaded, for datasets that are downloaded, make sure they are made into candles.

func GetDataManager

func GetDataManager() *DataManager

func NewDataManager

func NewDataManager(instruments []string, start, end time.Time) *DataManager

NewDataManager constructs a DataManager for the given instruments and time range.

func (*DataManager) BuildWantList

func (dm *DataManager) BuildWantList(ctx context.Context) (*Wantlist, error)

func (*DataManager) Candles

func (dm *DataManager) Candles(ctx context.Context, req CandleRequest) (candleIterator, error)

func (*DataManager) ExecuteDownloads

func (dm *DataManager) ExecuteDownloads(ctx context.Context) error

func (*DataManager) Init

func (dm *DataManager) Init()

Init will get DataManager ready to go.

func (*DataManager) Plan

func (dm *DataManager) Plan(ctx context.Context) (plan *Plan, err error)

func (*DataManager) Sync

func (dm *DataManager) Sync(ctx context.Context, download, build bool) error

type EMA

type EMA struct {
	// contains filtered or unexported fields
}

EMA computes an Exponential Moving Average over candle closes.

Pricing note:

  • trader.Candle prices are scaled integers.
  • EMA outputs float64 in *price units* (e.g. 1.08765), so we need the CandleSet scale. Pass the same scale used to build your CandleSet (e.g. 1_000_000 for Dukascopy).

func NewEMA

func NewEMA(period int, scale Scale6) *EMA

func (*EMA) Float64

func (e *EMA) Float64() float64

func (*EMA) Name

func (e *EMA) Name() string

func (*EMA) Period

func (e *EMA) Period() int

func (*EMA) Ready

func (e *EMA) Ready() bool

func (*EMA) Reset

func (e *EMA) Reset()

func (*EMA) Update

func (e *EMA) Update(c Candle)

func (*EMA) Warmup

func (e *EMA) Warmup() int

type EquitySnapshot

type EquitySnapshot struct {
	Timestamp   Timestamp
	Balance     Money
	Equity      Money
	MarginUsed  Money
	FreeMargin  Money
	MarginLevel Money
}

This could go into broker

type Event

type Event struct {
	Type          EventType
	Time          Timestamp
	ClientOrderID string
	BrokerOrderID string
	PositionID    string
	Instrument    string
	Reason        string
	Cause         closeCause

	Open  *OpenRequest
	Close *CloseRequest
	Trade *Trade
	Lot   *Lot
}

type EventType

type EventType int
const (
	EventOrderAccepted EventType = iota + 1
	EventOrderRejected
	EventOrderFilled
	EventOrderPartiallyFilled
	EventOrderCanceled
	EventPositionClosed
	EventAccountUpdated
)

func (EventType) String

func (e EventType) String() string

type ExitConfig

type ExitConfig struct {
	Kind   string         `json:"kind"   yaml:"kind"`
	Params map[string]any `json:"params" yaml:"params"`
}

ExitConfig mirrors the exit: section of a YAML backtest config.

type ExitStrategy

type ExitStrategy interface {
	// Name returns a human-readable description for reports.
	Name() string

	// Ready reports whether the exit strategy has enough history to place stops.
	Ready() bool

	// Tick updates internal indicators. Called every bar before strategy.Update().
	Tick(c Candle)

	// InitialStop returns the stop price at the moment a position is opened.
	InitialStop(side Side, entry Price, c Candle) Price

	// UpdateStop returns the new stop price for an open lot each bar.
	// extreme is the lot's ExtremePrice (highest high for longs, lowest low for shorts).
	// The implementation must never move the stop against the position.
	UpdateStop(side Side, currentStop Price, entry Price, extreme Price, c Candle) Price
}

ExitStrategy manages stop placement after a position is open. It is called every bar regardless of position state (to warm up indicators), and is consulted to set/update the stop price on open lots.

func GetExitStrategy

func GetExitStrategy(cfg ExitConfig, scale Scale6) (ExitStrategy, error)

GetExitStrategy constructs an ExitStrategy from cfg. If cfg.Kind is empty, NoopExit is returned (pass-through).

type FIFOMatcher

type FIFOMatcher struct{}

FIFOMatcher closes the oldest open lots first.

func (FIFOMatcher) Match

func (FIFOMatcher) Match(lots []*Lot, units Units) ([]LotMatch, error)

type IndicatorFloat64

type IndicatorFloat64 interface {
	// Value returns the current indicator value. If !Ready(), it should return 0
	// (or the last computed value) — callers should always check Ready().
	Float64() float64
}

type IndicatorFloat64s

type IndicatorFloat64s interface {
	// Value returns the current indicator value. If !Ready(), it should return 0
	// (or the last computed value) — callers should always check Ready().
	Float64() []float64
}

type IndicatorPrice

type IndicatorPrice interface {
	Price() Price
}

type Instrument

type Instrument struct {
	Name                string
	BaseCurrency        string
	QuoteCurrency       string
	PipLocation         int
	TradeUnitsPrecision int
	MinimumTradeSize    Units
	MarginRate          Rate
}

func GetInstrument

func GetInstrument(symbol string) *Instrument

func (*Instrument) AddPips

func (inst *Instrument) AddPips(px Price, pips Pips) Price

func (*Instrument) DukascopyPriceMultiplier

func (inst *Instrument) DukascopyPriceMultiplier() uint32

DukascopyPriceMultiplier returns the factor needed to convert a raw Dukascopy bi5 price integer into a Price value at the current PriceScale.

Dukascopy stores prices with (−PipLocation + 1) decimal places:

  • 5-decimal pairs (EURUSD, PipLocation=−4): native scale 100,000 → multiplier = 1
  • 3-decimal pairs (USDJPY, PipLocation=−2): native scale 1,000 → multiplier = 100

func (*Instrument) PipSize

func (inst *Instrument) PipSize() float64

func (*Instrument) PriceDeltaFromPips

func (inst *Instrument) PriceDeltaFromPips(pips Pips) Price

func (*Instrument) PriceUnitsPerPip

func (inst *Instrument) PriceUnitsPerPip() Price

func (*Instrument) SubPips

func (inst *Instrument) SubPips(px Price, pips Pips) Price

type Inventory

type Inventory struct {
	// contains filtered or unexported fields
}

func BuildInventory

func BuildInventory(ctx context.Context) (*Inventory, error)

func NewInventory

func NewInventory() *Inventory

func (*Inventory) Delete

func (inv *Inventory) Delete(key Key)

func (*Inventory) Get

func (inv *Inventory) Get(key Key) (Asset, bool)

func (*Inventory) Has

func (inv *Inventory) Has(key Key) bool

func (*Inventory) HasComplete

func (inv *Inventory) HasComplete(key Key) bool

func (*Inventory) Keys

func (inv *Inventory) Keys() []Key

func (*Inventory) Len

func (inv *Inventory) Len() int

func (*Inventory) List

func (inv *Inventory) List() []Asset

func (*Inventory) MissingComplete

func (inv *Inventory) MissingComplete(keys []Key) []Key

func (*Inventory) Put

func (inv *Inventory) Put(a Asset)

func (*Inventory) TicksComplete

func (inv *Inventory) TicksComplete(k Key) (bool, []Key)

func (*Inventory) Update

func (inv *Inventory) Update(key Key, fn func(*Asset) error) error

type Journal

type Journal interface {
	RecordTrade(TradeRecord) error
	RecordEquity(EquitySnapshot) error
	Close() error
}

func NewSQLite

func NewSQLite(_ string) (Journal, error)

NewSQLite is a stub that returns an error when the binary is built without the sqlite build tag. To enable SQLite support: go build -tags sqlite.

type Key

type Key struct {
	Instrument string
	Source     string
	Kind       DataKind
	TF         Timeframe
	Year       int
	Month      int
	Day        int
	Hour       int
}

func RequiredTickHoursForMonth

func RequiredTickHoursForMonth(source, instrument string, year, month int) []Key

func (Key) IsHourlyTick

func (k Key) IsHourlyTick() bool

func (Key) IsMonthlyCandle

func (k Key) IsMonthlyCandle() bool

func (Key) Path

func (k Key) Path() string

func (Key) Range

func (k Key) Range() TimeRange

func (Key) Time

func (ak Key) Time() time.Time

Time returns the UTC time represented by the key. Missing fields are normalized to the earliest valid value.

Examples:

Year=2024, Month=0, Day=0, Hour=0 -> 2024-01-01 00:00:00 UTC
Year=2024, Month=5, Day=0, Hour=0 -> 2024-05-01 00:00:00 UTC
Year=2024, Month=5, Day=7, Hour=13 -> 2024-05-07 13:00:00 UTC

type Keymap

type Keymap[V any] struct {
	// contains filtered or unexported fields
}

func NewKeymap

func NewKeymap[V any]() Keymap[V]

func (*Keymap[V]) Delete

func (km *Keymap[V]) Delete(key Key)

func (*Keymap[V]) Get

func (km *Keymap[V]) Get(key Key) (V, bool)

func (*Keymap[V]) Has

func (km *Keymap[V]) Has(key Key) bool

func (*Keymap[V]) Keys

func (km *Keymap[V]) Keys() []Key

func (*Keymap[V]) Len

func (km *Keymap[V]) Len() int

func (*Keymap[V]) List

func (km *Keymap[V]) List() []V

func (*Keymap[V]) Put

func (km *Keymap[V]) Put(key Key, v V)

func (*Keymap[V]) Range

func (km *Keymap[V]) Range(fn func(Key, V) bool)

func (*Keymap[V]) Update

func (km *Keymap[V]) Update(key Key, fn func(*V) error) error

type LinearCongruentialRandom

type LinearCongruentialRandom struct {
	// contains filtered or unexported fields
}

LinearCongruentialRandom is a simple deterministic RNG.

func NewLCRandom

func NewLCRandom(seed int64) *LinearCongruentialRandom

NewLCRandom creates a new LCR with a seed.

func (*LinearCongruentialRandom) NextGaussian

func (r *LinearCongruentialRandom) NextGaussian() float64

NextGaussian returns a pseudo-random number from a normal distribution (Box-Muller).

func (*LinearCongruentialRandom) NextUniform

func (r *LinearCongruentialRandom) NextUniform() float64

NextUniform returns a pseudo-random number in [0, 1).

type LiveJournal

type LiveJournal struct {
	// contains filtered or unexported fields
}

LiveJournal subscribes to an OANDA transaction stream and writes complete TradeRecord rows to the configured Journal as trades close.

Open ORDER_FILL events are buffered in memory (keyed by tradeID) until the matching close ORDER_FILL arrives. The close fill provides the realized P/L; we look up the buffered open to fill in entry side and open time, then RecordTrade(...) writes the complete row.

Heartbeats advance an in-memory "lastSeenTxID" cursor so callers can reconnect (or poll for gap recovery) from a known point.

func NewLiveJournal

func NewLiveJournal(client *oanda.Client, accountID string, journal Journal, log *slog.Logger) *LiveJournal

NewLiveJournal creates a journal worker. Call Run to start the subscription.

func (*LiveJournal) Backfill

func (lj *LiveJournal) Backfill(ctx context.Context, sinceID int64) error

Backfill polls GetTransactions from sinceID forward and replays them into the same handler used for streamed events. Call before Run to recover anything missed during downtime.

func (*LiveJournal) LastSeenTxID

func (lj *LiveJournal) LastSeenTxID() int64

LastSeenTxID returns the highest transaction ID we've processed (via heartbeat or actual transaction). Persist this for resume on restart.

func (*LiveJournal) Run

func (lj *LiveJournal) Run(ctx context.Context) error

Run subscribes to the transaction stream and processes events until ctx is cancelled or the stream ends. Returns the final error from the stream (nil on clean ctx-cancel exit).

type LiveOpenRequest

type LiveOpenRequest struct {
	Side     string  // "long" or "short"
	StopPips float64 // stop-loss distance in pips
	TakePips float64 // take-profit distance in pips (0 = none)
	RiskPct  float64 // percent of account NAV to risk
}

LiveOpenRequest carries the parameters for a new live position.

type LivePlan

type LivePlan struct {
	// Open describes a new position to open. Nil means hold.
	Open *LiveOpenRequest
	// CloseIDs lists trade IDs the strategy wants to close.
	CloseIDs []string
	// Reason is a human-readable note logged by the runner.
	Reason string
}

LivePlan is what the strategy asks the runner to do this tick. At most one new position is opened per tick; zero or more are closed.

type LivePrice

type LivePrice struct {
	Instrument string
	Bid        float64
	Ask        float64
	Time       time.Time
}

LivePrice is a bid/ask snapshot from the broker.

func (LivePrice) Mid

func (p LivePrice) Mid() float64

Mid returns the mid-price.

type LiveStrategy

type LiveStrategy interface {
	Name() string

	// Tick is called once per poll interval. price is the current bid/ask snapshot.
	// openTrades lists all tracked open positions for this strategy's instrument.
	// Returns a plan (open one new position and/or close a set of existing ones).
	Tick(ctx context.Context, price LivePrice, openTrades []LiveTrade) *LivePlan
}

LiveStrategy is implemented by strategies that drive live (non-backtest) trading. Tick is called on each price poll; the runner tracks position ages and passes them in so the strategy can decide what to open or close.

type LiveTrade

type LiveTrade struct {
	ID           string
	Instrument   string
	Units        int64 // positive = long, negative = short
	EntryPrice   float64
	UnrealizedPL float64
	TicksOpen    int // incremented by the runner each poll tick
}

LiveTrade describes an open position as seen by the live runner.

func (LiveTrade) Side

func (t LiveTrade) Side() string

Side returns "long" or "short".

type LogConfig

type LogConfig struct {
	// Level is the minimum log level to emit.  Accepted values (case-
	// insensitive): "debug", "info", "warn" / "warning", "error".
	// Defaults to "info" when empty or unrecognised.
	Level string

	// Format selects the handler format: "json" for JSON output, anything
	// else (or empty) for human-readable text.
	Format string

	// File is an optional path to a log file. When non-empty, log records
	// are written to both stdout and this file. When empty and no other sink
	// is configured, Setup falls back to a default log file.
	File string

	// Syslog enables forwarding of log records to the system logger.
	// Has no effect on Windows (syslog is not available there).
	Syslog bool

	// Stdout enables log output to stdout
	Stdout bool

	// Memory enables in-memory capture of log entries, accessible via
	// Entries() and ClearEntries().  Useful for testing and diagnostics.
	Memory bool
}

LogConfig holds the logging configuration that is typically populated from the application's RootConfig (RootConfig.LogLevel, etc.).

type LogEntry

type LogEntry struct {
	Time    time.Time
	Level   slog.Level
	Message string
	Attrs   []slog.Attr
}

LogEntry is a single structured log record stored in the in-memory stack.

func Entries

func Entries() []LogEntry

Entries returns a snapshot (copy) of all log entries currently held in the in-memory stack. It is safe to call from multiple goroutines.

type Lot

type Lot struct {
	*TradeCommon
	EntryPrice     Price
	EntryTime      Timestamp
	OriginalUnits  Units
	RemainingUnits Units
	State          lotState
	// ExtremePrice tracks the highest-high (long) or lowest-low (short) seen
	// since entry. Used by trailing/chandelier exit strategies.
	ExtremePrice Price
}

type LotBook

type LotBook struct {
	// contains filtered or unexported fields
}

func (*LotBook) Add

func (lb *LotBook) Add(lot *Lot)

func (*LotBook) All

func (lb *LotBook) All() map[string]*Lot

func (*LotBook) Delete

func (lb *LotBook) Delete(id string)

func (*LotBook) Len

func (lb *LotBook) Len() int

func (*LotBook) Range

func (lb *LotBook) Range(fn func(*Lot) error) error

func (*LotBook) Slice

func (lb *LotBook) Slice() []*Lot

type LotMatch

type LotMatch struct {
	Lot   *Lot
	Units Units
}

type Money

type Money int64

func MoneyFromFloat

func MoneyFromFloat(f float64) Money

func TradeMargin

func TradeMargin(units Units, price Price, instrument string, quoteToAccount Rate) (Money, error)

TradeMargin is a package-level helper that computes the margin required to hold a position of the given size at the given price for the named instrument. Unlike Account.TradeMargin, the caller supplies the quote-to-account rate directly, making this function usable without an Account instance (e.g. in unit tests or external calculators).

Result is in account currency, Money-scaled (micro-units).

func (Money) Float64

func (m Money) Float64() float64

func (Money) String

func (m Money) String() string

type NoopExit

type NoopExit struct{}

NoopExit is a pass-through exit strategy. It never moves stops; the entry strategy is responsible for setting an initial stop via the OpenRequest.

func (NoopExit) InitialStop

func (NoopExit) InitialStop(_ Side, _ Price, _ Candle) Price

func (NoopExit) Name

func (NoopExit) Name() string

func (NoopExit) Ready

func (NoopExit) Ready() bool

func (NoopExit) Tick

func (NoopExit) Tick(_ Candle)

func (NoopExit) UpdateStop

func (NoopExit) UpdateStop(_ Side, currentStop Price, _ Price, _ Price, _ Candle) Price

type NoopRegime

type NoopRegime struct{}

NoopRegime is a pass-through filter that always allows trading.

func (NoopRegime) Name

func (NoopRegime) Name() string

func (NoopRegime) Ready

func (NoopRegime) Ready() bool

func (NoopRegime) Tick

func (NoopRegime) Tick(_ Candle)

func (NoopRegime) Trending

func (NoopRegime) Trending() bool

type OpenOrders

type OpenOrders struct {
	Orders map[string]*order
}

func (*OpenOrders) Add

func (o *OpenOrders) Add(od *order)

func (*OpenOrders) Get

func (o *OpenOrders) Get(id string) *order

type OpenRequest

type OpenRequest struct {
	Request
}

func NewOpenRequest

func NewOpenRequest(
	instr string,
	c *CandleTime,
	side Side,
	stop Price,
	take Price,
	reason string) *OpenRequest

type OrderRequest

type OrderRequest struct {
	Instrument string
	Units      Units
}

type Pips

type Pips int32

Pips is scaled such that 1 == .1 pip and 20 == 2 pips

func PipsFromFloat

func PipsFromFloat(v float64) Pips

PipsFromFloat converts a pip count expressed as float64 to the Pips type.

func (Pips) Float64

func (p Pips) Float64() float64

type Plan

type Plan struct {
	Download []Key
	BuildM1  []BuildTask
	BuildH1  []BuildTask
	BuildD1  []BuildTask

	BlockedM1 []BuildDecision
	BlockedH1 []BuildDecision
	BlockedD1 []BuildDecision
}

Plan describes the data-preparation work that must be completed before a backtest can run: files to download and candle aggregations to build at each timeframe. Blocked entries list tasks that could not be scheduled due to missing inputs.

func (Plan) Log

func (p Plan) Log()

Log emits a structured summary of the plan (download and build counts) at info level.

type Position

type Position struct {
	Instrument    string
	NetUnits      Units
	AvgEntryPrice Price
	UnrealizedPL  Money
	MarginUsed    Money
}

Position is the computed aggregate view of all open lots for one instrument.

type Price

type Price int32

func PriceFromFloat

func PriceFromFloat(f float64) Price

func (Price) Float64

func (p Price) Float64() float64

func (Price) String

func (p Price) String() string

type Rate

type Rate int64

func RateFromFloat

func RateFromFloat(f float64) Rate

func (Rate) Float64

func (r Rate) Float64() float64

func (Rate) String

func (r Rate) String() string

type RawTick

type RawTick struct {
	Ask    Price
	Bid    Price
	AskVol float32
	BidVol float32
	// contains filtered or unexported fields
}

func (RawTick) FloorToHour

func (ms RawTick) FloorToHour() timemilli

func (RawTick) FloorToMinute

func (ms RawTick) FloorToMinute() timemilli

func (RawTick) Mid

func (t RawTick) Mid() Price

func (RawTick) Minute

func (t RawTick) Minute() timemilli

func (RawTick) Sec

func (ms RawTick) Sec() Timestamp

Conversions

func (RawTick) Spread

func (t RawTick) Spread() Price

func (RawTick) TimeMS

func (t RawTick) TimeMS() int64

TimeMS returns the tick timestamp in milliseconds since the Unix epoch. Exported for use by sibling packages that need raw tick time.

type RegimeConfig

type RegimeConfig struct {
	Kind   string         `json:"kind"   yaml:"kind"`
	Params map[string]any `json:"params" yaml:"params"`
}

RegimeConfig mirrors the regime: section of a YAML backtest config.

type RegimeFilter

type RegimeFilter interface {
	// Name returns a human-readable label for reports.
	Name() string

	// Ready reports whether the filter has enough history to classify.
	Ready() bool

	// Tick updates internal indicators. Called every bar.
	Tick(c Candle)

	// Trending returns true when the market is in a trending regime and
	// new entries should be allowed. Returns true while not yet ready so
	// warmup bars are not suppressed.
	Trending() bool
}

RegimeFilter classifies the current market as trending or ranging. The bar loop calls Tick() every bar and suppresses new position opens when Trending() returns false.

func GetRegimeFilter

func GetRegimeFilter(cfg RegimeConfig, scale Scale6) (RegimeFilter, error)

GetRegimeFilter constructs a RegimeFilter from cfg. If cfg.Kind is empty, NoopRegime is returned (no filtering).

type Request

type Request struct {
	*TradeCommon
	RequestType
	Price
	Timestamp
	Reason string
	Candle Candle
}

type RequestType

type RequestType uint8
const (
	RequestNone RequestType = iota
	RequestMarketOpen
	RequestLimitOpen
	RequestClose
)

type RootConfig

type RootConfig struct {
	ConfigPath string
	GlobalPath string
	DBPath     string
	ReportPath string
	DataDir    string

	LogLevel string
	NoColor  bool
}

type RunConfig

type RunConfig struct {
	Name     string         `json:"name"     yaml:"name"`
	Data     DataConfig     `json:"data"     yaml:"data"`
	Strategy StrategyConfig `json:"strategy" yaml:"strategy"`
	Exit     ExitConfig     `json:"exit"     yaml:"exit"`
	Regime   RegimeConfig   `json:"regime"   yaml:"regime"`
}

RunConfig describes a single backtest run: what data to load, which strategy to use, and optional exit and regime-filter overrides.

type RunDefaults

type RunDefaults struct {
	StartingBalance float64 `json:"starting-balance" yaml:"starting-balance"`
	AccountCCY      string  `json:"account-ccy" yaml:"account-ccy"`
	Scale           int64   `json:"scale" yaml:"scale"`
	Strict          bool    `json:"strict" yaml:"strict"`

	RiskPct       float64 `json:"risk-pct" yaml:"risk-pct"`
	StopPips      int32   `json:"stop-pips" yaml:"stop-pips"`
	TakePips      int32   `json:"take-pips" yaml:"take-pips"`
	RR            float64 `json:"rr" yaml:"rr"`
	Units         int32   `json:"units" yaml:"units"`
	SlippagePips  float64 `json:"slippage-pips" yaml:"slippage-pips"`
	MaxSpreadPips float64 `json:"max-spread-pips" yaml:"max-spread-pips"`

	Source string `json:"source" yaml:"source"`
}

RunDefaults holds account-level and execution-cost settings that apply to every run in the config unless overridden at the run level.

type Scale6

type Scale6 int32

type Scale7

type Scale7 int64

type Side

type Side int
const (
	Short Side = -1
	Long  Side = 1
)

func (Side) String

func (s Side) String() string

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store enforces a file naming convention like:

GBPUSD-M1-2026-01.csv
GBPUSD-H1-2026-02.csv
GBPUSD-D1-2026-02.csv

func GetStore

func GetStore() *Store

GetStore returns the global Store. Used by sibling packages (e.g. data/dukascopy) that need direct store access.

func NewStoreAt

func NewStoreAt(basedir string) *Store

NewStoreAt returns a fresh Store rooted at basedir. Useful for tests.

func (Store) Delete

func (s Store) Delete(k Key) error

func (Store) Exists

func (s Store) Exists(key Key) (bool, error)

func (*Store) IsUsableTickFile

func (s *Store) IsUsableTickFile(k Key) bool

func (*Store) OpenTickIterator

func (s *Store) OpenTickIterator(key Key) (iterator[RawTick], error)

func (*Store) PathForAsset

func (s *Store) PathForAsset(k Key) string

func (*Store) ReadCSV

func (store *Store) ReadCSV(key Key) (cs *candleSet, err error)

func (*Store) RelDir

func (s *Store) RelDir(key Key) string

func (*Store) SaveFile

func (s *Store) SaveFile(key Key, r io.ReadCloser) (path string, err error)

func (*Store) WriteCSV

func (s *Store) WriteCSV(cs *candleSet) error

func (*Store) WriteMonthlyCandles

func (s *Store) WriteMonthlyCandles(source, instrument string, tf Timeframe, monthStart time.Time, candles []Candle) error

WriteMonthlyCandles writes a slice of Candle as a monthly CSV file in the canonical trader format. The candles should be dense (one slot per timeframe step within the month); zero-valued candles are treated as gaps.

Source is the data source name (e.g. "oanda", "dukascopy") and ends up in the path: <basedir>/<source>/<instrument>/<year>/<month>/<instr>-<year>-<month>-<tf>.csv

type Strategy

type Strategy interface {
	Name() string
	Reset()
	Ready() bool
	Update(context.Context, *CandleTime, *Backtest) *StrategyPlan

	// StopDescription returns a human-readable description of how this strategy
	// places stops, e.g. "ATR(14)×1.5", "25 pips", or "" if none.
	StopDescription() string
}

Strategy is the single backtest strategy interface used across the repo.

func GetStrategy

func GetStrategy(scfg StrategyConfig) (Strategy, error)

GetStrategy is the public dispatcher used by config-driven backtest setup. It looks the strategy up in the registry; implementations register themselves via init() in their own packages.

type StrategyBaseConfig

type StrategyBaseConfig struct {
	Instrument string
}

type StrategyConfig

type StrategyConfig struct {
	Kind   string         `json:"kind" yaml:"kind"`
	Params map[string]any `json:"params" yaml:"params"`
}

StrategyConfig names the strategy and carries arbitrary key/value parameters that are passed to the strategy constructor at build time.

type StrategyConstructor

type StrategyConstructor func(params map[string]any) (Strategy, error)

StrategyConstructor builds a Strategy from a config's Params map. Each implementation owns its own param parsing.

func LookupStrategy

func LookupStrategy(name string) StrategyConstructor

LookupStrategy returns the constructor registered under name, or nil.

type StrategyPlan

type StrategyPlan struct {
	Opens  []*OpenRequest
	Closes []*CloseRequest
	Cancel []string
	Reason string
}

type SyntheticCandleConfig

type SyntheticCandleConfig struct {
	Instrument  string    // e.g., "EURUSD"
	Timeframe   Timeframe // e.g., H1 (hourly)
	StartPrice  Price     // Starting price in scale units
	Volatility  float64   // Volatility as percentage (e.g., 0.005 = 0.5%)
	Trend       float64   // Trend as log return per candle (e.g., 0.0001 = +0.01%)
	Seed        int64     // Random seed for reproducibility
	TicksPerBar int32     // Number of ticks per candle
}

SyntheticCandleConfig holds parameters for generating synthetic candle data.

func DefaultSyntheticConfig

func DefaultSyntheticConfig(instrument string) SyntheticCandleConfig

DefaultSyntheticConfig returns a sensible default configuration for EUR/USD.

func (SyntheticCandleConfig) GenerateSyntheticMonthlyCandles

func (cfg SyntheticCandleConfig) GenerateSyntheticMonthlyCandles(year int, month time.Month) (*candleSet, error)

GenerateSyntheticMonthlyCandles generates a full month of synthetic OHLC data.

func (SyntheticCandleConfig) GenerateSyntheticYearlyAndWrite

func (cfg SyntheticCandleConfig) GenerateSyntheticYearlyAndWrite(store *Store, year int) ([]string, error)

GenerateSyntheticYearlyAndWrite generates a year of synthetic data and writes it to CSV files.

func (SyntheticCandleConfig) GenerateSyntheticYearlyCandles

func (cfg SyntheticCandleConfig) GenerateSyntheticYearlyCandles(year int) ([]*candleSet, error)

GenerateSyntheticYearlyCandles generates a full year of monthly candle sets.

type Tick

type Tick struct {
	Instrument string
	Timestamp  Timestamp
	BA
}

func (Tick) Mid

func (t Tick) Mid() Price

func (Tick) Spread

func (t Tick) Spread() Price

type TimeRange

type TimeRange struct {
	Start Timestamp // inclusive
	End   Timestamp // exclusive
	TF    Timeframe // m1, h1, d1
}

func ParseTimeRange added in v0.2.0

func ParseTimeRange(from, to, tf string) (TimeRange, error)

ParseTimeRange parses a TimeRange from "YYYY-MM-DD" from/to strings and a timeframe string ("M1", "H1", "D1"). Exported for use by sibling packages.

func (TimeRange) Contains

func (r TimeRange) Contains(ts Timestamp) bool

func (TimeRange) Covers

func (r TimeRange) Covers(other TimeRange) bool

func (TimeRange) MonthsInRange

func (r TimeRange) MonthsInRange() []yearMonth

func (TimeRange) Overlaps

func (r TimeRange) Overlaps(other TimeRange) bool

func (TimeRange) String

func (r TimeRange) String() string

func (TimeRange) Valid

func (r TimeRange) Valid() bool

type Timeframe

type Timeframe int64

******************************************************************** Timeframe ********************************************************************

const (
	TF0   Timeframe = 0
	Ticks Timeframe = 1
	M1    Timeframe = 60
	H1    Timeframe = 3600
	D1    Timeframe = 86400
)

func (Timeframe) String

func (tf Timeframe) String() string

type Timestamp

type Timestamp int64

func FromString

func FromString(s string) Timestamp

func FromTime

func FromTime(t time.Time) Timestamp

func (Timestamp) Add

func (t Timestamp) Add(d time.Duration) Timestamp

func (Timestamp) After

func (t Timestamp) After(ts Timestamp) bool

func (Timestamp) Before

func (t Timestamp) Before(ts Timestamp) bool

func (Timestamp) FloorToHour

func (s Timestamp) FloorToHour() Timestamp

func (Timestamp) FloorToMinute

func (s Timestamp) FloorToMinute() Timestamp

Flooring (bar opens)

func (Timestamp) Int64

func (t Timestamp) Int64() int64

func (Timestamp) IsZero

func (t Timestamp) IsZero() bool

func (Timestamp) MS

func (s Timestamp) MS() timemilli

func (Timestamp) Milli

func (t Timestamp) Milli() timemilli

func (Timestamp) String

func (t Timestamp) String() string

func (Timestamp) Time

func (t Timestamp) Time() time.Time

type Trade

type Trade struct {
	*TradeCommon
	EntryPrice Price
	EntryTime  Timestamp
	ExitPrice  Price
	ExitTime   Timestamp
	PNL        Money // account currency (best-effort)
	CloseCause closeCause
}

type TradeCommon

type TradeCommon struct {
	ID         string
	Instrument string
	Side       // Long or Short
	Units
	Stop Price
	Take Price
}

type TradeHistory

type TradeHistory struct {
	*TradeCommon
	*OpenRequest
}

func NewTradeHistory

func NewTradeHistory(inst string) *TradeHistory

type TradeRecord

type TradeRecord struct {
	TradeID    string
	Instrument string
	Units      Units
	EntryPrice Price
	ExitPrice  Price
	OpenTime   Timestamp
	CloseTime  Timestamp
	RealizedPL Money
	Reason     string
}

This could go into trade or market

type Trader

type Trader struct {
	*DataManager
	*Broker
	*Store
}

func (*Trader) Backtest

func (t *Trader) Backtest(ctx context.Context, run *Backtest) error

type Units

type Units int64

func (Units) Int64

func (u Units) Int64() int64

func (Units) String

func (u Units) String() string

type Want

type Want struct {
	Key
	WantReason
}

type WantReason

type WantReason string
const (
	WantMissing    WantReason = "missing"
	WantIncomplete WantReason = "incomplete"
	WantStale      WantReason = "stale"
)

type Wantlist

type Wantlist struct {
	// contains filtered or unexported fields
}

func NewWantlist

func NewWantlist() *Wantlist

func (*Wantlist) Delete

func (wl *Wantlist) Delete(key Key)

func (*Wantlist) Get

func (wl *Wantlist) Get(key Key) (Want, bool)

func (*Wantlist) Has

func (wl *Wantlist) Has(key Key) bool

func (*Wantlist) Keys

func (wl *Wantlist) Keys() []Key

func (*Wantlist) Len

func (wl *Wantlist) Len() int

func (*Wantlist) List

func (wl *Wantlist) List() []Want

func (*Wantlist) Put

func (wl *Wantlist) Put(w Want)

func (*Wantlist) Update

func (wl *Wantlist) Update(key Key, fn func(*Want) error) error

type WorkState

type WorkState struct {
	// contains filtered or unexported fields
}

WorkState tracks which downloads and build tasks are currently queued or running, preventing duplicate work from being scheduled.

func NewWorkState

func NewWorkState() *WorkState

NewWorkState returns an empty WorkState with initialised internal maps.

func (*WorkState) ClearBuild

func (ws *WorkState) ClearBuild(k Key)

ClearBuild removes k from the active-builds set (call on completion or error).

func (*WorkState) ClearDownload

func (ws *WorkState) ClearDownload(k Key)

ClearDownload removes k from the active-downloads set (call on completion or error).

func (*WorkState) IsBuildQueuedOrActive

func (ws *WorkState) IsBuildQueuedOrActive(k Key) bool

IsBuildQueuedOrActive reports whether a build for k is already tracked.

func (*WorkState) IsDownloadQueuedOrActive

func (ws *WorkState) IsDownloadQueuedOrActive(k Key) bool

IsDownloadQueuedOrActive reports whether a download for k is already tracked.

func (*WorkState) MarkBuild

func (ws *WorkState) MarkBuild(k Key)

MarkBuild registers k as an active build.

func (*WorkState) MarkDownload

func (ws *WorkState) MarkDownload(k Key)

MarkDownload registers k as an active download.

Directories

Path Synopsis
api
mcp
Package mcp implements an MCP (Model Context Protocol) server over the service layer.
Package mcp implements an MCP (Model Context Protocol) server over the service layer.
rest
Package rest is the HTTP presentation layer over the service package.
Package rest is the HTTP presentation layer over the service package.
brokers
sim
cmd
api
Package api hosts the CLI command for starting the REST API server.
Package api hosts the CLI command for starting the REST API server.
gen-testdata command
live
Package live hosts CLI commands for the live trading subsystem.
Package live hosts CLI commands for the live trading subsystem.
mcp
Package mcp hosts the CLI command for starting the MCP server.
Package mcp hosts the CLI command for starting the MCP server.
order
Package order hosts CLI subcommands for live order management.
Package order hosts CLI subcommands for live order management.
serve
Package serve implements "trader serve" — the long-running daemon mode.
Package serve implements "trader serve" — the long-running daemon mode.
Package data defines the Provider interface implemented by every market-data source (Dukascopy, OANDA, future Polygon/IBKR, etc.).
Package data defines the Provider interface implemented by every market-data source (Dukascopy, OANDA, future Polygon/IBKR, etc.).
dukascopy
Package dukascopy implements the data.Provider interface for Dukascopy historical tick files.
Package dukascopy implements the data.Provider interface for Dukascopy historical tick files.
Package service is the protocol-agnostic business-logic layer.
Package service is the protocol-agnostic business-logic layer.
strategies
donchian
Package donchian implements the Donchian breakout strategy with close-strength confirmation.
Package donchian implements the Donchian breakout strategy with close-strength confirmation.
emacross
Package emacross implements the fast/slow EMA crossover strategy.
Package emacross implements the fast/slow EMA crossover strategy.
emacrossadx
Package emacrossadx implements the EMA-cross strategy with an ADX trend-strength gate.
Package emacrossadx implements the EMA-cross strategy with an ADX trend-strength gate.
fake
Package fake contains canned deterministic strategies used by trader's integration and lifecycle tests.
Package fake contains canned deterministic strategies used by trader's integration and lifecycle tests.
lifecycle
Package lifecycle is a deterministic canned strategy used to regression-test the full config→candles→strategy→Trader→Broker→Account→Trades→Result pipeline.
Package lifecycle is a deterministic canned strategy used to regression-test the full config→candles→strategy→Trader→Broker→Account→Trades→Result pipeline.
noop
Package noop implements a do-nothing strategy.
Package noop implements a do-nothing strategy.
pulse
Package pulse provides a mechanical live-trading strategy that opens and closes positions on a fixed schedule.
Package pulse provides a mechanical live-trading strategy that opens and closes positions on a fixed schedule.
tmpl
Package tmpl is a strategy template / starting point for new strategy implementations.
Package tmpl is a strategy template / starting point for new strategy implementations.
Package ui exposes the compiled SvelteKit front-end as an embed.FS.
Package ui exposes the compiled SvelteKit front-end as an embed.FS.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL