Ancient
view release on metacpan or search on metacpan
t/9053-nvec-valid-finance.t view on Meta::CPAN
subtest 'present value calculation' => sub {
# Future cash flows
my $cash_flows = nvec::new([100, 100, 100, 100, 100]); # 5 annual payments
my $rate = 0.05; # 5% discount rate
# Discount factors: 1/(1+r)^t for t=1,2,3,4,5
my @factors;
for my $t (1..5) {
push @factors, 1 / ((1 + $rate) ** $t);
}
my $discount_factors = nvec::new(\@factors);
# PV = sum(CF * DF)
my $pv = $cash_flows->dot($discount_factors);
# Should be less than sum of cash flows (432.95 for these values)
ok($pv < $cash_flows->sum(), 'PV < sum of cash flows');
ok($pv > 400, 'PV in reasonable range');
};
subtest 'compound interest' => sub {
my $principal = 1000;
my $rate = 0.05; # 5% annual
my $years = 10;
# Future value = P * (1 + r)^n
my $growth_factor = (1 + $rate) ** $years;
my $fv = $principal * $growth_factor;
within_tolerance($fv, 1000 * (1.05 ** 10), 'compound interest FV');
# Using nvec: create growth path
my $time = nvec::range(0, $years + 1); # 0 to 10
my $one_plus_r = nvec::fill($years + 1, 1 + $rate);
# growth[t] = (1+r)^t
my $growth = $one_plus_r->pow($time->to_array()->[0]); # pow takes scalar
# Verify final value
# Actually let's do this differently - build the growth path
my @growth_vals = map { (1 + $rate) ** $_ } (0..$years);
my $growth_vec = nvec::new(\@growth_vals);
within_tolerance($growth_vec->get($years), (1 + $rate) ** $years, 'growth factor at year 10');
};
subtest 'correlation approximation via dot product' => sub {
# Standardized returns (mean=0, std=1)
my $r1 = nvec::new([-1.2, 0.5, 0.3, -0.2, 0.6]);
my $r2 = nvec::new([-1.0, 0.4, 0.5, -0.1, 0.2]);
# Correlation â dot(r1, r2) / (n-1) for standardized data
my $n = $r1->len();
my $corr = $r1->dot($r2) / ($n - 1);
# Should be positive (similar patterns)
ok($corr > 0, 'positive correlation for similar patterns');
ok($corr < 1, 'correlation < 1');
};
subtest 'value at risk (VaR) - percentile approach' => sub {
# Simulated returns (sorted for percentile)
my $returns = nvec::new([-0.05, -0.03, -0.02, -0.01, 0, 0.01, 0.02, 0.03, 0.04, 0.05]);
# 5% VaR: 5th percentile of returns
my $sorted = $returns->sort();
my $var_5pct = $sorted->get(0); # Worst return in this sample
within_tolerance($var_5pct, -0.05, 'VaR 5% = worst return');
};
subtest 'position sizing' => sub {
my $portfolio_value = 100000;
my $risk_per_trade = 0.01; # 1% risk
# Multiple assets with different volatilities
my $volatilities = nvec::new([0.02, 0.05, 0.03, 0.08]);
# Position size = (portfolio * risk) / volatility
my $risk_amount = $portfolio_value * $risk_per_trade;
my $positions = nvec::fill($volatilities->len(), $risk_amount)->div($volatilities);
# Higher volatility = smaller position
ok($positions->get(1) < $positions->get(0), 'higher vol = smaller position');
ok($positions->get(3) < $positions->get(2), 'highest vol = smallest position');
};
subtest 'Kelly criterion approximation' => sub {
my $win_prob = 0.6;
my $win_amount = 1.0; # Win 1x
my $lose_amount = 1.0; # Lose 1x
# Kelly fraction = (p * b - q) / b
# where p = win prob, q = lose prob, b = win/lose ratio
my $b = $win_amount / $lose_amount;
my $kelly = ($win_prob * $b - (1 - $win_prob)) / $b;
# Kelly = (0.6 * 1 - 0.4) / 1 = 0.2
within_tolerance($kelly, 0.2, 'Kelly fraction = 20%');
};
done_testing();
( run in 2.208 seconds using v1.01-cache-2.11-cpan-8dfa8b56332 )