Packages
The problem
main.go is 2,000 lines. Finding anything means scrolling. Two unrelated things share a variable name and you didn't notice.
One directory, one package
Go's rule is simple: a directory is a package. Every .go file in it declares the same package name at the top.
tradebot/
├── go.mod
├── main.go package main
├── market/
│ ├── bar.go package market
│ ├── ring.go package market
│ └── indicators.go package market
├── strategy/
│ ├── strategy.go package strategy
│ └── emacross.go package strategy
└── backtest/
├── engine.go package backtest
└── result.go package backtestFiles in the same package see each other's names directly - no imports between bar.go and ring.go. Across packages you import.
Starting a module
mkdir tradebot && cd tradebot
go mod init github.com/yourname/tradebotThat creates go.mod holding the module path and Go version. The module path prefixes every import inside the project:
package main
import (
"fmt"
"github.com/yourname/tradebot/market"
"github.com/yourname/tradebot/strategy"
)
func main() {
bars := market.LoadCSV("prices.csv")
s := strategy.NewEmaCross(13, 21)
fmt.Println(s.Name(), len(bars))
}You reach things through the package name: market.Bar, strategy.NewEmaCross. It doesn't have to be a real GitHub URL unless you publish it - but using that form means you can, later, without renaming everything.
(That snippet won't compile on its own, of course: it imports packages you haven't written yet. It's showing the shape, and exercise 15.1 has you build the real thing.)
Standard-library imports go in one group, yours in another, separated by a blank line. gofmt maintains this.
Capitals decide what's visible
Chapter 10 mentioned this; here's where it earns its keep.
package market
type Bar struct {
Open float64 // visible outside
Close float64 // visible outside
rawLine string // private to package market
}
func LoadCSV(path string) ([]Bar, error) { ... } // visible
func parseRow(row []string) (Bar, error) { ... } // privateOutside code can call market.LoadCSV but not market.parseRow. That's your API boundary, expressed with the shift key.
Design tip: start everything lowercase. Export a thing only when something outside genuinely needs it. A small public surface is a package you can change later without breaking anyone.
The internal directory
There's one more level of privacy. Anything under a directory named internal can only be imported by code within the same module:
tradebot/
├── internal/
│ └── testdata/ importable only inside tradebotUseful for helpers you want to share across your own packages but never expose publicly.
No circular imports
If market imports strategy, then strategy cannot import market. Go refuses to compile it.
This feels restrictive until you realise what it prevents. A circular dependency means two packages are really one package that hasn't admitted it, and untangling them later is miserable. Go makes you deal with it immediately.
When you hit one, the usual fixes are: move the shared type into a third package that both import, or define an interface in the consumer so the dependency only points one way. That second option is Chapter 14 solving an architecture problem - the backtester defines Strategy, so backtest never needs to import strategy.
Documentation is just comments
A comment immediately above a declaration, starting with its name, is its documentation:
// Bar is a single OHLCV price bar. Timestamps are always UTC.
type Bar struct { ... }
// MovingAverage computes a simple moving average over a fixed window
// using a ring buffer, at constant cost per update regardless of
// window size.
type MovingAverage struct { ... }See It Work: read your own docs
go doc ./market
go doc ./market Bar
go doc ./market.MovingAverage.UpdateYour comments come back formatted. This is the same mechanism behind every Go package's documentation online - there's no separate doc system, no annotations, just comments in the right place.
Try it after writing a package. Seeing your own API listed back at you is a surprisingly good way to notice that you exported something you shouldn't have.
See It Work: what depends on what
go list -deps ./... | grep tradebotPrints your packages in dependency order. If the output surprises you - market depending on something you didn't expect - you've found accidental coupling.
A layout for the capstone
tradebot/
├── go.mod
├── cmd/
│ └── backtest/
│ └── main.go the runnable program
├── market/ Bar, ring buffer, indicators, CSV loading
├── strategy/ Strategy implementations
└── backtest/ the engine, costs, sizing, resultscmd/ holding the entry point is a widespread convention, and it scales: add cmd/livetrade/ later and both share every other package.
Direction of dependency: cmd → backtest → market, and strategy → market. Nothing points back up. If you find yourself wanting an arrow the other way, that's the signal to extract an interface.
Exercises
15.1 Split your existing code into market, strategy and backtest packages with a cmd/backtest/main.go. Get it compiling.
15.2 Go through market and make private everything that doesn't need to be public. How much of your API disappears?
15.3 Write doc comments for every exported name in market, then read them back with go doc. Fix any that don't make sense out of context.
15.4 Deliberately create a circular import and read the error. Then fix it by defining an interface in the consuming package.
15.5 Run go list -deps ./... and draw the dependency graph on paper. Any surprises?
15.6 Harder. Add internal/testdata with a small fixed CSV, and have your tests load from it. Confirm that code outside the module can't import it.