4
4
import numpy as np
5
5
from pandas import DataFrame
6
6
7
+ from precog import constants
7
8
from precog .protocol import Challenge
8
9
from precog .utils .cm_data import CMData
9
10
from precog .utils .general import pd_to_dict , rank
@@ -15,42 +16,91 @@ def calc_rewards(
15
16
self ,
16
17
responses : List [Challenge ],
17
18
) -> np .ndarray :
19
+ evaluation_window_hours = constants .EVALUATION_WINDOW_HOURS
20
+ prediction_future_hours = constants .PREDICTION_FUTURE_HOURS
21
+ prediction_interval_minutes = constants .PREDICTION_INTERVAL_MINUTES
22
+
23
+ expected_timepoints = evaluation_window_hours * 60 / prediction_interval_minutes
24
+
18
25
# preallocate
19
26
point_errors = []
20
27
interval_errors = []
28
+ completeness_scores = []
21
29
decay = 0.9
22
30
weights = np .linspace (0 , len (self .available_uids ) - 1 , len (self .available_uids ))
23
31
decayed_weights = decay ** weights
24
32
timestamp = responses [0 ].timestamp
33
+ bt .logging .debug (f"Calculating rewards for timestamp: { timestamp } " )
25
34
cm = CMData ()
26
- start_time : str = to_str (get_before (timestamp = timestamp , hours = 1 ))
27
- end_time : str = to_str (to_datetime (timestamp )) # built-ins handle CM API's formatting
35
+ # Adjust time window to look at predictions that have had time to mature
36
+ # Start: (evaluation_window + prediction) hours ago
37
+ # End: prediction_future_hours ago (to ensure all predictions have matured)
38
+ start_time : str = to_str (get_before (timestamp = timestamp , hours = evaluation_window_hours + prediction_future_hours ))
39
+ end_time : str = to_str (to_datetime (get_before (timestamp = timestamp , hours = prediction_future_hours )))
28
40
# Query CM API for sample standard deviation of the 1s residuals
29
41
historical_price_data : DataFrame = cm .get_CM_ReferenceRate (
30
42
assets = "BTC" , start = start_time , end = end_time , frequency = "1s"
31
43
)
32
44
cm_data = pd_to_dict (historical_price_data )
45
+
33
46
for uid , response in zip (self .available_uids , responses ):
34
47
current_miner = self .MinerHistory [uid ]
35
48
self .MinerHistory [uid ].add_prediction (response .timestamp , response .prediction , response .interval )
36
- prediction_dict , interval_dict = current_miner .format_predictions (response .timestamp )
37
- mature_time_dict = mature_dictionary (prediction_dict )
49
+ # Get predictions from the evaluation window that have had time to mature
50
+ prediction_dict , interval_dict = current_miner .format_predictions (
51
+ reference_timestamp = get_before (timestamp , hours = prediction_future_hours ),
52
+ hours = evaluation_window_hours ,
53
+ )
54
+
55
+ # Mature the predictions (shift forward by 1 hour)
56
+ mature_time_dict = mature_dictionary (prediction_dict , hours = prediction_future_hours )
57
+
38
58
preds , price , aligned_pred_timestamps = align_timepoints (mature_time_dict , cm_data )
39
- for i , j , k in zip (preds , price , aligned_pred_timestamps ):
40
- bt .logging .debug (f"Prediction: { i } | Price: { j } | Aligned Prediction: { k } " )
59
+
60
+ num_predictions = len (preds ) if preds is not None else 0
61
+
62
+ # Ensure a maximum ratio of 1.0
63
+ completeness_ratio = min (num_predictions / expected_timepoints , 1.0 )
64
+ completeness_scores .append (completeness_ratio )
65
+ bt .logging .debug (
66
+ f"UID: { uid } | Completeness: { completeness_ratio :.2f} ({ num_predictions } /{ expected_timepoints } )"
67
+ )
68
+
69
+ # for i, j, k in zip(preds, price, aligned_pred_timestamps):
70
+ # bt.logging.debug(f"Prediction: {i} | Price: {j} | Aligned Prediction: {k}")
41
71
inters , interval_prices , aligned_int_timestamps = align_timepoints (interval_dict , cm_data )
42
- for i , j , k in zip (inters , interval_prices , aligned_int_timestamps ):
43
- bt .logging .debug (f"Interval: { i } | Interval Price: { j } | Aligned TS: { k } " )
44
- point_errors .append (point_error (preds , price ))
72
+ # for i, j, k in zip(inters, interval_prices, aligned_int_timestamps):
73
+ # bt.logging.debug(f"Interval: {i} | Interval Price: {j} | Aligned TS: {k}")
74
+
75
+ # Penalize miners with missing predictions by increasing their point error
76
+ if preds is None or len (preds ) == 0 :
77
+ point_errors .append (np .inf ) # Maximum penalty for no predictions
78
+ else :
79
+ # Calculate error as normal, but apply completeness penalty
80
+ base_point_error = point_error (preds , price )
81
+ # Apply penalty inversely proportional to completeness
82
+ # This will increase error for incomplete prediction sets
83
+ adjusted_point_error = base_point_error / completeness_ratio
84
+ point_errors .append (adjusted_point_error )
85
+
45
86
if any ([np .isnan (inters ).any (), np .isnan (interval_prices ).any ()]):
46
87
interval_errors .append (0 )
47
88
else :
48
- interval_errors .append (interval_error (inters , interval_prices ))
89
+ # Similarly, penalize interval errors for incompleteness
90
+ base_interval_error = interval_error (inters , interval_prices )
91
+ adjusted_interval_error = base_interval_error * completeness_ratio # Lower score for incomplete sets
92
+ interval_errors .append (adjusted_interval_error )
93
+
49
94
bt .logging .debug (f"UID: { uid } | point_errors: { point_errors [- 1 ]} | interval_errors: { interval_errors [- 1 ]} " )
50
95
51
96
point_ranks = rank (np .array (point_errors ))
52
97
interval_ranks = rank (- np .array (interval_errors )) # 1 is best, 0 is worst, so flip it
53
- rewards = (decayed_weights [point_ranks ] + decayed_weights [interval_ranks ]) / 2
98
+
99
+ base_rewards = (decayed_weights [point_ranks ] + decayed_weights [interval_ranks ]) / 2
100
+
101
+ # Simply multiply the final rewards by the completeness score
102
+ rewards = base_rewards * np .array (completeness_scores )
103
+
54
104
return rewards
55
105
56
106
0 commit comments