proof-of-portfolio 0.0.44__tar.gz → 0.0.46__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.44 → proof_of_portfolio-0.0.46}/PKG-INFO +1 -1
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/_version.py +1 -1
- proof_of_portfolio-0.0.46/proof_of_portfolio/min_metrics.py +390 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/proof_generator.py +80 -26
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/PKG-INFO +1 -1
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/SOURCES.txt +1 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/pyproject.toml +1 -1
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/README.md +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/__init__.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/analyze_data.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/calmar.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/cps_to_log_returns.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/drawdown.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/merkle.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/mod.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/omega.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/position.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/sharpe.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/sortino.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/core/tstat.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/float/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/float/Verifier.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/float/src/lib.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/lib.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/nrstat.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/ann_excess_return.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/ann_volatility.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/average.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/constants.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/mod.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/sqrt.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/variance.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/components/src/utils/weighting_distribution.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/generate_inputs.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/proof/proof +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/proof/public_inputs +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/target/circuits.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/vk/vk +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_calmar/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_calmar/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_calmar/target/just_calmar.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_cps_to_log_return/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_cps_to_log_return/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_cps_to_log_return/target/just_cps_to_log_return.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_drawdown/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_drawdown/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_drawdown/target/just_drawdown.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_omega/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_omega/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_omega/target/just_omega.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_sharpe/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_sharpe/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_sharpe/target/just_sharpe.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_sortino/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_sortino/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_sortino/target/just_sortino.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_tstat/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_tstat/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/just_tstat/target/just_tstat.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/merkle_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demo/merkle_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/all.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/calmar.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/drawdown.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/generate_input_data.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/log_returns.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/main.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/omega.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/sharpe.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/sortino.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/tstat.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/utils.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/main.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/miner.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/post_install.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/returns_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/returns_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/returns_generator/target/returns_generator.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/signal_processor.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/tree_generator/Nargo.toml +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/tree_generator/src/main.nr +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/tree_generator/target/tree_generator.json +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/tree_generator/target.gz +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/validator.py +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/dependency_links.txt +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/entry_points.txt +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/requires.txt +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/top_level.txt +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/setup.cfg +0 -0
- {proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/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.46
|
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.46"
|
@@ -0,0 +1,390 @@
|
|
1
|
+
import math
|
2
|
+
import numpy as np
|
3
|
+
from scipy.stats import ttest_1samp
|
4
|
+
from typing import Union
|
5
|
+
|
6
|
+
|
7
|
+
class MinMetrics:
|
8
|
+
"""
|
9
|
+
Minimal metrics implementation that matches the proprietary trading network
|
10
|
+
validator metrics calculations exactly. Used for comparing ZK circuit outputs
|
11
|
+
against Python calculations on the same input data.
|
12
|
+
"""
|
13
|
+
|
14
|
+
# Constants from ValiConfig
|
15
|
+
WEIGHTED_AVERAGE_DECAY_RATE = 0.075
|
16
|
+
WEIGHTED_AVERAGE_DECAY_MIN = 0.15
|
17
|
+
WEIGHTED_AVERAGE_DECAY_MAX = 1.0
|
18
|
+
ANNUAL_RISK_FREE_PERCENTAGE = 4.19
|
19
|
+
ANNUAL_RISK_FREE_DECIMAL = ANNUAL_RISK_FREE_PERCENTAGE / 100
|
20
|
+
DAYS_IN_YEAR_CRYPTO = 365
|
21
|
+
DAYS_IN_YEAR_FOREX = 252
|
22
|
+
|
23
|
+
# Metric-specific constants
|
24
|
+
SHARPE_STDDEV_MINIMUM = 0.01
|
25
|
+
STATISTICAL_CONFIDENCE_MINIMUM_N = 60
|
26
|
+
SHARPE_NOCONFIDENCE_VALUE = -100
|
27
|
+
OMEGA_LOSS_MINIMUM = 0.01
|
28
|
+
OMEGA_NOCONFIDENCE_VALUE = -100
|
29
|
+
SORTINO_DOWNSIDE_MINIMUM = 0.01
|
30
|
+
SORTINO_NOCONFIDENCE_VALUE = -100
|
31
|
+
CALMAR_RATIO_CAP = 1000
|
32
|
+
CALMAR_NOCONFIDENCE_VALUE = -100
|
33
|
+
STATISTICAL_CONFIDENCE_NOCONFIDENCE_VALUE = -100
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def log_risk_free_rate(days_in_year: int) -> float:
|
37
|
+
if days_in_year is None or days_in_year <= 0:
|
38
|
+
return np.inf
|
39
|
+
return math.log(1 + MinMetrics.ANNUAL_RISK_FREE_DECIMAL) / days_in_year
|
40
|
+
|
41
|
+
@staticmethod
|
42
|
+
def weighting_distribution(
|
43
|
+
log_returns: Union[list[float], np.ndarray]
|
44
|
+
) -> np.ndarray:
|
45
|
+
"""
|
46
|
+
Returns the weighting distribution that decays from max_weight to min_weight
|
47
|
+
using the configured decay rate
|
48
|
+
"""
|
49
|
+
max_weight = MinMetrics.WEIGHTED_AVERAGE_DECAY_MAX
|
50
|
+
min_weight = MinMetrics.WEIGHTED_AVERAGE_DECAY_MIN
|
51
|
+
decay_rate = MinMetrics.WEIGHTED_AVERAGE_DECAY_RATE
|
52
|
+
|
53
|
+
if len(log_returns) < 1:
|
54
|
+
return np.ones(0)
|
55
|
+
|
56
|
+
weighting_distribution_days = np.arange(0, len(log_returns))
|
57
|
+
|
58
|
+
# Calculate decay from max to min
|
59
|
+
weight_range = max_weight - min_weight
|
60
|
+
decay_values = min_weight + (
|
61
|
+
weight_range * np.exp(-decay_rate * weighting_distribution_days)
|
62
|
+
)
|
63
|
+
|
64
|
+
return decay_values[::-1][-len(log_returns) :]
|
65
|
+
|
66
|
+
@staticmethod
|
67
|
+
def average(
|
68
|
+
log_returns: Union[list[float], np.ndarray],
|
69
|
+
weighting=False,
|
70
|
+
indices: Union[list[int], None] = None,
|
71
|
+
) -> float:
|
72
|
+
"""
|
73
|
+
Returns the mean of the log returns
|
74
|
+
"""
|
75
|
+
if len(log_returns) == 0:
|
76
|
+
return 0.0
|
77
|
+
|
78
|
+
weighting_distribution = MinMetrics.weighting_distribution(log_returns)
|
79
|
+
|
80
|
+
if indices is not None and len(indices) != 0:
|
81
|
+
indices = [i for i in indices if i in range(len(log_returns))]
|
82
|
+
log_returns = [log_returns[i] for i in indices]
|
83
|
+
weighting_distribution = [weighting_distribution[i] for i in indices]
|
84
|
+
|
85
|
+
if weighting:
|
86
|
+
avg_value = np.average(log_returns, weights=weighting_distribution)
|
87
|
+
else:
|
88
|
+
avg_value = np.mean(log_returns)
|
89
|
+
|
90
|
+
return float(avg_value)
|
91
|
+
|
92
|
+
@staticmethod
|
93
|
+
def variance(
|
94
|
+
log_returns: list[float],
|
95
|
+
ddof: int = 1,
|
96
|
+
weighting=False,
|
97
|
+
indices: Union[list[int], None] = None,
|
98
|
+
) -> float:
|
99
|
+
"""
|
100
|
+
Returns the variance of the log returns
|
101
|
+
"""
|
102
|
+
if len(log_returns) == 0:
|
103
|
+
return 0.0
|
104
|
+
|
105
|
+
window = len(indices) if indices is not None else len(log_returns)
|
106
|
+
if window < ddof + 1:
|
107
|
+
return np.inf
|
108
|
+
|
109
|
+
return MinMetrics.average(
|
110
|
+
(
|
111
|
+
np.array(log_returns)
|
112
|
+
- MinMetrics.average(log_returns, weighting=weighting, indices=indices)
|
113
|
+
)
|
114
|
+
** 2,
|
115
|
+
weighting=weighting,
|
116
|
+
indices=indices,
|
117
|
+
)
|
118
|
+
|
119
|
+
@staticmethod
|
120
|
+
def ann_excess_return(
|
121
|
+
log_returns: list[float],
|
122
|
+
weighting=False,
|
123
|
+
days_in_year: int = DAYS_IN_YEAR_CRYPTO,
|
124
|
+
) -> float:
|
125
|
+
"""
|
126
|
+
Calculates annualized excess return using mean daily log returns and mean daily 1yr risk free rate.
|
127
|
+
"""
|
128
|
+
annual_risk_free_rate = MinMetrics.ANNUAL_RISK_FREE_DECIMAL
|
129
|
+
|
130
|
+
if len(log_returns) == 0:
|
131
|
+
return 0.0
|
132
|
+
|
133
|
+
# Annualize the mean daily excess returns
|
134
|
+
annualized_excess_return = (
|
135
|
+
MinMetrics.average(log_returns, weighting=weighting) * days_in_year
|
136
|
+
) - annual_risk_free_rate
|
137
|
+
return annualized_excess_return
|
138
|
+
|
139
|
+
@staticmethod
|
140
|
+
def ann_volatility(
|
141
|
+
log_returns: list[float],
|
142
|
+
ddof: int = 1,
|
143
|
+
weighting=False,
|
144
|
+
indices: list[int] = None,
|
145
|
+
days_in_year: int = DAYS_IN_YEAR_CRYPTO,
|
146
|
+
) -> float:
|
147
|
+
"""
|
148
|
+
Calculates annualized volatility ASSUMING DAILY OBSERVATIONS
|
149
|
+
"""
|
150
|
+
if indices is None:
|
151
|
+
indices = list(range(len(log_returns)))
|
152
|
+
|
153
|
+
# Annualize volatility of the daily log returns assuming sample variance
|
154
|
+
window = len(indices)
|
155
|
+
if window < ddof + 1:
|
156
|
+
return np.inf
|
157
|
+
|
158
|
+
annualized_volatility = np.sqrt(
|
159
|
+
MinMetrics.variance(
|
160
|
+
log_returns, ddof=ddof, weighting=weighting, indices=indices
|
161
|
+
)
|
162
|
+
* days_in_year
|
163
|
+
)
|
164
|
+
|
165
|
+
return float(annualized_volatility)
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def ann_downside_volatility(
|
169
|
+
log_returns: list[float],
|
170
|
+
target: int = None,
|
171
|
+
weighting=False,
|
172
|
+
days_in_year: int = DAYS_IN_YEAR_CRYPTO,
|
173
|
+
) -> float:
|
174
|
+
"""
|
175
|
+
Calculates annualized downside volatility
|
176
|
+
"""
|
177
|
+
if target is None:
|
178
|
+
target = MinMetrics.log_risk_free_rate(days_in_year=days_in_year)
|
179
|
+
|
180
|
+
indices = [i for i, log_return in enumerate(log_returns) if log_return < target]
|
181
|
+
return MinMetrics.ann_volatility(
|
182
|
+
log_returns, weighting=weighting, indices=indices, days_in_year=days_in_year
|
183
|
+
)
|
184
|
+
|
185
|
+
@staticmethod
|
186
|
+
def daily_max_drawdown(log_returns: list[float]) -> float:
|
187
|
+
"""
|
188
|
+
Calculates the daily maximum drawdown
|
189
|
+
"""
|
190
|
+
if len(log_returns) == 0:
|
191
|
+
return 0.0
|
192
|
+
|
193
|
+
# More efficient implementation using cumulative sum of log returns
|
194
|
+
cumulative_log_returns = np.cumsum(log_returns)
|
195
|
+
|
196
|
+
# Maximum cumulative log return at each point
|
197
|
+
running_max_log = np.maximum.accumulate(cumulative_log_returns)
|
198
|
+
|
199
|
+
# Drawdown = 1 - exp(current - peak)
|
200
|
+
# This gives us the percentage decline from the peak
|
201
|
+
drawdowns = 1 - np.exp(cumulative_log_returns - running_max_log)
|
202
|
+
|
203
|
+
# Find the maximum drawdown
|
204
|
+
max_drawdown = np.max(drawdowns)
|
205
|
+
|
206
|
+
return max_drawdown
|
207
|
+
|
208
|
+
@staticmethod
|
209
|
+
def sharpe(
|
210
|
+
log_returns: list[float],
|
211
|
+
bypass_confidence: bool = False,
|
212
|
+
weighting: bool = False,
|
213
|
+
days_in_year: int = DAYS_IN_YEAR_CRYPTO,
|
214
|
+
**kwargs,
|
215
|
+
) -> float:
|
216
|
+
"""
|
217
|
+
Calculates the Sharpe ratio
|
218
|
+
"""
|
219
|
+
# Need a large enough sample size
|
220
|
+
if len(log_returns) < MinMetrics.STATISTICAL_CONFIDENCE_MINIMUM_N:
|
221
|
+
if not bypass_confidence:
|
222
|
+
return MinMetrics.SHARPE_NOCONFIDENCE_VALUE
|
223
|
+
|
224
|
+
# Hyperparameter
|
225
|
+
min_std_dev = MinMetrics.SHARPE_STDDEV_MINIMUM
|
226
|
+
|
227
|
+
excess_return = MinMetrics.ann_excess_return(
|
228
|
+
log_returns, weighting=weighting, days_in_year=days_in_year
|
229
|
+
)
|
230
|
+
volatility = MinMetrics.ann_volatility(
|
231
|
+
log_returns, weighting=weighting, days_in_year=days_in_year
|
232
|
+
)
|
233
|
+
|
234
|
+
return float(excess_return / max(volatility, min_std_dev))
|
235
|
+
|
236
|
+
@staticmethod
|
237
|
+
def omega(
|
238
|
+
log_returns: list[float],
|
239
|
+
bypass_confidence: bool = False,
|
240
|
+
weighting: bool = False,
|
241
|
+
**kwargs,
|
242
|
+
) -> float:
|
243
|
+
"""
|
244
|
+
Calculates the Omega ratio
|
245
|
+
"""
|
246
|
+
# Need a large enough sample size
|
247
|
+
if len(log_returns) < MinMetrics.STATISTICAL_CONFIDENCE_MINIMUM_N:
|
248
|
+
if not bypass_confidence:
|
249
|
+
return MinMetrics.OMEGA_NOCONFIDENCE_VALUE
|
250
|
+
|
251
|
+
if weighting:
|
252
|
+
weighing_array = MinMetrics.weighting_distribution(log_returns)
|
253
|
+
|
254
|
+
positive_indices = []
|
255
|
+
negative_indices = []
|
256
|
+
product_sum_positive = product_sum_negative = 0
|
257
|
+
sum_of_weights_positive = sum_of_weights_negative = (
|
258
|
+
MinMetrics.OMEGA_LOSS_MINIMUM
|
259
|
+
)
|
260
|
+
|
261
|
+
for c, log_return in enumerate(log_returns):
|
262
|
+
if log_return > 0:
|
263
|
+
positive_indices.append(c)
|
264
|
+
else:
|
265
|
+
negative_indices.append(c)
|
266
|
+
|
267
|
+
log_return_arr = np.array(log_returns)
|
268
|
+
|
269
|
+
if len(positive_indices) > 0:
|
270
|
+
positive_indices_arr = np.array(positive_indices)
|
271
|
+
sum_of_weights_positive = max(
|
272
|
+
np.sum(weighing_array[positive_indices_arr]),
|
273
|
+
MinMetrics.OMEGA_LOSS_MINIMUM,
|
274
|
+
)
|
275
|
+
|
276
|
+
product_sum_positive = np.sum(
|
277
|
+
np.multiply(
|
278
|
+
log_return_arr[positive_indices_arr],
|
279
|
+
weighing_array[positive_indices_arr],
|
280
|
+
)
|
281
|
+
)
|
282
|
+
|
283
|
+
if len(negative_indices) > 0:
|
284
|
+
negative_indices_arr = np.array(negative_indices)
|
285
|
+
sum_of_weights_negative = max(
|
286
|
+
np.sum(weighing_array[negative_indices_arr]),
|
287
|
+
MinMetrics.OMEGA_LOSS_MINIMUM,
|
288
|
+
)
|
289
|
+
product_sum_negative = np.sum(
|
290
|
+
np.multiply(
|
291
|
+
log_return_arr[negative_indices_arr],
|
292
|
+
weighing_array[negative_indices_arr],
|
293
|
+
)
|
294
|
+
)
|
295
|
+
|
296
|
+
positive_sum = product_sum_positive * sum_of_weights_negative
|
297
|
+
negative_sum = product_sum_negative * sum_of_weights_positive
|
298
|
+
|
299
|
+
else:
|
300
|
+
positive_sum = 0
|
301
|
+
negative_sum = 0
|
302
|
+
|
303
|
+
for log_return in log_returns:
|
304
|
+
if log_return > 0:
|
305
|
+
positive_sum += log_return
|
306
|
+
else:
|
307
|
+
negative_sum += log_return
|
308
|
+
|
309
|
+
numerator = positive_sum
|
310
|
+
denominator = max(abs(negative_sum), MinMetrics.OMEGA_LOSS_MINIMUM)
|
311
|
+
|
312
|
+
return float(numerator / denominator)
|
313
|
+
|
314
|
+
@staticmethod
|
315
|
+
def statistical_confidence(
|
316
|
+
log_returns: list[float], bypass_confidence: bool = False, **kwargs
|
317
|
+
) -> float:
|
318
|
+
"""
|
319
|
+
Calculates statistical confidence using t-test
|
320
|
+
"""
|
321
|
+
# Impose a minimum sample size on the miner
|
322
|
+
if len(log_returns) < MinMetrics.STATISTICAL_CONFIDENCE_MINIMUM_N:
|
323
|
+
if not bypass_confidence or len(log_returns) < 2:
|
324
|
+
return MinMetrics.STATISTICAL_CONFIDENCE_NOCONFIDENCE_VALUE
|
325
|
+
|
326
|
+
# Also now check for zero variance condition
|
327
|
+
zero_variance_condition = bool(np.isclose(np.var(log_returns), 0))
|
328
|
+
if zero_variance_condition:
|
329
|
+
return MinMetrics.STATISTICAL_CONFIDENCE_NOCONFIDENCE_VALUE
|
330
|
+
|
331
|
+
res = ttest_1samp(log_returns, 0, alternative="greater")
|
332
|
+
return float(res.statistic)
|
333
|
+
|
334
|
+
@staticmethod
|
335
|
+
def sortino(
|
336
|
+
log_returns: list[float],
|
337
|
+
bypass_confidence: bool = False,
|
338
|
+
weighting: bool = False,
|
339
|
+
days_in_year: int = DAYS_IN_YEAR_CRYPTO,
|
340
|
+
**kwargs,
|
341
|
+
) -> float:
|
342
|
+
"""
|
343
|
+
Calculates the Sortino ratio
|
344
|
+
"""
|
345
|
+
# Need a large enough sample size
|
346
|
+
if len(log_returns) < MinMetrics.STATISTICAL_CONFIDENCE_MINIMUM_N:
|
347
|
+
if not bypass_confidence:
|
348
|
+
return MinMetrics.SORTINO_NOCONFIDENCE_VALUE
|
349
|
+
|
350
|
+
# Hyperparameter
|
351
|
+
min_downside = MinMetrics.SORTINO_DOWNSIDE_MINIMUM
|
352
|
+
|
353
|
+
# Sortino ratio is calculated as the mean of the returns divided by the standard deviation of the negative returns
|
354
|
+
excess_return = MinMetrics.ann_excess_return(
|
355
|
+
log_returns, weighting=weighting, days_in_year=days_in_year
|
356
|
+
)
|
357
|
+
downside_volatility = MinMetrics.ann_downside_volatility(
|
358
|
+
log_returns, weighting=weighting, days_in_year=days_in_year
|
359
|
+
)
|
360
|
+
|
361
|
+
return float(excess_return / max(downside_volatility, min_downside))
|
362
|
+
|
363
|
+
@staticmethod
|
364
|
+
def calmar(
|
365
|
+
log_returns: list[float],
|
366
|
+
bypass_confidence: bool = False,
|
367
|
+
weighting: bool = False,
|
368
|
+
days_in_year: int = DAYS_IN_YEAR_CRYPTO,
|
369
|
+
**kwargs,
|
370
|
+
) -> float:
|
371
|
+
"""
|
372
|
+
Calculates the Calmar ratio (simplified version without ledger dependency)
|
373
|
+
"""
|
374
|
+
# Positional Component
|
375
|
+
if len(log_returns) < 30: # Simplified confidence check
|
376
|
+
if not bypass_confidence:
|
377
|
+
return MinMetrics.CALMAR_NOCONFIDENCE_VALUE
|
378
|
+
|
379
|
+
base_return_percentage = (
|
380
|
+
MinMetrics.average(log_returns, weighting=weighting) * days_in_year * 100
|
381
|
+
)
|
382
|
+
max_drawdown = MinMetrics.daily_max_drawdown(log_returns)
|
383
|
+
|
384
|
+
# Simplified risk normalization factor (without full ledger context)
|
385
|
+
drawdown_normalization_factor = 1.0 / max(max_drawdown, 0.01)
|
386
|
+
|
387
|
+
raw_calmar = float(base_return_percentage * drawdown_normalization_factor)
|
388
|
+
return min(
|
389
|
+
raw_calmar, MinMetrics.CALMAR_RATIO_CAP
|
390
|
+
) # Cap the Calmar ratio to prevent extreme values
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/proof_generator.py
RENAMED
@@ -3,6 +3,8 @@ import toml
|
|
3
3
|
import re
|
4
4
|
import os
|
5
5
|
import time
|
6
|
+
from .min_metrics import MinMetrics
|
7
|
+
import math
|
6
8
|
|
7
9
|
# Constants for the circuit
|
8
10
|
MAX_CHECKPOINTS = 200
|
@@ -185,10 +187,10 @@ def field_to_toml_value(f):
|
|
185
187
|
return str(f)
|
186
188
|
|
187
189
|
|
188
|
-
def
|
190
|
+
def run_bb_prove(circuit_dir):
|
189
191
|
"""
|
190
|
-
Runs barretenberg proving
|
191
|
-
Returns proof generation time and
|
192
|
+
Runs barretenberg proving.
|
193
|
+
Returns proof generation time and status.
|
192
194
|
"""
|
193
195
|
print("\n--- Running Barretenberg Proof Generation ---")
|
194
196
|
|
@@ -212,7 +214,6 @@ def run_bb_prove_and_verify(circuit_dir, circuit_name="main"):
|
|
212
214
|
circuit_file = os.path.join(target_dir, "circuits.json")
|
213
215
|
|
214
216
|
proof_file = proof_dir
|
215
|
-
vk_file = os.path.join(vk_dir, "vk")
|
216
217
|
|
217
218
|
prove_start = time.time()
|
218
219
|
prove_result = subprocess.run(
|
@@ -242,25 +243,7 @@ def run_bb_prove_and_verify(circuit_dir, circuit_name="main"):
|
|
242
243
|
return None, False
|
243
244
|
|
244
245
|
print(f"Proof generated in {prove_time:.3f}s")
|
245
|
-
|
246
|
-
public_inputs_file = os.path.join(proof_dir, "public_inputs")
|
247
|
-
verify_result = subprocess.run(
|
248
|
-
["bb", "verify", "-p", proof_file, "-k", vk_file, "-i", public_inputs_file],
|
249
|
-
capture_output=True,
|
250
|
-
text=True,
|
251
|
-
cwd=circuit_dir,
|
252
|
-
)
|
253
|
-
|
254
|
-
verification_success = verify_result.returncode == 0
|
255
|
-
|
256
|
-
if verification_success:
|
257
|
-
print("✅ Proof verification: PASSED")
|
258
|
-
else:
|
259
|
-
print("❌ Proof verification: FAILED")
|
260
|
-
print(verify_result.stdout)
|
261
|
-
print(verify_result.stderr)
|
262
|
-
|
263
|
-
return prove_time, verification_success
|
246
|
+
return prove_time, True
|
264
247
|
|
265
248
|
except Exception as e:
|
266
249
|
print(f"Error during proof generation/verification: {e}")
|
@@ -587,7 +570,47 @@ def generate_proof(data=None, miner_hotkey=None, verbose=None):
|
|
587
570
|
sortino_ratio_scaled = sortino_ratio_raw / SCALING_FACTOR
|
588
571
|
stat_confidence_scaled = stat_confidence_raw / SCALING_FACTOR
|
589
572
|
|
590
|
-
prove_time, verification_success =
|
573
|
+
prove_time, verification_success = run_bb_prove(main_circuit_dir)
|
574
|
+
|
575
|
+
# Calculate MinMetrics (Python) for comparison with ZK circuit
|
576
|
+
try:
|
577
|
+
|
578
|
+
# Extract daily log returns from checkpoint data (same as ZK circuit)
|
579
|
+
daily_log_returns = []
|
580
|
+
for cp in cps:
|
581
|
+
if cp["gain"] > 0:
|
582
|
+
daily_log_returns.append(math.log(1 + cp["gain"]))
|
583
|
+
elif cp["loss"] > 0:
|
584
|
+
daily_log_returns.append(math.log(1 - cp["loss"]))
|
585
|
+
else:
|
586
|
+
daily_log_returns.append(0.0)
|
587
|
+
|
588
|
+
if len(daily_log_returns) > 0:
|
589
|
+
python_avg_daily_pnl = MinMetrics.average(daily_log_returns)
|
590
|
+
python_sharpe = MinMetrics.sharpe(daily_log_returns, bypass_confidence=True)
|
591
|
+
python_max_drawdown = MinMetrics.daily_max_drawdown(daily_log_returns)
|
592
|
+
python_calmar = MinMetrics.calmar(daily_log_returns, bypass_confidence=True)
|
593
|
+
python_omega = MinMetrics.omega(daily_log_returns, bypass_confidence=True)
|
594
|
+
python_sortino = MinMetrics.sortino(
|
595
|
+
daily_log_returns, bypass_confidence=True
|
596
|
+
)
|
597
|
+
python_stat_confidence = MinMetrics.statistical_confidence(
|
598
|
+
daily_log_returns, bypass_confidence=True
|
599
|
+
)
|
600
|
+
else:
|
601
|
+
python_avg_daily_pnl = python_sharpe = python_max_drawdown = (
|
602
|
+
python_calmar
|
603
|
+
) = python_omega = python_sortino = python_stat_confidence = 0.0
|
604
|
+
|
605
|
+
except Exception as e:
|
606
|
+
if verbose:
|
607
|
+
print(f"Warning: Could not calculate Python MinMetrics: {e}")
|
608
|
+
import traceback
|
609
|
+
|
610
|
+
print(f"Traceback: {traceback.format_exc()}")
|
611
|
+
python_avg_daily_pnl = python_sharpe = python_max_drawdown = python_calmar = (
|
612
|
+
python_omega
|
613
|
+
) = python_sortino = python_stat_confidence = "N/A"
|
591
614
|
|
592
615
|
# Always print key production info: hotkey and verification status
|
593
616
|
print(f"Hotkey: {miner_hotkey}")
|
@@ -601,10 +624,41 @@ def generate_proof(data=None, miner_hotkey=None, verbose=None):
|
|
601
624
|
print(f"Omega Ratio: {omega_ratio_scaled:.9f}")
|
602
625
|
print(f"Sortino Ratio: {sortino_ratio_scaled:.9f}")
|
603
626
|
print(f"Statistical Confidence: {stat_confidence_scaled:.9f}")
|
604
|
-
|
627
|
+
|
628
|
+
# Print Python MinMetrics for comparison
|
629
|
+
print("\n--- PYTHON MINMETRICS ---")
|
630
|
+
if isinstance(python_avg_daily_pnl, (int, float)):
|
631
|
+
print(f"Average Daily PnL: {python_avg_daily_pnl:.9f}")
|
632
|
+
else:
|
633
|
+
print(f"Average Daily PnL: {python_avg_daily_pnl}")
|
634
|
+
if isinstance(python_sharpe, (int, float)):
|
635
|
+
print(f"Sharpe Ratio: {python_sharpe:.9f}")
|
636
|
+
else:
|
637
|
+
print(f"Sharpe Ratio: {python_sharpe}")
|
638
|
+
if isinstance(python_max_drawdown, (int, float)):
|
605
639
|
print(
|
606
|
-
f"
|
640
|
+
f"Max Drawdown: {python_max_drawdown:.9f} ({python_max_drawdown * 100:.6f}%)"
|
607
641
|
)
|
642
|
+
else:
|
643
|
+
print(f"Max Drawdown: {python_max_drawdown}")
|
644
|
+
if isinstance(python_calmar, (int, float)):
|
645
|
+
print(f"Calmar Ratio: {python_calmar:.9f}")
|
646
|
+
else:
|
647
|
+
print(f"Calmar Ratio: {python_calmar}")
|
648
|
+
if isinstance(python_omega, (int, float)):
|
649
|
+
print(f"Omega Ratio: {python_omega:.9f}")
|
650
|
+
else:
|
651
|
+
print(f"Omega Ratio: {python_omega}")
|
652
|
+
if isinstance(python_sortino, (int, float)):
|
653
|
+
print(f"Sortino Ratio: {python_sortino:.9f}")
|
654
|
+
else:
|
655
|
+
print(f"Sortino Ratio: {python_sortino}")
|
656
|
+
if isinstance(python_stat_confidence, (int, float)):
|
657
|
+
print(f"Statistical Confidence: {python_stat_confidence:.9f}")
|
658
|
+
else:
|
659
|
+
print(f"Statistical Confidence: {python_stat_confidence}")
|
660
|
+
if prove_time is not None:
|
661
|
+
print(f"Proof generated in {prove_time}s")
|
608
662
|
else:
|
609
663
|
print("Proof generation failed")
|
610
664
|
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: proof-of-portfolio
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.46
|
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
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/SOURCES.txt
RENAMED
@@ -6,6 +6,7 @@ proof_of_portfolio/__init__.py
|
|
6
6
|
proof_of_portfolio/_version.py
|
7
7
|
proof_of_portfolio/analyze_data.py
|
8
8
|
proof_of_portfolio/main.py
|
9
|
+
proof_of_portfolio/min_metrics.py
|
9
10
|
proof_of_portfolio/miner.py
|
10
11
|
proof_of_portfolio/post_install.py
|
11
12
|
proof_of_portfolio/proof_generator.py
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
5
5
|
|
6
6
|
[project]
|
7
7
|
name = "proof-of-portfolio"
|
8
|
-
version = "0.0.
|
8
|
+
version = "0.0.46"
|
9
9
|
description = "Zero-Knowledge Proof framework for verifiable, private portfolio performance metrics"
|
10
10
|
readme = "README.md"
|
11
11
|
authors = [{ name = "Inference Labs, Inc.", email = "info@inferencelabs.com" }]
|
File without changes
|
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/Nargo.toml
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/proof/proof
RENAMED
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/circuits/src/main.nr
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/drawdown.py
RENAMED
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/demos/log_returns.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/signal_processor.py
RENAMED
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/tree_generator/Nargo.toml
RENAMED
File without changes
|
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio/tree_generator/target.gz
RENAMED
File without changes
|
File without changes
|
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/entry_points.txt
RENAMED
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/requires.txt
RENAMED
File without changes
|
{proof_of_portfolio-0.0.44 → proof_of_portfolio-0.0.46}/proof_of_portfolio.egg-info/top_level.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|