Special Relativity in Financial Modeling 1.0.0
Lorentz transforms, spacetime classification, and geodesic price paths for quantitative finance
Loading...
Searching...
No Matches
portfolio_manifold.cpp
Go to the documentation of this file.
1/// @file src/portfolio_manifold.cpp
2/// @brief Implementation of MinkowskiCovariance and SpacetimeCausalGraph.
3///
4/// See include/portfolio_manifold.hpp for the public API contract.
5
7
8#include "srfm/constants.hpp"
9
10#include <Eigen/Dense>
11#include <algorithm>
12#include <cmath>
13#include <stdexcept>
14
15namespace srfm::portfolio {
16
17// ─── AssetEvent helpers ───────────────────────────────────────────────────────
18
19Eigen::Vector4d AssetEvent::to_four_vector() const noexcept {
20 Eigen::Vector4d v;
21 v << t, P, V, M;
22 return v;
23}
24
26 // Map the 4-coord event (t, P, V, M) to an NAssetEvent with prices = [P, V, M].
27 Eigen::VectorXd prices(3);
28 prices << P, V, M;
29 // NAssetEvent::make validates non-empty prices; always succeeds here.
30 auto opt = manifold::NAssetEvent::make(t, prices);
31 // Fallback: construct manually if make fails (should never happen for valid data).
32 if (opt) {
33 return *opt;
34 }
35 // Return a default-constructed event with single zero price to satisfy noexcept.
36 Eigen::VectorXd fallback(1);
37 fallback << 0.0;
38 return manifold::NAssetEvent{t, fallback};
39}
40
41// ─── MinkowskiCovariance ──────────────────────────────────────────────────────
42
44 : c_market_(c_market) {}
45
47 events_.push_back(std::move(event));
48}
49
50std::size_t MinkowskiCovariance::size() const noexcept {
51 return events_.size();
52}
53
54const std::vector<AssetEvent>& MinkowskiCovariance::events() const noexcept {
55 return events_;
56}
57
59 events_.clear();
60}
61
62double MinkowskiCovariance::raw_interval(std::size_t i,
63 std::size_t j) const noexcept {
64 // ds² = -c²·Δt² + ΔP² + ΔV² + ΔM²
65 const AssetEvent& a = events_[i];
66 const AssetEvent& b = events_[j];
67
68 const double dt = b.t - a.t;
69 const double dP = b.P - a.P;
70 const double dV = b.V - a.V;
71 const double dM = b.M - a.M;
72
73 return -(c_market_ * c_market_) * (dt * dt)
74 + (dP * dP)
75 + (dV * dV)
76 + (dM * dM);
77}
78
79std::optional<double>
81 std::size_t j) const noexcept {
82 if (i >= events_.size() || j >= events_.size()) {
83 return std::nullopt;
84 }
85 return raw_interval(i, j);
86}
87
88std::optional<manifold::IntervalType>
89MinkowskiCovariance::classify_pair(std::size_t i, std::size_t j) const noexcept {
90 if (i >= events_.size() || j >= events_.size()) {
91 return std::nullopt;
92 }
93 const double ds2 = raw_interval(i, j);
94 if (std::abs(ds2) < manifold::LIGHTLIKE_THRESHOLD) {
96 }
97 return ds2 < 0.0 ? manifold::IntervalType::TIMELIKE
99}
100
101std::optional<Eigen::MatrixXd>
103 const std::size_t N = events_.size();
104 if (N < 2) {
105 return std::nullopt;
106 }
107
108 Eigen::MatrixXd cov(static_cast<int>(N), static_cast<int>(N));
109
110 for (std::size_t i = 0; i < N; ++i) {
111 for (std::size_t j = 0; j < N; ++j) {
112 if (i == j) {
113 // Diagonal: perfect self-correlation.
114 cov(static_cast<int>(i), static_cast<int>(j)) = 1.0;
115 } else {
116 // Off-diagonal: Gaussian kernel over the spacetime interval.
117 // C(i,j) = exp(−|ds²(i,j)|)
118 // - TIMELIKE (ds² << 0): |ds²| large → C → 0
119 // - LIGHTLIKE (ds² ≈ 0): |ds²| ≈ 0 → C → 1
120 // - SPACELIKE (ds² >> 0): |ds²| large → C → 0
121 const double ds2 = raw_interval(i, j);
122 cov(static_cast<int>(i), static_cast<int>(j)) =
123 std::exp(-std::abs(ds2));
124 }
125 }
126 }
127
128 return cov;
129}
130
131// ─── SpacetimeCausalGraph ─────────────────────────────────────────────────────
132
133SpacetimeCausalGraph::SpacetimeCausalGraph(Eigen::MatrixXi adjacency,
134 std::vector<std::string> ids) noexcept
135 : adj_(std::move(adjacency))
136 , asset_ids_(std::move(ids)) {}
137
138std::optional<SpacetimeCausalGraph>
140 const std::size_t N = mc.size();
141 if (N < 2) {
142 return std::nullopt;
143 }
144
145 const int n = static_cast<int>(N);
146 Eigen::MatrixXi adj = Eigen::MatrixXi::Zero(n, n);
147
148 for (std::size_t i = 0; i < N; ++i) {
149 for (std::size_t j = 0; j < N; ++j) {
150 if (i == j) {
151 // No self-loops.
152 continue;
153 }
154 // Edge (i→j) exists iff asset_i is TIMELIKE-separated from asset_j.
155 // TIMELIKE means ds²(i,j) < −LIGHTLIKE_THRESHOLD.
156 auto ds2_opt = mc.interval_correlation(i, j);
157 if (!ds2_opt) {
158 continue;
159 }
160 const double ds2 = *ds2_opt;
162 // TIMELIKE separation: causal influence i → j.
163 adj(static_cast<int>(i), static_cast<int>(j)) = 1;
164 }
165 }
166 }
167
168 // Collect asset IDs.
169 std::vector<std::string> ids;
170 ids.reserve(N);
171 for (const auto& ev : mc.events()) {
172 ids.push_back(ev.asset_id);
173 }
174
175 return SpacetimeCausalGraph(std::move(adj), std::move(ids));
176}
177
178const Eigen::MatrixXi& SpacetimeCausalGraph::adjacency_matrix() const noexcept {
179 return adj_;
180}
181
182bool SpacetimeCausalGraph::has_edge(std::size_t i, std::size_t j) const noexcept {
183 if (i >= static_cast<std::size_t>(adj_.rows()) ||
184 j >= static_cast<std::size_t>(adj_.cols())) {
185 return false;
186 }
187 return adj_(static_cast<int>(i), static_cast<int>(j)) != 0;
188}
189
190std::size_t SpacetimeCausalGraph::n_assets() const noexcept {
191 return static_cast<std::size_t>(adj_.rows());
192}
193
194int SpacetimeCausalGraph::out_degree(std::size_t i) const noexcept {
195 if (i >= static_cast<std::size_t>(adj_.rows())) {
196 return 0;
197 }
198 return adj_.row(static_cast<int>(i)).sum();
199}
200
201int SpacetimeCausalGraph::in_degree(std::size_t j) const noexcept {
202 if (j >= static_cast<std::size_t>(adj_.cols())) {
203 return 0;
204 }
205 return adj_.col(static_cast<int>(j)).sum();
206}
207
208const std::vector<std::string>& SpacetimeCausalGraph::asset_ids() const noexcept {
209 return asset_ids_;
210}
211
212} // namespace srfm::portfolio
std::optional< Eigen::MatrixXd > compute_spacetime_covariance() const noexcept
std::size_t size() const noexcept
Return the number of asset events currently stored.
std::optional< manifold::IntervalType > classify_pair(std::size_t i, std::size_t j) const noexcept
std::optional< double > interval_correlation(std::size_t i, std::size_t j) const noexcept
const std::vector< AssetEvent > & events() const noexcept
Read-only access to the stored asset events.
void clear() noexcept
Clear all stored events.
MinkowskiCovariance(double c_market=constants::SPEED_OF_INFORMATION) noexcept
std::size_t n_assets() const noexcept
Return the number of assets (graph nodes).
const Eigen::MatrixXi & adjacency_matrix() const noexcept
const std::vector< std::string > & asset_ids() const noexcept
Return asset IDs in the order they were added.
int out_degree(std::size_t i) const noexcept
Return the out-degree of node i (number of assets that i causally precedes).
static std::optional< SpacetimeCausalGraph > build(const MinkowskiCovariance &mc) noexcept
bool has_edge(std::size_t i, std::size_t j) const noexcept
int in_degree(std::size_t j) const noexcept
Return the in-degree of node j (number of assets that causally precede j).
Physical and financial constants for the SRFM system.
@ TIMELIKE
ds² < 0 (causal separation; price change < time*c_market).
@ LIGHTLIKE
|ds²| < threshold (on the light cone).
@ SPACELIKE
ds² > 0 (space-like; price change > time*c_market).
constexpr double LIGHTLIKE_THRESHOLD
|ds²| below this value is classified as LIGHTLIKE.
N-Asset Minkowski Covariance Matrix and Spacetime Causal Graph.
A spacetime event: a moment in time with N asset prices.
static std::optional< NAssetEvent > make(double t, Eigen::VectorXd prices) noexcept
Factory: validate and construct an NAssetEvent.
Eigen::Vector4d to_four_vector() const noexcept
Convert to a 4-vector [t, P, V, M] compatible with the 4D manifold.
manifold::NAssetEvent to_nasset_event() const noexcept
double M
Market-cap coordinate (spatial axis 3)
double P
Price coordinate (spatial axis 1)
double V
Volume coordinate (spatial axis 2)
double t
Market time coordinate (bar index or epoch seconds)