Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion synth/utils/sequential_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ def start(self):
while True:
try:
cycle_start_time = self.run_cycle(cycle_start_time)
self.first_run = False
except Exception:
bt.logging.exception("Error in cycle ")
cycle_start_time = get_current_time()
self.first_run = False
Comment on lines 30 to +33
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.first_run is now set to False even when run_cycle() raises. Previously it was only flipped after a successful cycle; with this change, a failed initial cycle will be treated as a subsequent run, which changes the delay calculation and can slow down recovery/retry scheduling. Consider only setting first_run = False after run_cycle() completes successfully (or track success explicitly).

Suggested change
except Exception:
bt.logging.exception("Error in cycle ")
cycle_start_time = get_current_time()
self.first_run = False
self.first_run = False
except Exception:
bt.logging.exception("Error in cycle ")
cycle_start_time = get_current_time()

Copilot uses AI. Check for mistakes.

def run_cycle(
self,
Expand Down
8 changes: 6 additions & 2 deletions synth/validator/miner_data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def get_latest_asset(self, time_length: int) -> str | None:
return None

@retry(
stop=stop_after_attempt(5),
wait=wait_random_exponential(multiplier=7),
stop=stop_after_attempt(3),
wait=wait_random_exponential(multiplier=2),
reraise=True,
before=before_log(bt.logging._logger, logging.DEBUG),
)
Expand Down Expand Up @@ -182,6 +182,7 @@ def save_responses(
return validator_requests_id # TODO: finish this: refactor to add the validator_requests_id in the score and reward table
except Exception as e:
bt.logging.exception(f"in save_responses (got an exception): {e}")
raise

@retry(
stop=stop_after_attempt(5),
Expand Down Expand Up @@ -406,6 +407,9 @@ def get_validator_requests_to_score(
ValidatorRequest.start_time
+ literal_column("INTERVAL '1 second'")
* ValidatorRequest.time_length
+ literal_column(
"INTERVAL '1 minute'"
) # add 1 minute to ensure that pyth has the last candle available
)

query = (
Expand Down
55 changes: 35 additions & 20 deletions synth/validator/price_data_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,29 +72,41 @@ def fetch_data(self, validator_request: ValidatorRequest) -> list:
"""
asset = str(validator_request.asset)

if asset in self.HYPERLIQUID_SYMBOL_MAP:
return self.fetch_data_hyperliquid(validator_request)
prices = []

start_time_int = from_iso_to_unix_time(
validator_request.start_time.isoformat()
)
params = {
"symbol": self.PYTH_SYMBOL_MAP[asset],
"resolution": 1,
"from": start_time_int,
"to": start_time_int + validator_request.time_length,
}
if asset in self.HYPERLIQUID_SYMBOL_MAP:
prices = self.fetch_data_hyperliquid(validator_request)
else:
start_time_int = from_iso_to_unix_time(
validator_request.start_time.isoformat()
)
params = {
"symbol": self.PYTH_SYMBOL_MAP[asset],
"resolution": 1,
"from": start_time_int,
"to": start_time_int + validator_request.time_length,
}

response = requests.get(self.PYTH_BASE_URL, params=params)
response.raise_for_status()
data = response.json()

prices = self._transform_data(
data,
start_time_int,
int(validator_request.time_increment),
int(validator_request.time_length),
)

response = requests.get(self.PYTH_BASE_URL, params=params)
response.raise_for_status()
data = response.json()
if not prices or np.isnan(prices[-1]):
bt.logging.warning(
f"missing price data for the last timestamp for asset {asset} in request {validator_request.id}"
)
raise ValueError(
f"missing price data for the last timestamp for asset {asset} in request {validator_request.id}"
)
Comment on lines +105 to +107
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new behavior raises when the last price is NaN, but there’s no unit test covering the "missing last timestamp" path (and the existing "no prices" behavior should remain well-defined). Please add/adjust tests to cover (1) last value missing => error raised, and (2) empty response => returns [] (or whatever behavior is intended).

Copilot uses AI. Check for mistakes.

return self._transform_data(
data,
start_time_int,
int(validator_request.time_increment),
int(validator_request.time_length),
)
return prices

def fetch_data_hyperliquid(
self, validator_request: ValidatorRequest
Expand Down Expand Up @@ -194,6 +206,9 @@ def _transform_data(
if len(timestamps) != int(time_length / time_increment) + 1:
# Note: this part of code should never be activated; just included for precaution
if len(timestamps) == int(time_length / time_increment) + 2:
bt.logging.warning(
f"Unexpected number of timestamps generated. Expected {int(time_length / time_increment) + 1} but got {len(timestamps)}. Adjusting the timestamps list by removing the extra timestamp."
)
if data["t"][-1] < timestamps[1]:
timestamps = timestamps[:-1]
elif data["t"][0] > timestamps[0]:
Expand Down
21 changes: 5 additions & 16 deletions tests/test_price_data_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,23 @@ def test_fetch_data_gap_3(self):
# 1739974740 - 2025-02-19T14:19:00+00:00
# 1739974800 - 2025-02-19T14:20:00+00:00
# 1739974860 - 2025-02-19T14:21:00+00:00
# gap - 2025-02-19T14:22:00+00:00
# 1739974920 - 2025-02-19T14:22:00+00:00
mock_response = {
"t": [
1739974320,
1739974680,
1739974740,
1739974800,
1739974860,
1739974920,
],
"c": [
100000.23,
108000.867,
99000.23,
97123.55,
105123.345,
107995.889,
],
}

Expand All @@ -144,7 +146,7 @@ def test_fetch_data_gap_3(self):
validator_request_eth = ValidatorRequest(
asset="ETH",
start_time=datetime.fromisoformat("2025-02-19T14:12:00+00:00"),
time_length=600,
time_length=540,
time_increment=120,
)

Expand All @@ -156,22 +158,9 @@ def test_fetch_data_gap_3(self):
np.nan,
108000.867,
97123.55,
np.nan,
107995.889,
]

def test_fetch_data_no_prices(self):
mock_response: dict = {
"t": [],
"c": [],
}

with patch("requests.get") as mock_get:
mock_get.return_value.json.return_value = mock_response

result = self.dataProvider.fetch_data(validator_request)

assert result == []

def test_fetch_data_gap_from_start(self):
# gap - 2025-02-19T14:12:00+00:00
# gap - 2025-02-19T14:13:00+00:00
Expand Down
Loading