proof-of-portfolio 0.0.100__tar.gz → 0.0.102__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.100 → proof_of_portfolio-0.0.102}/PKG-INFO +1 -1
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/_version.py +1 -1
- proof_of_portfolio-0.0.102/proof_of_portfolio/circuits/components/src/core/calmar.nr +170 -0
- proof_of_portfolio-0.0.102/proof_of_portfolio/circuits/components/src/core/drawdown.nr +182 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/omega.nr +95 -29
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/pnl_score.nr +2 -2
- proof_of_portfolio-0.0.102/proof_of_portfolio/circuits/components/src/core/sharpe.nr +144 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/sortino.nr +43 -13
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/tstat.nr +7 -9
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/ann_excess_return.nr +1 -1
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/ann_volatility.nr +7 -7
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/constants.nr +2 -2
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/risk_normalization.nr +4 -1
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/sqrt.nr +18 -14
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/variance.nr +4 -4
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/src/main.nr +0 -5
- proof_of_portfolio-0.0.102/proof_of_portfolio/circuits/target/circuits.json +1 -0
- proof_of_portfolio-0.0.102/proof_of_portfolio/circuits/vk/vk +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio.egg-info/PKG-INFO +1 -1
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/pyproject.toml +1 -1
- proof_of_portfolio-0.0.100/proof_of_portfolio/circuits/components/src/core/calmar.nr +0 -159
- proof_of_portfolio-0.0.100/proof_of_portfolio/circuits/components/src/core/drawdown.nr +0 -108
- proof_of_portfolio-0.0.100/proof_of_portfolio/circuits/components/src/core/sharpe.nr +0 -89
- proof_of_portfolio-0.0.100/proof_of_portfolio/circuits/target/circuits.json +0 -1
- proof_of_portfolio-0.0.100/proof_of_portfolio/circuits/vk/vk +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/README.md +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/__init__.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/analyze_data.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/merkle.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/mod.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/core/position.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/lib.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/average.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/mod.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/components/src/utils/weighting_distribution.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/circuits/generate_inputs.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_calmar/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_calmar/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_calmar/target/just_calmar.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_cps_to_log_return/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_cps_to_log_return/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_cps_to_log_return/target/just_cps_to_log_return.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_drawdown/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_drawdown/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_drawdown/target/just_drawdown.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_omega/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_omega/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_omega/target/just_omega.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_pnl/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_pnl/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_sharpe/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_sharpe/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_sharpe/target/just_sharpe.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_sortino/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_sortino/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_sortino/target/just_sortino.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_tstat/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_tstat/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/just_tstat/target/just_tstat.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/merkle_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demo/merkle_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/all.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/calmar.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/drawdown.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/generate_input_data.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/log_returns.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/main.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/omega.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/sharpe.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/sortino.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/tstat.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/demos/utils.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/main.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/min_metrics.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/miner.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/parsing_utils.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/post_install.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/proof_generator.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/returns_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/returns_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/returns_generator/target/returns_generator.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/signal_processor.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/tree_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/tree_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/tree_generator/target/tree_generator.json +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/tree_generator/target.gz +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio/validator.py +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio.egg-info/SOURCES.txt +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio.egg-info/dependency_links.txt +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio.egg-info/entry_points.txt +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio.egg-info/requires.txt +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/proof_of_portfolio.egg-info/top_level.txt +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/setup.cfg +0 -0
- {proof_of_portfolio-0.0.100 → proof_of_portfolio-0.0.102}/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.102
|
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.102"
|
@@ -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,7 +10,7 @@ pub fn omega(
|
|
9
10
|
use_weighting: bool,
|
10
11
|
bypass_confidence: bool,
|
11
12
|
omega_loss_min: i64,
|
12
|
-
|
13
|
+
_daily_rf: i64,
|
13
14
|
) -> i64 {
|
14
15
|
if !bypass_confidence & actual_len < STATISTICAL_CONFIDENCE_MINIMUM_N {
|
15
16
|
OMEGA_NOCONFIDENCE_VALUE
|
@@ -24,10 +25,10 @@ pub fn omega(
|
|
24
25
|
if (i as u32) < actual_len {
|
25
26
|
let weight = weights[i];
|
26
27
|
let log_return = log_returns[i];
|
27
|
-
if log_return >
|
28
|
+
if log_return > 0 {
|
28
29
|
product_sum_positive += log_return * weight;
|
29
30
|
sum_weights_positive += weight;
|
30
|
-
} else
|
31
|
+
} else {
|
31
32
|
product_sum_negative += log_return * weight;
|
32
33
|
sum_weights_negative += weight;
|
33
34
|
}
|
@@ -54,38 +55,26 @@ pub fn omega(
|
|
54
55
|
} else {
|
55
56
|
let mut positive_sum: i64 = 0;
|
56
57
|
let mut negative_sum: i64 = 0;
|
57
|
-
let mut
|
58
|
-
let mut
|
58
|
+
let mut _count_pos: u32 = 0;
|
59
|
+
let mut _count_neg: u32 = 0;
|
59
60
|
|
60
61
|
for i in 0..ARRAY_SIZE {
|
61
62
|
if (i as u32) < actual_len {
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
} else
|
66
|
-
negative_sum +=
|
67
|
-
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;
|
68
68
|
}
|
69
69
|
}
|
70
70
|
}
|
71
71
|
|
72
|
-
let
|
73
|
-
|
74
|
-
} else {
|
75
|
-
0
|
76
|
-
};
|
77
|
-
let mean_neg = if count_neg > 0 {
|
78
|
-
negative_sum / (count_neg as i64)
|
79
|
-
} else {
|
80
|
-
0
|
81
|
-
};
|
82
|
-
|
83
|
-
let effective_denominator = if mean_neg >= omega_loss_min {
|
84
|
-
mean_neg
|
72
|
+
let effective_denominator = if -negative_sum >= omega_loss_min {
|
73
|
+
-negative_sum
|
85
74
|
} else {
|
86
75
|
omega_loss_min
|
87
76
|
};
|
88
|
-
(
|
77
|
+
(positive_sum * RATIO_SCALE_FACTOR) / effective_denominator
|
89
78
|
}
|
90
79
|
}
|
91
80
|
}
|
@@ -99,7 +88,7 @@ fn test_omega_all_positive() {
|
|
99
88
|
|
100
89
|
let weights = [100000; ARRAY_SIZE];
|
101
90
|
let result = omega(returns, 5, weights, false, false, 10000000, 0);
|
102
|
-
assert(result ==
|
91
|
+
assert(result == 0);
|
103
92
|
}
|
104
93
|
|
105
94
|
#[test]
|
@@ -124,7 +113,7 @@ fn test_omega_mixed_returns() {
|
|
124
113
|
|
125
114
|
let weights = [100000; ARRAY_SIZE];
|
126
115
|
let result = omega(returns, 4, weights, false, false, 10000000, 0);
|
127
|
-
assert(result ==
|
116
|
+
assert(result == 0);
|
128
117
|
}
|
129
118
|
|
130
119
|
#[test]
|
@@ -136,5 +125,82 @@ fn test_omega_zero_returns() {
|
|
136
125
|
|
137
126
|
let weights = [100000; ARRAY_SIZE];
|
138
127
|
let result = omega(returns, 5, weights, false, false, 10000000, 0);
|
139
|
-
assert(result ==
|
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);
|
140
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
|
+
}
|