Skip to content
Open
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
45 changes: 45 additions & 0 deletions synth_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,51 @@ def _get(self, path: str, mock_path_parts: list[str], params: dict | None = None
return self._load_mock(*mock_path_parts)
return self._request(path, params)

# ─── Asset Summary Helper ────────────────────────────────────────

def get_asset_summary(self, asset: str, horizon: str = "24h") -> dict:
"""
Lightweight helper to expose the latest forecast snapshot for an asset.

This is primarily used by downstream tools (e.g. Tide Chart gTrade
integration) that only need a current price and basic forecast context,
without re‑implementing Synth API plumbing.

Args:
asset: Asset symbol (must be one of SUPPORTED_ASSETS)
horizon: Forecast horizon — "1h" or "24h" (default: "24h")

Returns:
Dict with at least:
- asset
- horizon
- current_price
- forecast_future (if available from prediction percentiles)

Raises:
ValueError: If asset or horizon is not supported.
FileNotFoundError / requests.HTTPError: Propagated from underlying
forecast fetch when data is unavailable.
"""
if asset not in SUPPORTED_ASSETS:
raise ValueError(f"Unsupported asset: {asset}. Supported: {SUPPORTED_ASSETS}")
if horizon not in SUPPORTED_HORIZONS:
raise ValueError(
f"Unsupported horizon: {horizon}. Supported: {SUPPORTED_HORIZONS}"
)

pct = self.get_prediction_percentiles(asset, horizon=horizon)
summary: dict = {
"asset": asset,
"horizon": horizon,
"current_price": pct.get("current_price"),
}

if "forecast_future" in pct:
summary["forecast_future"] = pct["forecast_future"]

return summary

# ─── Prediction Percentiles ──────────────────────────────────────

def get_prediction_percentiles(self, asset: str, horizon: str = "24h") -> dict:
Expand Down
22 changes: 22 additions & 0 deletions tests/test_synth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,25 @@ def test_leaderboard(client, asset):
if len(data) > 0:
assert "neuron_uid" in data[0]
assert "rewards" in data[0]


def test_get_asset_summary_returns_current_price(client):
summary = client.get_asset_summary("BTC", horizon="24h")
assert summary["asset"] == "BTC"
assert summary["horizon"] == "24h"
assert "current_price" in summary
assert isinstance(summary["current_price"], (int, float))


def test_get_asset_summary_rejects_unsupported_asset(client):
import pytest

with pytest.raises(ValueError):
client.get_asset_summary("DOGE", horizon="24h")


def test_get_asset_summary_rejects_unsupported_horizon(client):
import pytest

with pytest.raises(ValueError):
client.get_asset_summary("BTC", horizon="7d")
22 changes: 16 additions & 6 deletions tools/tide-chart/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,15 +1048,25 @@ def gtrade_resolve_pair():
except Exception:
trading_vars = None
pair_index = resolve_pair_index(asset, trading_vars, skip_fetch=True)
# Include fresh price for the frontend openTrade struct
# Include fresh price for the frontend openTrade struct.
# Use SynthClient's forecast percentiles helper instead of a hard-coded,
# non-existent client method to keep this logic aligned with the SDK.
current_price = None
try:
summary = client.get_asset_summary(asset)
if summary and "current_price" in summary:
current_price = summary["current_price"]
summary = client.get_asset_summary(asset, horizon="24h")
current_price = summary.get("current_price")
except Exception:
pass
return jsonify({"asset": asset, "pair_index": pair_index, "current_price": current_price})
# If Synth data is temporarily unavailable, still return a valid
# response shape so the frontend can fall back gracefully.
current_price = None

return jsonify(
{
"asset": asset,
"pair_index": pair_index,
"current_price": current_price,
}
)

@app.route("/api/gtrade/open-trades")
def gtrade_open_trades():
Expand Down
6 changes: 5 additions & 1 deletion tools/tide-chart/tests/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ def test_flask_gtrade_resolve_pair_invalid():


def test_flask_gtrade_resolve_pair_valid():
"""Verify resolve-pair returns for a valid asset (pair_index may be None without live API)."""
"""Verify resolve-pair returns for a valid asset and includes current_price when Synth data is available."""
client = _make_client()
app = create_app(client)
with app.test_client() as tc:
Expand All @@ -762,6 +762,10 @@ def test_flask_gtrade_resolve_pair_valid():
data = json.loads(resp.data)
assert data["asset"] == "SPY"
assert "pair_index" in data
# In mock mode, SynthClient is backed by local prediction percentiles,
# so current_price should be present and numeric.
assert "current_price" in data
assert isinstance(data["current_price"], (int, float))


def test_dashboard_html_contains_wallet_ui():
Expand Down