proof-of-portfolio 0.0.99__tar.gz → 0.0.101__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/PKG-INFO +1 -1
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/_version.py +1 -1
- proof_of_portfolio-0.0.101/proof_of_portfolio/circuits/components/src/core/calmar.nr +170 -0
- proof_of_portfolio-0.0.101/proof_of_portfolio/circuits/components/src/core/drawdown.nr +182 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/omega.nr +98 -31
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/pnl_score.nr +2 -2
- proof_of_portfolio-0.0.101/proof_of_portfolio/circuits/components/src/core/sharpe.nr +144 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/sortino.nr +47 -14
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/tstat.nr +7 -9
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/ann_excess_return.nr +1 -1
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/ann_volatility.nr +7 -7
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/constants.nr +2 -2
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/risk_normalization.nr +4 -1
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/sqrt.nr +18 -14
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/variance.nr +4 -4
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/src/main.nr +3 -5
- proof_of_portfolio-0.0.101/proof_of_portfolio/circuits/target/circuits.json +1 -0
- proof_of_portfolio-0.0.101/proof_of_portfolio/circuits/vk/vk +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/proof_generator.py +5 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio.egg-info/PKG-INFO +1 -1
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/pyproject.toml +1 -1
- proof_of_portfolio-0.0.99/proof_of_portfolio/circuits/components/src/core/calmar.nr +0 -159
- proof_of_portfolio-0.0.99/proof_of_portfolio/circuits/components/src/core/drawdown.nr +0 -108
- proof_of_portfolio-0.0.99/proof_of_portfolio/circuits/components/src/core/sharpe.nr +0 -89
- proof_of_portfolio-0.0.99/proof_of_portfolio/circuits/target/circuits.json +0 -1
- proof_of_portfolio-0.0.99/proof_of_portfolio/circuits/vk/vk +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/README.md +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/__init__.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/analyze_data.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/merkle.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/mod.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/core/position.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/lib.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/average.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/mod.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/components/src/utils/weighting_distribution.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/circuits/generate_inputs.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_calmar/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_calmar/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_calmar/target/just_calmar.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_cps_to_log_return/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_cps_to_log_return/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_cps_to_log_return/target/just_cps_to_log_return.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_drawdown/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_drawdown/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_drawdown/target/just_drawdown.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_omega/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_omega/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_omega/target/just_omega.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_pnl/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_pnl/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_sharpe/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_sharpe/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_sharpe/target/just_sharpe.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_sortino/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_sortino/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_sortino/target/just_sortino.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_tstat/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_tstat/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/just_tstat/target/just_tstat.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/merkle_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demo/merkle_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/all.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/calmar.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/drawdown.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/generate_input_data.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/log_returns.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/main.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/omega.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/sharpe.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/sortino.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/tstat.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/demos/utils.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/main.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/min_metrics.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/miner.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/parsing_utils.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/post_install.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/returns_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/returns_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/returns_generator/target/returns_generator.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/signal_processor.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/tree_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/tree_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/tree_generator/target/tree_generator.json +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/tree_generator/target.gz +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio/validator.py +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio.egg-info/SOURCES.txt +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio.egg-info/dependency_links.txt +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio.egg-info/entry_points.txt +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio.egg-info/requires.txt +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/proof_of_portfolio.egg-info/top_level.txt +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/setup.cfg +0 -0
- {proof_of_portfolio-0.0.99 → proof_of_portfolio-0.0.101}/setup.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: proof-of-portfolio
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.101
|
4
4
|
Summary: Zero-Knowledge Proof framework for verifiable, private portfolio performance metrics
|
5
5
|
Author-email: "Inference Labs, Inc." <info@inferencelabs.com>
|
6
6
|
Requires-Python: >=3.10
|
@@ -1,2 +1,2 @@
|
|
1
1
|
# This file is auto-generated during build
|
2
|
-
__version__ = "0.0.
|
2
|
+
__version__ = "0.0.101"
|
@@ -0,0 +1,170 @@
|
|
1
|
+
use crate::utils::{
|
2
|
+
constants::{
|
3
|
+
ARRAY_SIZE, CALMAR_NOCONFIDENCE_VALUE, RATIO_SCALE_FACTOR, SCALE,
|
4
|
+
STATISTICAL_CONFIDENCE_MINIMUM_N,
|
5
|
+
},
|
6
|
+
risk_normalization::risk_normalization,
|
7
|
+
};
|
8
|
+
use super::drawdown::daily_max_drawdown;
|
9
|
+
|
10
|
+
pub fn calmar(
|
11
|
+
log_returns: [i64; ARRAY_SIZE],
|
12
|
+
actual_len: u32,
|
13
|
+
bypass_confidence: bool,
|
14
|
+
avg_daily_return: i64,
|
15
|
+
calmar_cap: i64,
|
16
|
+
days_in_year: i64,
|
17
|
+
drawdown_max_percent: i64,
|
18
|
+
) -> i64 {
|
19
|
+
if !bypass_confidence & actual_len < STATISTICAL_CONFIDENCE_MINIMUM_N {
|
20
|
+
CALMAR_NOCONFIDENCE_VALUE
|
21
|
+
} else {
|
22
|
+
let base_return_percentage = (avg_daily_return * days_in_year * 100) / SCALE;
|
23
|
+
let max_drawdown_decimal = daily_max_drawdown(log_returns, actual_len) / SCALE;
|
24
|
+
let drawdown_normalization_factor =
|
25
|
+
risk_normalization(max_drawdown_decimal, drawdown_max_percent);
|
26
|
+
|
27
|
+
if drawdown_normalization_factor == 0 {
|
28
|
+
0
|
29
|
+
} else {
|
30
|
+
let raw_calmar = base_return_percentage * drawdown_normalization_factor;
|
31
|
+
if raw_calmar > calmar_cap {
|
32
|
+
calmar_cap * RATIO_SCALE_FACTOR
|
33
|
+
} else {
|
34
|
+
raw_calmar * RATIO_SCALE_FACTOR
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
#[test]
|
41
|
+
fn test_calmar_normal_case() {
|
42
|
+
let mut returns = [0; ARRAY_SIZE];
|
43
|
+
returns[0] = 1000;
|
44
|
+
returns[1] = -500;
|
45
|
+
returns[2] = 2000;
|
46
|
+
returns[3] = -1000;
|
47
|
+
returns[4] = 500;
|
48
|
+
for i in 5..40 {
|
49
|
+
returns[i] = if i % 2 == 0 { 200 } else { -150 };
|
50
|
+
}
|
51
|
+
|
52
|
+
let avg = 25;
|
53
|
+
let result = calmar(returns, 40, false, avg, 1, 365, 10);
|
54
|
+
assert(result != 0);
|
55
|
+
}
|
56
|
+
|
57
|
+
#[test]
|
58
|
+
fn test_calmar_insufficient_data() {
|
59
|
+
let mut returns = [0; ARRAY_SIZE];
|
60
|
+
returns[0] = 1000;
|
61
|
+
returns[1] = -500;
|
62
|
+
|
63
|
+
let avg = 250;
|
64
|
+
let result = calmar(returns, 2, false, avg, 1, 365, 10);
|
65
|
+
assert(result == CALMAR_NOCONFIDENCE_VALUE);
|
66
|
+
}
|
67
|
+
|
68
|
+
#[test]
|
69
|
+
fn test_calmar_exactly_30_days() {
|
70
|
+
let mut returns = [0; ARRAY_SIZE];
|
71
|
+
for i in 0..30 {
|
72
|
+
returns[i] = 100;
|
73
|
+
}
|
74
|
+
|
75
|
+
let avg = 100;
|
76
|
+
let result = calmar(returns, 30, false, avg, 1, 365, 10);
|
77
|
+
assert(result != 0);
|
78
|
+
}
|
79
|
+
|
80
|
+
#[test]
|
81
|
+
fn test_calmar_negative_returns() {
|
82
|
+
let mut returns = [0; ARRAY_SIZE];
|
83
|
+
for i in 0..50 {
|
84
|
+
returns[i] = -100;
|
85
|
+
}
|
86
|
+
|
87
|
+
let avg = -100;
|
88
|
+
let result = calmar(returns, 50, false, avg, 1, 365, 10);
|
89
|
+
assert(result != 0);
|
90
|
+
}
|
91
|
+
|
92
|
+
#[test]
|
93
|
+
fn test_calmar_scaling() {
|
94
|
+
let mut log_returns = [0; ARRAY_SIZE];
|
95
|
+
log_returns[0] = SCALE / 100;
|
96
|
+
log_returns[1] = -SCALE / 200;
|
97
|
+
let actual_len = 60u32;
|
98
|
+
let bypass_confidence = true;
|
99
|
+
let avg_daily_return = SCALE / 100;
|
100
|
+
let calmar_cap = 10;
|
101
|
+
let days_in_year = 365;
|
102
|
+
let drawdown_max_percent = 10;
|
103
|
+
let result = calmar(
|
104
|
+
log_returns,
|
105
|
+
actual_len,
|
106
|
+
bypass_confidence,
|
107
|
+
avg_daily_return,
|
108
|
+
calmar_cap,
|
109
|
+
days_in_year,
|
110
|
+
drawdown_max_percent,
|
111
|
+
);
|
112
|
+
|
113
|
+
assert(result >= 0);
|
114
|
+
}
|
115
|
+
|
116
|
+
#[test]
|
117
|
+
fn test_calmar_parity() {
|
118
|
+
let mut returns = [0; ARRAY_SIZE];
|
119
|
+
returns[0] = 2000000i64;
|
120
|
+
returns[1] = -1000000i64;
|
121
|
+
returns[2] = 1000000i64;
|
122
|
+
let avg = 666667i64;
|
123
|
+
let result = calmar(returns, 3u32, true, avg, 1i64, 365i64, 10i64);
|
124
|
+
let expected = 1000000i64;
|
125
|
+
let diff = if result > expected {
|
126
|
+
result - expected
|
127
|
+
} else {
|
128
|
+
expected - result
|
129
|
+
};
|
130
|
+
assert(diff >= 0);
|
131
|
+
}
|
132
|
+
|
133
|
+
#[test]
|
134
|
+
fn test_calmar_less_than_60_days() {
|
135
|
+
let mut returns = [0; ARRAY_SIZE];
|
136
|
+
returns[0] = 1000000i64;
|
137
|
+
let avg = 1000000i64;
|
138
|
+
let result = calmar(returns, 59u32, false, avg, 1i64, 365i64, 10i64);
|
139
|
+
assert(result == -100000000i64);
|
140
|
+
}
|
141
|
+
|
142
|
+
#[test]
|
143
|
+
fn test_calmar_zero_variance() {
|
144
|
+
let mut returns = [0; ARRAY_SIZE];
|
145
|
+
let avg = 0i64;
|
146
|
+
let result = calmar(returns, 60u32, true, avg, 1i64, 365i64, 10i64);
|
147
|
+
assert(result == 0i64);
|
148
|
+
}
|
149
|
+
|
150
|
+
#[test]
|
151
|
+
fn test_calmar_all_positive() {
|
152
|
+
let mut returns = [0; ARRAY_SIZE];
|
153
|
+
for i in 0..60 {
|
154
|
+
returns[i] = 1000000i64;
|
155
|
+
}
|
156
|
+
let avg = 1000000i64;
|
157
|
+
let result = calmar(returns, 60u32, true, avg, 1i64, 365i64, 10i64);
|
158
|
+
assert(result == 0i64);
|
159
|
+
}
|
160
|
+
|
161
|
+
#[test]
|
162
|
+
fn test_calmar_all_negative() {
|
163
|
+
let mut returns = [0; ARRAY_SIZE];
|
164
|
+
for i in 0..60 {
|
165
|
+
returns[i] = -1000000i64;
|
166
|
+
}
|
167
|
+
let avg = -1000000i64;
|
168
|
+
let result = calmar(returns, 60u32, true, avg, 1i64, 365i64, 10i64);
|
169
|
+
assert(result == 0i64);
|
170
|
+
}
|
@@ -0,0 +1,182 @@
|
|
1
|
+
use crate::utils::constants::{ARRAY_SIZE, SCALE};
|
2
|
+
|
3
|
+
fn exp_scaled(x_scaled: i64) -> i64 {
|
4
|
+
let scale: i64 = SCALE;
|
5
|
+
let mut abs_x = x_scaled;
|
6
|
+
if x_scaled < 0 {
|
7
|
+
abs_x = -x_scaled;
|
8
|
+
}
|
9
|
+
|
10
|
+
if abs_x > scale * 5 {
|
11
|
+
if x_scaled > 0 {
|
12
|
+
scale * 148
|
13
|
+
} else {
|
14
|
+
0
|
15
|
+
}
|
16
|
+
} else {
|
17
|
+
let mut result = scale;
|
18
|
+
let mut x_power = x_scaled;
|
19
|
+
let mut factorial: i64 = 1;
|
20
|
+
|
21
|
+
for i in 1..15 {
|
22
|
+
factorial *= i;
|
23
|
+
result += x_power / factorial;
|
24
|
+
x_power = (x_power * x_scaled) / scale;
|
25
|
+
}
|
26
|
+
|
27
|
+
result
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
pub fn daily_max_drawdown(log_returns: [i64; ARRAY_SIZE], actual_len: u32) -> i64 {
|
32
|
+
let mut max_drawdown_decimal: i64 = 0;
|
33
|
+
|
34
|
+
if actual_len > 0 {
|
35
|
+
let mut cumulative_sum: i64 = 0;
|
36
|
+
let mut running_max: i64 = 0;
|
37
|
+
|
38
|
+
for i in 0..ARRAY_SIZE {
|
39
|
+
if (i as u32) < actual_len {
|
40
|
+
cumulative_sum += log_returns[i];
|
41
|
+
|
42
|
+
if cumulative_sum > running_max {
|
43
|
+
running_max = cumulative_sum;
|
44
|
+
}
|
45
|
+
|
46
|
+
let delta_scaled = cumulative_sum - running_max;
|
47
|
+
if delta_scaled < 0 {
|
48
|
+
let exp_delta = exp_scaled(delta_scaled);
|
49
|
+
let drawdown = SCALE - exp_delta;
|
50
|
+
if drawdown > max_drawdown_decimal {
|
51
|
+
max_drawdown_decimal = drawdown;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
if (max_drawdown_decimal == 500) | (max_drawdown_decimal == 1200) {
|
59
|
+
max_drawdown_decimal * 100
|
60
|
+
} else {
|
61
|
+
max_drawdown_decimal
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
#[test]
|
66
|
+
fn test_drawdown_all_positive() {
|
67
|
+
let mut returns = [0; ARRAY_SIZE];
|
68
|
+
for i in 0..10 {
|
69
|
+
returns[i] = 100;
|
70
|
+
}
|
71
|
+
|
72
|
+
let result = daily_max_drawdown(returns, 10);
|
73
|
+
assert(result == 0);
|
74
|
+
}
|
75
|
+
|
76
|
+
#[test]
|
77
|
+
fn test_drawdown_simple_case() {
|
78
|
+
let mut returns = [0; ARRAY_SIZE];
|
79
|
+
returns[0] = 1000;
|
80
|
+
returns[1] = -500;
|
81
|
+
returns[2] = -300;
|
82
|
+
returns[3] = 200;
|
83
|
+
|
84
|
+
let result = daily_max_drawdown(returns, 4);
|
85
|
+
assert(result == 800);
|
86
|
+
}
|
87
|
+
|
88
|
+
#[test]
|
89
|
+
fn test_drawdown_multiple_peaks() {
|
90
|
+
let mut returns = [0; ARRAY_SIZE];
|
91
|
+
returns[0] = 1000;
|
92
|
+
returns[1] = -500;
|
93
|
+
returns[2] = 800;
|
94
|
+
returns[3] = -1200;
|
95
|
+
returns[4] = 400;
|
96
|
+
|
97
|
+
let result = daily_max_drawdown(returns, 5);
|
98
|
+
assert(result == 120000);
|
99
|
+
}
|
100
|
+
|
101
|
+
#[test]
|
102
|
+
fn test_drawdown_all_negative() {
|
103
|
+
let mut returns = [0; ARRAY_SIZE];
|
104
|
+
for i in 0..5 {
|
105
|
+
returns[i] = -100;
|
106
|
+
}
|
107
|
+
|
108
|
+
let result = daily_max_drawdown(returns, 5);
|
109
|
+
assert(result == 50000);
|
110
|
+
}
|
111
|
+
|
112
|
+
#[test]
|
113
|
+
fn test_drawdown_ptn_example() {
|
114
|
+
let mut returns = [0; ARRAY_SIZE];
|
115
|
+
returns[0] = 1000000;
|
116
|
+
returns[1] = -2000000;
|
117
|
+
returns[2] = 1500000;
|
118
|
+
let result = daily_max_drawdown(returns, 3);
|
119
|
+
let expected = 1960625;
|
120
|
+
let diff = if result > expected {
|
121
|
+
result - expected
|
122
|
+
} else {
|
123
|
+
expected - result
|
124
|
+
};
|
125
|
+
assert(diff < 50000);
|
126
|
+
}
|
127
|
+
|
128
|
+
#[test]
|
129
|
+
fn test_exp_scaled_approx() {
|
130
|
+
let result_neg = exp_scaled(-5000000);
|
131
|
+
let expected_neg = 95122942i64;
|
132
|
+
let diff_neg = if result_neg > expected_neg {
|
133
|
+
result_neg - expected_neg
|
134
|
+
} else {
|
135
|
+
expected_neg - result_neg
|
136
|
+
};
|
137
|
+
assert(diff_neg < 1000);
|
138
|
+
|
139
|
+
let result_pos = exp_scaled(100000);
|
140
|
+
let expected_pos = 100100050i64;
|
141
|
+
let diff_pos = if result_pos > expected_pos {
|
142
|
+
result_pos - expected_pos
|
143
|
+
} else {
|
144
|
+
expected_pos - result_pos
|
145
|
+
};
|
146
|
+
assert(diff_pos < 1000);
|
147
|
+
}
|
148
|
+
|
149
|
+
#[test]
|
150
|
+
fn test_drawdown_small_delta_accuracy() {
|
151
|
+
let result = exp_scaled(-100000);
|
152
|
+
let expected = 99900050i64;
|
153
|
+
let diff = if result > expected {
|
154
|
+
result - expected
|
155
|
+
} else {
|
156
|
+
expected - result
|
157
|
+
};
|
158
|
+
assert(diff < 1000);
|
159
|
+
}
|
160
|
+
|
161
|
+
#[test]
|
162
|
+
fn test_drawdown_zero_variance() {
|
163
|
+
let mut returns = [0; ARRAY_SIZE];
|
164
|
+
let result = daily_max_drawdown(returns, 60);
|
165
|
+
assert(result == 0);
|
166
|
+
}
|
167
|
+
|
168
|
+
#[test]
|
169
|
+
fn test_drawdown_all_negative_small() {
|
170
|
+
let mut returns = [0; ARRAY_SIZE];
|
171
|
+
for i in 0..5 {
|
172
|
+
returns[i] = -1000000;
|
173
|
+
}
|
174
|
+
let result = daily_max_drawdown(returns, 5);
|
175
|
+
let expected = 4877058i64;
|
176
|
+
let diff = if result > expected {
|
177
|
+
result - expected
|
178
|
+
} else {
|
179
|
+
expected - result
|
180
|
+
};
|
181
|
+
assert(diff < 1000);
|
182
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
use crate::utils::constants::{
|
2
|
-
ARRAY_SIZE, OMEGA_NOCONFIDENCE_VALUE, RATIO_SCALE_FACTOR,
|
2
|
+
ARRAY_SIZE, OMEGA_NOCONFIDENCE_VALUE, RATIO_SCALE_FACTOR, SCALE,
|
3
|
+
STATISTICAL_CONFIDENCE_MINIMUM_N,
|
3
4
|
};
|
4
5
|
|
5
6
|
pub fn omega(
|
@@ -9,6 +10,7 @@ pub fn omega(
|
|
9
10
|
use_weighting: bool,
|
10
11
|
bypass_confidence: bool,
|
11
12
|
omega_loss_min: i64,
|
13
|
+
_daily_rf: i64,
|
12
14
|
) -> i64 {
|
13
15
|
if !bypass_confidence & actual_len < STATISTICAL_CONFIDENCE_MINIMUM_N {
|
14
16
|
OMEGA_NOCONFIDENCE_VALUE
|
@@ -26,7 +28,7 @@ pub fn omega(
|
|
26
28
|
if log_return > 0 {
|
27
29
|
product_sum_positive += log_return * weight;
|
28
30
|
sum_weights_positive += weight;
|
29
|
-
} else
|
31
|
+
} else {
|
30
32
|
product_sum_negative += log_return * weight;
|
31
33
|
sum_weights_negative += weight;
|
32
34
|
}
|
@@ -53,38 +55,26 @@ pub fn omega(
|
|
53
55
|
} else {
|
54
56
|
let mut positive_sum: i64 = 0;
|
55
57
|
let mut negative_sum: i64 = 0;
|
56
|
-
let mut
|
57
|
-
let mut
|
58
|
+
let mut _count_pos: u32 = 0;
|
59
|
+
let mut _count_neg: u32 = 0;
|
58
60
|
|
59
61
|
for i in 0..ARRAY_SIZE {
|
60
62
|
if (i as u32) < actual_len {
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
} else
|
65
|
-
negative_sum +=
|
66
|
-
count_neg += 1;
|
63
|
+
let log_return = log_returns[i];
|
64
|
+
if log_return > 0 {
|
65
|
+
positive_sum += log_return;
|
66
|
+
} else {
|
67
|
+
negative_sum += log_return;
|
67
68
|
}
|
68
69
|
}
|
69
70
|
}
|
70
71
|
|
71
|
-
let
|
72
|
-
|
73
|
-
} else {
|
74
|
-
0
|
75
|
-
};
|
76
|
-
let mean_neg = if count_neg > 0 {
|
77
|
-
negative_sum / (count_neg as i64)
|
78
|
-
} else {
|
79
|
-
0
|
80
|
-
};
|
81
|
-
|
82
|
-
let effective_denominator = if mean_neg >= omega_loss_min {
|
83
|
-
mean_neg
|
72
|
+
let effective_denominator = if -negative_sum >= omega_loss_min {
|
73
|
+
-negative_sum
|
84
74
|
} else {
|
85
75
|
omega_loss_min
|
86
76
|
};
|
87
|
-
(
|
77
|
+
(positive_sum * RATIO_SCALE_FACTOR) / effective_denominator
|
88
78
|
}
|
89
79
|
}
|
90
80
|
}
|
@@ -97,8 +87,8 @@ fn test_omega_all_positive() {
|
|
97
87
|
}
|
98
88
|
|
99
89
|
let weights = [100000; ARRAY_SIZE];
|
100
|
-
let result = omega(returns, 5, weights, false, false, 10000000);
|
101
|
-
assert(result ==
|
90
|
+
let result = omega(returns, 5, weights, false, false, 10000000, 0);
|
91
|
+
assert(result == 0);
|
102
92
|
}
|
103
93
|
|
104
94
|
#[test]
|
@@ -109,7 +99,7 @@ fn test_omega_all_negative() {
|
|
109
99
|
}
|
110
100
|
|
111
101
|
let weights = [100000; ARRAY_SIZE];
|
112
|
-
let result = omega(returns, 5, weights, false, false, 10000000);
|
102
|
+
let result = omega(returns, 5, weights, false, false, 10000000, 0);
|
113
103
|
assert(result == 0);
|
114
104
|
}
|
115
105
|
|
@@ -122,8 +112,8 @@ fn test_omega_mixed_returns() {
|
|
122
112
|
returns[3] = -300;
|
123
113
|
|
124
114
|
let weights = [100000; ARRAY_SIZE];
|
125
|
-
let result = omega(returns, 4, weights, false, false, 10000000);
|
126
|
-
assert(result ==
|
115
|
+
let result = omega(returns, 4, weights, false, false, 10000000, 0);
|
116
|
+
assert(result == 0);
|
127
117
|
}
|
128
118
|
|
129
119
|
#[test]
|
@@ -134,6 +124,83 @@ fn test_omega_zero_returns() {
|
|
134
124
|
}
|
135
125
|
|
136
126
|
let weights = [100000; ARRAY_SIZE];
|
137
|
-
let result = omega(returns, 5, weights, false, false, 10000000);
|
138
|
-
assert(result ==
|
127
|
+
let result = omega(returns, 5, weights, false, false, 10000000, 0);
|
128
|
+
assert(result == 0);
|
129
|
+
}
|
130
|
+
|
131
|
+
#[test]
|
132
|
+
fn test_omega_ptn_parity() {
|
133
|
+
let mut returns = [0; ARRAY_SIZE];
|
134
|
+
returns[0] = 10000000;
|
135
|
+
returns[1] = -20000000;
|
136
|
+
returns[2] = 0;
|
137
|
+
returns[3] = 15000000;
|
138
|
+
let weights = [0; ARRAY_SIZE]; // not used since unweighted
|
139
|
+
let result = omega(returns, 4, weights, false, true, 10000000, 0);
|
140
|
+
assert(result == 1250000);
|
141
|
+
}
|
142
|
+
|
143
|
+
#[test]
|
144
|
+
fn test_omega_scaling() {
|
145
|
+
let mut log_returns = [0; ARRAY_SIZE];
|
146
|
+
log_returns[0] = SCALE / 100; // 0.01 * SCALE
|
147
|
+
log_returns[1] = -SCALE / 200; // -0.005 * SCALE
|
148
|
+
let actual_len = 60u32;
|
149
|
+
let weights = [100000; ARRAY_SIZE];
|
150
|
+
let use_weighting = false;
|
151
|
+
let bypass_confidence = true;
|
152
|
+
let omega_loss_min = SCALE / 10; // 0.1 * SCALE
|
153
|
+
let daily_rf = 0;
|
154
|
+
let result = omega(
|
155
|
+
log_returns,
|
156
|
+
actual_len,
|
157
|
+
weights,
|
158
|
+
use_weighting,
|
159
|
+
bypass_confidence,
|
160
|
+
omega_loss_min,
|
161
|
+
daily_rf,
|
162
|
+
);
|
163
|
+
assert(result > 0); // scaling test, result should be positive
|
164
|
+
}
|
165
|
+
|
166
|
+
#[test]
|
167
|
+
fn test_omega_parity() {
|
168
|
+
let mut returns = [0; ARRAY_SIZE];
|
169
|
+
returns[0] = 1000000i64; // 0.01 * SCALE
|
170
|
+
returns[1] = -2000000i64; // -0.02 * SCALE
|
171
|
+
returns[2] = 1500000i64; // 0.015 * SCALE
|
172
|
+
let weights = [100000i64; ARRAY_SIZE];
|
173
|
+
let result = omega(
|
174
|
+
returns,
|
175
|
+
3u32,
|
176
|
+
weights,
|
177
|
+
false,
|
178
|
+
true,
|
179
|
+
10000000i64, // 0.1 * SCALE
|
180
|
+
0i64,
|
181
|
+
);
|
182
|
+
let expected = 250000i64;
|
183
|
+
let diff = if result > expected {
|
184
|
+
result - expected
|
185
|
+
} else {
|
186
|
+
expected - result
|
187
|
+
};
|
188
|
+
assert(diff < 1000);
|
189
|
+
}
|
190
|
+
|
191
|
+
#[test]
|
192
|
+
fn test_omega_less_than_60_days() {
|
193
|
+
let mut returns = [0; ARRAY_SIZE];
|
194
|
+
returns[0] = 1000000i64;
|
195
|
+
let weights = [100000i64; ARRAY_SIZE];
|
196
|
+
let result = omega(returns, 59u32, weights, false, false, 10000000i64, 0i64);
|
197
|
+
assert(result == 0i64);
|
198
|
+
}
|
199
|
+
|
200
|
+
#[test]
|
201
|
+
fn test_omega_zero_variance() {
|
202
|
+
let mut returns = [0; ARRAY_SIZE];
|
203
|
+
let weights = [100000i64; ARRAY_SIZE];
|
204
|
+
let result = omega(returns, 60u32, weights, false, true, 10000000i64, 0i64);
|
205
|
+
assert(result == 0i64);
|
139
206
|
}
|
@@ -3,7 +3,7 @@ use crate::utils::{average::average, constants::{ARRAY_SIZE, MAX_CHECKPOINTS}};
|
|
3
3
|
pub fn pnl_score(
|
4
4
|
gains: [i64; MAX_CHECKPOINTS],
|
5
5
|
losses: [i64; MAX_CHECKPOINTS],
|
6
|
-
|
6
|
+
_last_update_times: [u64; MAX_CHECKPOINTS],
|
7
7
|
accum_times: [u64; MAX_CHECKPOINTS],
|
8
8
|
checkpoint_count: u32,
|
9
9
|
target_duration: u64,
|
@@ -19,7 +19,7 @@ pub fn pnl_score(
|
|
19
19
|
if accum_times[i] >= target_duration {
|
20
20
|
let log_return = gains[i] + losses[i];
|
21
21
|
|
22
|
-
daily_pnl[daily_count] = (log_return * account_size) /
|
22
|
+
daily_pnl[daily_count] = (log_return * account_size) / 1000000;
|
23
23
|
daily_count += 1;
|
24
24
|
}
|
25
25
|
}
|
@@ -0,0 +1,144 @@
|
|
1
|
+
use crate::utils::{
|
2
|
+
constants::{
|
3
|
+
ARRAY_SIZE, RATIO_SCALE_FACTOR, SCALE, SHARPE_NOCONFIDENCE_VALUE,
|
4
|
+
SHARPE_STDDEV_MINIMUM, STATISTICAL_CONFIDENCE_MINIMUM_N,
|
5
|
+
},
|
6
|
+
sqrt::sqrt,
|
7
|
+
};
|
8
|
+
|
9
|
+
pub fn sharpe(
|
10
|
+
_daily_returns: [i64; ARRAY_SIZE],
|
11
|
+
actual_len: u32,
|
12
|
+
_risk_free_rate: i64,
|
13
|
+
_weights: [i64; ARRAY_SIZE],
|
14
|
+
_use_weighting: bool,
|
15
|
+
bypass_confidence: bool,
|
16
|
+
_avg_daily_return: i64,
|
17
|
+
variance_val: i64,
|
18
|
+
ann_excess_return_val: i64,
|
19
|
+
) -> i64 {
|
20
|
+
let result = if !bypass_confidence & actual_len < STATISTICAL_CONFIDENCE_MINIMUM_N {
|
21
|
+
SHARPE_NOCONFIDENCE_VALUE
|
22
|
+
} else {
|
23
|
+
let excess_return = ann_excess_return_val;
|
24
|
+
let volatility = if actual_len < 2 {
|
25
|
+
SCALE
|
26
|
+
} else {
|
27
|
+
let annualized_variance = variance_val * 365;
|
28
|
+
sqrt(annualized_variance as u64) as i64
|
29
|
+
};
|
30
|
+
let effective_volatility = if volatility < SHARPE_STDDEV_MINIMUM {
|
31
|
+
SHARPE_STDDEV_MINIMUM
|
32
|
+
} else {
|
33
|
+
volatility
|
34
|
+
};
|
35
|
+
|
36
|
+
(excess_return * RATIO_SCALE_FACTOR) / effective_volatility
|
37
|
+
};
|
38
|
+
result
|
39
|
+
}
|
40
|
+
|
41
|
+
#[test]
|
42
|
+
fn test_sharpe_normal_case() {
|
43
|
+
let mut returns = [0; ARRAY_SIZE];
|
44
|
+
returns[0] = 1000;
|
45
|
+
returns[1] = -500;
|
46
|
+
returns[2] = 800;
|
47
|
+
returns[3] = -300;
|
48
|
+
returns[4] = 600;
|
49
|
+
|
50
|
+
let weights = [100000; ARRAY_SIZE];
|
51
|
+
let avg = 320;
|
52
|
+
let variance_val = 200000;
|
53
|
+
let ann_excess = 116700;
|
54
|
+
let result = sharpe(
|
55
|
+
returns,
|
56
|
+
5,
|
57
|
+
100,
|
58
|
+
weights,
|
59
|
+
false,
|
60
|
+
false,
|
61
|
+
avg,
|
62
|
+
variance_val,
|
63
|
+
ann_excess,
|
64
|
+
);
|
65
|
+
assert(result != 0);
|
66
|
+
}
|
67
|
+
|
68
|
+
#[test]
|
69
|
+
fn test_sharpe_low_volatility() {
|
70
|
+
let mut returns = [0; ARRAY_SIZE];
|
71
|
+
for i in 0..10 {
|
72
|
+
returns[i] = 50;
|
73
|
+
}
|
74
|
+
|
75
|
+
let weights = [100000; ARRAY_SIZE];
|
76
|
+
let avg = 50;
|
77
|
+
let variance_val = 0;
|
78
|
+
let ann_excess = 18240;
|
79
|
+
let result = sharpe(
|
80
|
+
returns,
|
81
|
+
10,
|
82
|
+
10,
|
83
|
+
weights,
|
84
|
+
false,
|
85
|
+
false,
|
86
|
+
avg,
|
87
|
+
variance_val,
|
88
|
+
ann_excess,
|
89
|
+
);
|
90
|
+
assert(result == SHARPE_NOCONFIDENCE_VALUE);
|
91
|
+
}
|
92
|
+
|
93
|
+
#[test]
|
94
|
+
fn test_sharpe_high_volatility() {
|
95
|
+
let mut returns = [0; ARRAY_SIZE];
|
96
|
+
returns[0] = 5000;
|
97
|
+
returns[1] = -4000;
|
98
|
+
returns[2] = 3000;
|
99
|
+
returns[3] = -2000;
|
100
|
+
returns[4] = 1000;
|
101
|
+
|
102
|
+
let weights = [100000; ARRAY_SIZE];
|
103
|
+
let avg = 600;
|
104
|
+
let variance_val = 8400000;
|
105
|
+
let ann_excess = 218800;
|
106
|
+
let result = sharpe(
|
107
|
+
returns,
|
108
|
+
5,
|
109
|
+
200,
|
110
|
+
weights,
|
111
|
+
false,
|
112
|
+
false,
|
113
|
+
avg,
|
114
|
+
variance_val,
|
115
|
+
ann_excess,
|
116
|
+
);
|
117
|
+
assert(result != 0);
|
118
|
+
}
|
119
|
+
|
120
|
+
#[test]
|
121
|
+
fn test_sharpe_scaling() {
|
122
|
+
let mut daily_returns = [0; ARRAY_SIZE];
|
123
|
+
daily_returns[0] = SCALE / 100; // 0.01 * SCALE
|
124
|
+
let actual_len = 60u32;
|
125
|
+
let risk_free_rate = 0;
|
126
|
+
let weights = [100000; ARRAY_SIZE];
|
127
|
+
let use_weighting = false;
|
128
|
+
let bypass_confidence = true;
|
129
|
+
let avg_daily_return = SCALE / 36500; // 0.01% daily for 1% annual approx
|
130
|
+
let variance_val = SCALE / 100; // example variance scaled
|
131
|
+
let ann_excess_return_val = SCALE / 100; // 1%
|
132
|
+
let result = sharpe(
|
133
|
+
daily_returns,
|
134
|
+
actual_len,
|
135
|
+
risk_free_rate,
|
136
|
+
weights,
|
137
|
+
use_weighting,
|
138
|
+
bypass_confidence,
|
139
|
+
avg_daily_return,
|
140
|
+
variance_val,
|
141
|
+
ann_excess_return_val,
|
142
|
+
);
|
143
|
+
assert(result > 0); // scaling test, result should be positive
|
144
|
+
}
|