Skip to content

Commit 26e6a2e

Browse files
author
Alexander Robbins
committed
Merge: keeping local changes
2 parents 5ea4af1 + 7d7a0d9 commit 26e6a2e

4 files changed

Lines changed: 66 additions & 63 deletions

File tree

include/lfmc/adaptive_estimator.hpp

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,8 @@ namespace detail {
116116
// Two calls with the same (k, p) give the same seed; different (k, p) pairs are
117117
// statistically independent because they hit different streams of splitmix64.
118118
inline uint64_t make_seed(size_t k, size_t phase) noexcept {
119-
uint64_t x = (static_cast<uint64_t>(k) * 0x9e3779b97f4a7c15ULL)
120-
^ (static_cast<uint64_t>(phase) * 0x6c62272e07bb0142ULL)
121-
^ 0xdeadbeefcafe0000ULL;
119+
uint64_t x = (static_cast<uint64_t>(k) * 0x9e3779b97f4a7c15ULL) ^
120+
(static_cast<uint64_t>(phase) * 0x6c62272e07bb0142ULL) ^ 0xdeadbeefcafe0000ULL;
122121
x ^= x >> 30;
123122
x *= 0xbf58476d1ce4e5b9ULL;
124123
x ^= x >> 27;
@@ -184,8 +183,8 @@ inline std::pair<double, double> mean_variance(const std::vector<double>& sample
184183

185184
// Plain pseudo-random Monte Carlo - baseline strategy
186185
template <StochasticProcess SP, NumericalScheme<SP> NS>
187-
SamplerFn make_plain_mc_sampler(SP process, NS scheme, std::shared_ptr<Payoff> payoff,
188-
size_t steps, double T) {
186+
SamplerFn make_plain_mc_sampler(SP process, NS scheme, std::shared_ptr<Payoff> payoff, size_t steps,
187+
double T) {
189188
return [process, scheme, payoff, steps, T](size_t n, uint64_t seed) -> std::vector<double> {
190189
std::mt19937_64 rng{seed};
191190
std::vector<double> samples;
@@ -206,7 +205,7 @@ SamplerFn make_plain_mc_sampler(SP process, NS scheme, std::shared_ptr<Payoff> p
206205
// because the two paths are negatively correlated for monotone payoffs.
207206
template <StochasticProcess SP, NumericalScheme<SP> NS>
208207
SamplerFn make_antithetic_sampler(SP process, NS scheme, std::shared_ptr<Payoff> payoff,
209-
size_t steps, double T) {
208+
size_t steps, double T) {
210209
return [process, scheme, payoff, steps, T](size_t n, uint64_t seed) -> std::vector<double> {
211210
std::mt19937_64 rng{seed};
212211
std::vector<double> samples;
@@ -238,8 +237,8 @@ SamplerFn make_antithetic_sampler(SP process, NS scheme, std::shared_ptr<Payoff>
238237
// Returns: X_i - beta_hat * (Y_i - E[Y]) for each i
239238
template <StochasticProcess SP, NumericalScheme<SP> NS>
240239
SamplerFn make_control_variate_sampler(SP process, NS scheme, std::shared_ptr<Payoff> target,
241-
std::shared_ptr<Payoff> control, double control_mean,
242-
size_t steps, double T) {
240+
std::shared_ptr<Payoff> control, double control_mean,
241+
size_t steps, double T) {
243242
return [process, scheme, target, control, control_mean, steps,
244243
T](size_t n, uint64_t seed) -> std::vector<double> {
245244
std::mt19937_64 rng{seed};
@@ -287,8 +286,8 @@ SamplerFn make_control_variate_sampler(SP process, NS scheme, std::shared_ptr<Pa
287286
// Each raw sample is the antithetic average; OLS beta is estimated from those averages.
288287
template <StochasticProcess SP, NumericalScheme<SP> NS>
289288
SamplerFn make_antithetic_cv_sampler(SP process, NS scheme, std::shared_ptr<Payoff> target,
290-
std::shared_ptr<Payoff> control, double control_mean,
291-
size_t steps, double T) {
289+
std::shared_ptr<Payoff> control, double control_mean,
290+
size_t steps, double T) {
292291
return [process, scheme, target, control, control_mean, steps,
293292
T](size_t n, uint64_t seed) -> std::vector<double> {
294293
std::mt19937_64 rng{seed};

src/payoff/asian_payoffs.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ AsianCall::generate_payoffs(const std::vector<Path>& paths) const {
2020
if (path.empty())
2121
return std::unexpected("Empty path encountered in AsianCall");
2222

23-
double mean =
24-
std::reduce(path.begin(), path.end(), 0.0) / static_cast<double>(path.size());
23+
double mean = std::reduce(path.begin(), path.end(), 0.0) / static_cast<double>(path.size());
2524
payoffs.push_back(std::max(mean - strike_, 0.0));
2625
}
2726

@@ -39,8 +38,7 @@ AsianPut::generate_payoffs(const std::vector<Path>& paths) const {
3938
if (path.empty())
4039
return std::unexpected("Empty path encountered in AsianPut");
4140

42-
double mean =
43-
std::reduce(path.begin(), path.end(), 0.0) / static_cast<double>(path.size());
41+
double mean = std::reduce(path.begin(), path.end(), 0.0) / static_cast<double>(path.size());
4442
payoffs.push_back(std::max(strike_ - mean, 0.0));
4543
}
4644

tests/bench_asvr_vs_fixed.cpp

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424

2525
using namespace lfmc;
2626

27-
static constexpr double S0 = 100.0;
28-
static constexpr double MU = 0.05;
29-
static constexpr double SIGMA = 0.2;
30-
static constexpr double K = 100.0;
31-
static constexpr double T = 1.0;
32-
static constexpr size_t STEPS = 52;
27+
static constexpr double S0 = 100.0;
28+
static constexpr double MU = 0.05;
29+
static constexpr double SIGMA = 0.2;
30+
static constexpr double K = 100.0;
31+
static constexpr double T = 1.0;
32+
static constexpr size_t STEPS = 52;
3333
static const double CONTROL_MEAN = S0 * std::exp(MU * T);
3434

3535
static GeometricBrownianMotion GBM{MU, SIGMA, S0};
@@ -43,8 +43,8 @@ struct BenchResult {
4343
double std_error_of_mean;
4444
};
4545

46-
BenchResult bench_fixed(const std::string& name, SamplerFn sampler,
47-
double ref_mean, size_t N, size_t RUNS) {
46+
BenchResult bench_fixed(const std::string& name, SamplerFn sampler, double ref_mean, size_t N,
47+
size_t RUNS) {
4848
std::vector<double> errors;
4949
errors.reserve(RUNS);
5050
double sum_est = 0.0;
@@ -60,13 +60,15 @@ BenchResult bench_fixed(const std::string& name, SamplerFn sampler,
6060
// std error of the MSE estimate (rough)
6161
double mean_err2 = mse;
6262
double sq_sum = 0.0;
63-
for (double e2 : errors) { double d = e2 - mean_err2; sq_sum += d*d; }
64-
double sem = std::sqrt(sq_sum / static_cast<double>(RUNS * (RUNS-1)));
63+
for (double e2 : errors) {
64+
double d = e2 - mean_err2;
65+
sq_sum += d * d;
66+
}
67+
double sem = std::sqrt(sq_sum / static_cast<double>(RUNS * (RUNS - 1)));
6568
return {name, mse, sum_est / RUNS, sem};
6669
}
6770

68-
BenchResult bench_asvr(std::shared_ptr<Payoff> payoff, double ref_mean,
69-
size_t N, size_t RUNS) {
71+
BenchResult bench_asvr(std::shared_ptr<Payoff> payoff, double ref_mean, size_t N, size_t RUNS) {
7072
std::vector<double> errors;
7173
errors.reserve(RUNS);
7274
double sum_est = 0.0;
@@ -83,20 +85,23 @@ BenchResult bench_asvr(std::shared_ptr<Payoff> payoff, double ref_mean,
8385
double mse = std::accumulate(errors.begin(), errors.end(), 0.0) / static_cast<double>(RUNS);
8486
double mean_err2 = mse;
8587
double sq_sum = 0.0;
86-
for (double e2 : errors) { double d = e2 - mean_err2; sq_sum += d*d; }
87-
double sem = std::sqrt(sq_sum / static_cast<double>(RUNS * (RUNS-1)));
88+
for (double e2 : errors) {
89+
double d = e2 - mean_err2;
90+
sq_sum += d * d;
91+
}
92+
double sem = std::sqrt(sq_sum / static_cast<double>(RUNS * (RUNS - 1)));
8893
return {"ASVR", mse, sum_est / RUNS, sem};
8994
}
9095

91-
void run_option_bench(const std::string& opt_name, std::shared_ptr<Payoff> payoff,
92-
size_t N, size_t RUNS) {
96+
void run_option_bench(const std::string& opt_name, std::shared_ptr<Payoff> payoff, size_t N,
97+
size_t RUNS) {
9398
printf("\n=== %-35s (N=%zu, runs=%zu) ===\n", opt_name.c_str(), N, RUNS);
9499

95100
// Reference from 500k plain MC samples
96101
auto plain_ref_fn = make_plain_mc_sampler(GBM, EULER, payoff, STEPS, T);
97102
auto ref_samples = plain_ref_fn(500'000, detail::make_seed(0, 99));
98-
double ref_mean = std::accumulate(ref_samples.begin(), ref_samples.end(), 0.0)
99-
/ static_cast<double>(ref_samples.size());
103+
double ref_mean = std::accumulate(ref_samples.begin(), ref_samples.end(), 0.0) /
104+
static_cast<double>(ref_samples.size());
100105
printf(" Reference mean: %.6f\n", ref_mean);
101106

102107
auto strategies = build_all_strategies(GBM, EULER, payoff, CONTROL_MEAN, STEPS, T);
@@ -111,47 +116,49 @@ void run_option_bench(const std::string& opt_name, std::shared_ptr<Payoff> payof
111116
results.push_back(bench_asvr(payoff, ref_mean, N, RUNS));
112117

113118
// Sort by MSE ascending
114-
std::sort(results.begin(), results.end(),
115-
[](const BenchResult& a, const BenchResult& b) {
116-
return a.mean_sq_error < b.mean_sq_error;
117-
});
119+
std::sort(results.begin(), results.end(), [](const BenchResult& a, const BenchResult& b) {
120+
return a.mean_sq_error < b.mean_sq_error;
121+
});
118122

119123
double plain_mse = 0.0;
120-
for (auto& r : results) if (r.name == "plain_mc") plain_mse = r.mean_sq_error;
124+
for (auto& r : results)
125+
if (r.name == "plain_mc")
126+
plain_mse = r.mean_sq_error;
121127

122128
printf(" %-28s %12s %8s %8s\n", "Strategy", "MSE", "vs PlainMC", "vs ASVR");
123-
printf(" %-28s %12s %8s %8s\n",
124-
std::string(28,'-').c_str(), std::string(12,'-').c_str(),
125-
std::string(8,'-').c_str(), std::string(8,'-').c_str());
129+
printf(" %-28s %12s %8s %8s\n", std::string(28, '-').c_str(), std::string(12, '-').c_str(),
130+
std::string(8, '-').c_str(), std::string(8, '-').c_str());
126131

127132
double asvr_mse = 0.0;
128-
for (auto& r : results) if (r.name == "ASVR") asvr_mse = r.mean_sq_error;
133+
for (auto& r : results)
134+
if (r.name == "ASVR")
135+
asvr_mse = r.mean_sq_error;
129136

130137
for (auto& r : results) {
131138
double vs_plain = plain_mse / r.mean_sq_error;
132-
double vs_asvr = asvr_mse / r.mean_sq_error;
133-
printf(" %-28s %12.6f %8.3fx %8.3fx\n",
134-
r.name.c_str(), r.mean_sq_error, vs_plain, vs_asvr);
139+
double vs_asvr = asvr_mse / r.mean_sq_error;
140+
printf(" %-28s %12.6f %8.3fx %8.3fx\n", r.name.c_str(), r.mean_sq_error, vs_plain,
141+
vs_asvr);
135142
}
136143
}
137144

138145
int main() {
139-
const size_t N = 10'000; // sample budget per trial
140-
const size_t RUNS = 200; // trials for MSE estimate
146+
const size_t N = 10'000; // sample budget per trial
147+
const size_t RUNS = 200; // trials for MSE estimate
141148

142149
printf("ASVR vs Fixed Strategy Benchmark\n");
143150
printf("=================================\n");
144151
printf("Budget N=%zu per trial, %zu independent runs\n", N, RUNS);
145152
printf("Strategies compared: all 10 fixed + ASVR (adaptive)\n");
146153

147-
run_option_bench("European Call", std::make_shared<EuropeanCall>(K), N, RUNS);
148-
run_option_bench("European Put", std::make_shared<EuropeanPut>(K), N, RUNS);
149-
run_option_bench("Asian Call", std::make_shared<AsianCall>(K), N, RUNS);
150-
run_option_bench("Asian Put", std::make_shared<AsianPut>(K), N, RUNS);
151-
run_option_bench("Up-And-Out Call B=130", std::make_shared<UpAndOutCall>(K, 130.0), N, RUNS);
152-
run_option_bench("Down-And-In Put B=70", std::make_shared<DownAndInPut>(K, 70.0), N, RUNS);
153-
run_option_bench("Lookback Call", std::make_shared<LookbackCall>(), N, RUNS);
154-
run_option_bench("Lookback Put", std::make_shared<LookbackPut>(), N, RUNS);
154+
run_option_bench("European Call", std::make_shared<EuropeanCall>(K), N, RUNS);
155+
run_option_bench("European Put", std::make_shared<EuropeanPut>(K), N, RUNS);
156+
run_option_bench("Asian Call", std::make_shared<AsianCall>(K), N, RUNS);
157+
run_option_bench("Asian Put", std::make_shared<AsianPut>(K), N, RUNS);
158+
run_option_bench("Up-And-Out Call B=130", std::make_shared<UpAndOutCall>(K, 130.0), N, RUNS);
159+
run_option_bench("Down-And-In Put B=70", std::make_shared<DownAndInPut>(K, 70.0), N, RUNS);
160+
run_option_bench("Lookback Call", std::make_shared<LookbackCall>(), N, RUNS);
161+
run_option_bench("Lookback Put", std::make_shared<LookbackPut>(), N, RUNS);
155162

156163
printf("\nDone.\n");
157164
return 0;

tests/test_adaptive.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ static double bs_call(double S, double K, double r, double sigma, double T) {
3535
//
3636
// The Black-Scholes discounted price is NOT the right comparison target here.
3737

38-
static constexpr double S0 = 100.0;
39-
static constexpr double MU = 0.05;
38+
static constexpr double S0 = 100.0;
39+
static constexpr double MU = 0.05;
4040
static constexpr double SIGMA = 0.2;
41-
static constexpr double K = 100.0;
42-
static constexpr double T = 1.0;
41+
static constexpr double K = 100.0;
42+
static constexpr double T = 1.0;
4343
static constexpr size_t STEPS = 52; // weekly steps
4444
// control: E[S_T] = S0 * exp(mu*T)
4545
static const double CONTROL_MEAN = S0 * std::exp(MU * T);
@@ -60,12 +60,11 @@ static std::vector<NamedStrategy> build_strategies() {
6060
// for GBM), E[S_T] = S0 * exp(mu*T)
6161
{"control_variate",
6262
make_control_variate_sampler(gbm, euler, std::make_shared<EuropeanCall>(K),
63-
std::make_shared<EuropeanCall>(0.0), CONTROL_MEAN, STEPS,
64-
T)},
63+
std::make_shared<EuropeanCall>(0.0), CONTROL_MEAN, STEPS, T)},
6564

6665
{"antithetic_cv",
6766
make_antithetic_cv_sampler(gbm, euler, std::make_shared<EuropeanCall>(K),
68-
std::make_shared<EuropeanCall>(0.0), CONTROL_MEAN, STEPS, T)},
67+
std::make_shared<EuropeanCall>(0.0), CONTROL_MEAN, STEPS, T)},
6968
};
7069
}
7170

@@ -136,8 +135,8 @@ TEST_CASE("ASVR: exploration identifies a VR strategy as best") {
136135

137136
// Find which strategy got the highest weight
138137
const auto& weights = result.precision_weights;
139-
const size_t best_k = static_cast<size_t>(
140-
std::max_element(weights.begin(), weights.end()) - weights.begin());
138+
const size_t best_k =
139+
static_cast<size_t>(std::max_element(weights.begin(), weights.end()) - weights.begin());
141140

142141
INFO("Best strategy: " << result.exploration_stats[best_k].name);
143142
INFO("Weights: ");

0 commit comments

Comments
 (0)