How AI coding agents should work in this codebase.
Two things a change has to respect, neither of which is obvious from the file it touches:
- C++17 is the baseline, not a floor to build on.
CMakeLists.txtdefaultsCMAKE_CXX_STANDARDto 17 andconfigure.acchecks for it. CI additionally compiles under newer language modes up to C++26, so a post-C++17 construct will pass some matrices and fail the default build. - Three build systems are exercised in CI — CMake, Autotools, and a Visual
Studio solution. A new source file has to be registered in all of them, or
filelists.ymlfails on the pull request. See.agents/registering-new-files.md.
Task-specific detail lives in .agents/. Read one when the
task calls for it:
| Read this | When |
|---|---|
.agents/architecture.md |
You need the instrument/engine lifecycle, the recalculation triggers, the cash-flow hierarchy, or the design patterns. |
.agents/build-and-test.md |
You need build options, configure flags, test invocations, or the CI workflow map. |
.agents/registering-new-files.md |
You add, rename, or remove a .hpp/.cpp file. |
.agents/extending-quantlib.md |
You add a new instrument, term structure, calendar, or day counter. |
.agents/deprecation.md |
You deprecate, change, or remove a public API. |
.agents/maintaining-agent-docs.md |
You edit this file or anything in .agents/. |
Source of truth: .clang-format.
- 4-space indent, no tabs.
- 100-column limit.
- Namespace indentation enabled.
- Pointer/reference alignment:
T* p,T& x. - Include order: local (
"...") →<ql/...>→<boost/...>→ standard headers.
- Types/classes:
PascalCase - Functions/methods:
lowerCamelCase - Data members: trailing underscore (
engine_,calculated_) - Macros:
QL_UPPER_CASE - Prefer
constcorrectness and pass heavy objects byconst&.
Use the QuantLib portability aliases in ql/shared_ptr.hpp:
ext::shared_ptr<T>ext::make_shared<T>(...)ext::dynamic_pointer_cast<T>(...)
Use std::unique_ptr for strict local ownership in implementations.
Use the ql/errors.hpp macros, not raw throw/assert:
QL_REQUIREQL_ENSUREQL_FAILQL_ASSERT
New headers should have:
- The standard QuantLib license block
- Doxygen
\fileand brief - Include guard
quantlib_<name>_hpp - Self-contained includes
cmake --preset linux-gcc-ninja-release
cmake --build build/linux-gcc-ninja-release
./build/linux-gcc-ninja-release/test-suite/quantlib-test-suite \
--log_level=messageBuild options, Autotools and MSVC instructions, targeted test invocations, and
the CI workflow map are in
.agents/build-and-test.md.
These mostly fail silently — a wrong number or undefined behavior rather than a build error, so no compiler or test will point at them for you.
Settings::evaluationDate()is global by default; with sessions enabled it becomes per-session/per-thread.- Test cases do not need their own
SavedSettings.TopLevelFixture(test-suite/toplevelfixture.hpp) already holds one, so settings are restored after every case.
Instrument::NPV() returns a cached result and re-runs the pricing engine only
after an observed dependency has notified a change. Most of the non-obvious
behavior in this library follows from that:
- Result accessors do not recompute on demand.
NPV()and its siblings return whatever the lastperformCalculations()cached. When a changed input does not move a price, the cause is almost always a missing notification, not a wrong formula. - Anything a price depends on has to arrive as an observable. A data member
holding a value copied at construction will never invalidate the cache; take a
Handle<Quote>or a term-structure handle andregisterWith()it. - Notification forwarding behavior can differ (default/per-object, compile-time macro influence).
- Cycle handling can be silent unless
QL_THROW_IN_CYCLESis enabled. - Misused
freeze()can leave stale values.
Before changing anything that touches caching, notification, or curve
construction, read .agents/architecture.md.
Handle<T>defaults toregisterAsObserver=true.- If a handle does not own the pointee safely, observer callbacks can outlive valid memory.
- Relinking a
RelinkableHandlecan trigger broad recalculation cascades.
- 30/360 variants differ materially.
- Actual/Actual ISMA requires schedule context for irregular periods.
Calendar::isEndOfMonth()is business-day aware, not raw month-end.
- Prefer robust solvers (often Brent) when derivatives are unavailable or noisy.
- Set realistic tolerances and max evaluations.
- Validate implied-vol and calibration outputs against known references.
ql/types.hpp aliases are macro-backed (QL_REAL, QL_INTEGER,
QL_BIG_INTEGER in ql/qldefines.hpp) and configurable; do not hard-code
assumptions beyond the defaults.
Before finishing a change:
- Build passes with no new warnings.
- Relevant tests pass.
- New behavior has tests.
- New price inputs are observables the object registered with, and a test shows that changing one moves the result (see 5.2).
- Build lists are updated where needed (
ql/*,test-suite/*). - Visual Studio project/filter files are updated for new files
(
QuantLib.vcxproj*,test-suite/testsuite.vcxproj*). - Headers are self-contained.
- Error handling uses
QL_*macros. - Pointer types use
ext::shared_ptrconventions where appropriate. - Numerical tolerances are justified for quant outputs.