proof-of-portfolio 0.0.45__py3-none-any.whl → 0.0.47__py3-none-any.whl
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/__init__.py +8 -6
- proof_of_portfolio/_version.py +1 -1
- proof_of_portfolio/min_metrics.py +390 -0
- proof_of_portfolio/proof_generator.py +75 -2
- {proof_of_portfolio-0.0.45.dist-info → proof_of_portfolio-0.0.47.dist-info}/METADATA +1 -1
- {proof_of_portfolio-0.0.45.dist-info → proof_of_portfolio-0.0.47.dist-info}/RECORD +9 -8
- {proof_of_portfolio-0.0.45.dist-info → proof_of_portfolio-0.0.47.dist-info}/WHEEL +0 -0
- {proof_of_portfolio-0.0.45.dist-info → proof_of_portfolio-0.0.47.dist-info}/entry_points.txt +0 -0
- {proof_of_portfolio-0.0.45.dist-info → proof_of_portfolio-0.0.47.dist-info}/top_level.txt +0 -0
proof_of_portfolio/__init__.py
CHANGED
@@ -52,14 +52,14 @@ def requires_dependencies(func):
|
|
52
52
|
return wrapper
|
53
53
|
|
54
54
|
|
55
|
-
def _prove_worker(miner_data, hotkey):
|
55
|
+
def _prove_worker(miner_data, hotkey, verbose=False):
|
56
56
|
"""
|
57
57
|
Worker function to run proof generation in a separate process.
|
58
58
|
"""
|
59
59
|
try:
|
60
60
|
from .proof_generator import generate_proof
|
61
61
|
|
62
|
-
result = generate_proof(miner_data, hotkey)
|
62
|
+
result = generate_proof(miner_data, hotkey, verbose=verbose)
|
63
63
|
|
64
64
|
return {
|
65
65
|
"status": "success",
|
@@ -81,13 +81,14 @@ def _prove_worker(miner_data, hotkey):
|
|
81
81
|
|
82
82
|
|
83
83
|
@requires_dependencies
|
84
|
-
async def prove(miner_data, hotkey):
|
84
|
+
async def prove(miner_data, hotkey, verbose=False):
|
85
85
|
"""
|
86
86
|
Generate zero-knowledge proof for miner portfolio data asynchronously.
|
87
87
|
|
88
88
|
Args:
|
89
89
|
miner_data: Dictionary containing perf_ledgers and positions for the miner
|
90
90
|
hotkey: Miner's hotkey
|
91
|
+
verbose: Boolean to control logging verbosity
|
91
92
|
|
92
93
|
Returns:
|
93
94
|
Dictionary with proof results including status, portfolio_metrics, etc.
|
@@ -97,7 +98,7 @@ async def prove(miner_data, hotkey):
|
|
97
98
|
with ProcessPoolExecutor(max_workers=1) as executor:
|
98
99
|
try:
|
99
100
|
result = await loop.run_in_executor(
|
100
|
-
executor, _prove_worker, miner_data, hotkey
|
101
|
+
executor, _prove_worker, miner_data, hotkey, verbose
|
101
102
|
)
|
102
103
|
return result
|
103
104
|
except Exception as e:
|
@@ -108,15 +109,16 @@ async def prove(miner_data, hotkey):
|
|
108
109
|
}
|
109
110
|
|
110
111
|
|
111
|
-
def prove_sync(miner_data, hotkey):
|
112
|
+
def prove_sync(miner_data, hotkey, verbose=False):
|
112
113
|
"""
|
113
114
|
Synchronous wrapper for the prove function for backward compatibility.
|
114
115
|
|
115
116
|
Args:
|
116
117
|
miner_data: Dictionary containing perf_ledgers and positions for the miner
|
117
118
|
hotkey: Miner's hotkey
|
119
|
+
verbose: Boolean to control logging verbosity
|
118
120
|
|
119
121
|
Returns:
|
120
122
|
Dictionary with proof results including status, portfolio_metrics, etc.
|
121
123
|
"""
|
122
|
-
return _prove_worker(miner_data, hotkey)
|
124
|
+
return _prove_worker(miner_data, hotkey, verbose)
|
proof_of_portfolio/_version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# This file is auto-generated during build
|
2
|
-
__version__ = "0.0.
|
2
|
+
__version__ = "0.0.47"
|
@@ -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
|
@@ -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
|
@@ -570,6 +572,46 @@ def generate_proof(data=None, miner_hotkey=None, verbose=None):
|
|
570
572
|
|
571
573
|
prove_time, verification_success = run_bb_prove(main_circuit_dir)
|
572
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"
|
614
|
+
|
573
615
|
# Always print key production info: hotkey and verification status
|
574
616
|
print(f"Hotkey: {miner_hotkey}")
|
575
617
|
print(f"Orders processed: {signals_count}")
|
@@ -582,10 +624,41 @@ def generate_proof(data=None, miner_hotkey=None, verbose=None):
|
|
582
624
|
print(f"Omega Ratio: {omega_ratio_scaled:.9f}")
|
583
625
|
print(f"Sortino Ratio: {sortino_ratio_scaled:.9f}")
|
584
626
|
print(f"Statistical Confidence: {stat_confidence_scaled:.9f}")
|
585
|
-
|
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)):
|
586
639
|
print(
|
587
|
-
f"
|
640
|
+
f"Max Drawdown: {python_max_drawdown:.9f} ({python_max_drawdown * 100:.6f}%)"
|
588
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")
|
589
662
|
else:
|
590
663
|
print("Proof generation failed")
|
591
664
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: proof-of-portfolio
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.47
|
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,10 +1,11 @@
|
|
1
|
-
proof_of_portfolio/__init__.py,sha256=
|
2
|
-
proof_of_portfolio/_version.py,sha256=
|
1
|
+
proof_of_portfolio/__init__.py,sha256=cb8b_zhdFvxvpq7I5g3bP5RM7yAmD8kv4lrcrhD3-1w,3580
|
2
|
+
proof_of_portfolio/_version.py,sha256=p0btOyieR5oGcDyMaFDVp1gIO7fOoskkRSjuDGVhg7k,66
|
3
3
|
proof_of_portfolio/analyze_data.py,sha256=t80ueFtBUzcWTrPiamiC1HXvw5Em-151zXJx0cCk8sQ,3709
|
4
4
|
proof_of_portfolio/main.py,sha256=JbvdAK7B6hw2sQdjT0EVCiglCQZgN3urKphdeWcW43w,30994
|
5
|
+
proof_of_portfolio/min_metrics.py,sha256=BAEpJdqYoR6RjkqX-M5BUTWlurSADvxAHuLFznH3AT8,13196
|
5
6
|
proof_of_portfolio/miner.py,sha256=eGXcPMRQVAepzXJj1ePbbDAf-72E9Yj9n-yfP7GohLw,17177
|
6
7
|
proof_of_portfolio/post_install.py,sha256=e-57Z_h-svQdjA8ibsM985MXmdV6-KLyymQm3Cgu8YM,5654
|
7
|
-
proof_of_portfolio/proof_generator.py,sha256=
|
8
|
+
proof_of_portfolio/proof_generator.py,sha256=U0ZXkoKJasj-i6reCCx8TEDXcmzjsetkV48KhTTao6Q,27317
|
8
9
|
proof_of_portfolio/signal_processor.py,sha256=JQjnkMJEbv_MWIgKNrjKjrBIcIT5pAkAlCneEOGsqT0,6702
|
9
10
|
proof_of_portfolio/validator.py,sha256=kt3BeaXOffv-h52PZBKV1YHUWtiGsnPte16m3EJpITY,3839
|
10
11
|
proof_of_portfolio/circuits/Nargo.toml,sha256=D6ycN7H3xiTcWHH5wz4qXYTXn7Ht0WgPr9w4B7d8ZGw,141
|
@@ -79,8 +80,8 @@ proof_of_portfolio/tree_generator/Nargo.toml,sha256=O6iSvb-EpV0XcETiDxNgSp7XKNiY
|
|
79
80
|
proof_of_portfolio/tree_generator/target.gz,sha256=7LPzAb8JvKWSDOzyW5vI_6NKQ0aB9cb31q4CWbchFSw,308614
|
80
81
|
proof_of_portfolio/tree_generator/src/main.nr,sha256=zHG_0OphqSROyeVc7yTSEULg4bYS8B-LsmvTzTl8aW4,2393
|
81
82
|
proof_of_portfolio/tree_generator/target/tree_generator.json,sha256=M_bI5et5ncgILJ_Czen4ZsGfWHwComEVxMLQpIWmN1k,1540889
|
82
|
-
proof_of_portfolio-0.0.
|
83
|
-
proof_of_portfolio-0.0.
|
84
|
-
proof_of_portfolio-0.0.
|
85
|
-
proof_of_portfolio-0.0.
|
86
|
-
proof_of_portfolio-0.0.
|
83
|
+
proof_of_portfolio-0.0.47.dist-info/METADATA,sha256=TyZe0lNQcrp9QRAyXRTo_NeFBDwceDc9zv-9-d0KyNE,7051
|
84
|
+
proof_of_portfolio-0.0.47.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
85
|
+
proof_of_portfolio-0.0.47.dist-info/entry_points.txt,sha256=KeLSJT_UJtr1WiLTkhlGqWQuPKbO_ylgj6OXOC2gJV4,53
|
86
|
+
proof_of_portfolio-0.0.47.dist-info/top_level.txt,sha256=sY5xZnE6YuiISK1IuRHPfl71NV0vXO3N3YA2li_SPXU,19
|
87
|
+
proof_of_portfolio-0.0.47.dist-info/RECORD,,
|
File without changes
|
{proof_of_portfolio-0.0.45.dist-info → proof_of_portfolio-0.0.47.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|