build(agent): new-agents-2#7e3bbc iteration
This commit is contained in:
parent
91e8c6459a
commit
02f4d00ab0
|
|
@ -0,0 +1,8 @@
|
||||||
|
"""MercuryMesh Pipeline: lightweight toy cross-venue analytics pipeline.
|
||||||
|
|
||||||
|
Exposes a minimal aggregator interface used by MVP demonstrations.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .aggregator import aggregate_signals # noqa: F401
|
||||||
|
|
||||||
|
__all__ = ["aggregate_signals"]
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import List, Dict
|
||||||
|
from datetime import datetime
|
||||||
|
import uuid
|
||||||
|
from mercurymesh.contracts import MarketSignal, AggregatedSignal
|
||||||
|
from mercurymesh.provenance import merkle_proof_for_signal
|
||||||
|
|
||||||
|
|
||||||
|
def _average_features(signals: List[MarketSignal]) -> Dict[str, float]:
|
||||||
|
# Collect keys across signals
|
||||||
|
keys: set[str] = set()
|
||||||
|
for s in signals:
|
||||||
|
keys.update(s.features.keys())
|
||||||
|
|
||||||
|
# Initialize sums
|
||||||
|
sums: Dict[str, float] = {k: 0.0 for k in keys}
|
||||||
|
counts: Dict[str, int] = {k: 0 for k in keys}
|
||||||
|
|
||||||
|
for s in signals:
|
||||||
|
for k in keys:
|
||||||
|
if k in s.features:
|
||||||
|
sums[k] += s.features[k]
|
||||||
|
counts[k] += 1
|
||||||
|
|
||||||
|
# Compute averages for present keys
|
||||||
|
avg: Dict[str, float] = {}
|
||||||
|
for k in keys:
|
||||||
|
if counts[k] > 0:
|
||||||
|
avg[k] = sums[k] / counts[k]
|
||||||
|
return avg
|
||||||
|
|
||||||
|
|
||||||
|
def aggregate_signals(signals: List[MarketSignal]) -> AggregatedSignal:
|
||||||
|
"""Create an AggregatedSignal from a list of MarketSignal objects.
|
||||||
|
|
||||||
|
This is a lightweight MVP aggregation: average feature values across venues
|
||||||
|
and emit a Merkle-style provenance proof based on the input signals.
|
||||||
|
"""
|
||||||
|
if not signals:
|
||||||
|
raise ValueError("signals list must not be empty")
|
||||||
|
|
||||||
|
venues = [s.venue_id for s in signals]
|
||||||
|
feature_vector = _average_features(signals)
|
||||||
|
privacy_budget_used = 0.0
|
||||||
|
nonce = uuid.uuid4().hex
|
||||||
|
|
||||||
|
# Create a simple merkle_proof from the input signals for provenance traceability
|
||||||
|
# We reuse merkle_proof_for_signal on the first signal as a compact probe
|
||||||
|
merkle_proof = merkle_proof_for_signal(signals[0]) if signals else None
|
||||||
|
|
||||||
|
aggregated = AggregatedSignal(
|
||||||
|
venues=venues,
|
||||||
|
feature_vector=feature_vector,
|
||||||
|
privacy_budget_used=privacy_budget_used,
|
||||||
|
nonce=nonce,
|
||||||
|
merkle_proof=merkle_proof,
|
||||||
|
)
|
||||||
|
return aggregated
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
from datetime import datetime
|
||||||
|
from mercurymesh.contracts import MarketSignal
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_string(s: str) -> str:
|
||||||
|
return hashlib.sha256(s.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def merkle_proof_for_signal(signal: MarketSignal) -> str:
|
||||||
|
"""Generate a lightweight, deterministic Merkle-like proof for a signal.
|
||||||
|
|
||||||
|
This is a simple stand-in for a real Merkle proof suitable for MVP testing.
|
||||||
|
It hashes the concatenation of venue_id, symbol, timestamp, and a sorted
|
||||||
|
representation of features.
|
||||||
|
"""
|
||||||
|
features_items = sorted(signal.features.items())
|
||||||
|
features_str = ",".join([f"{k}={v}" for k, v in features_items])
|
||||||
|
base = f"{signal.venue_id}|{signal.symbol}|{signal.timestamp.isoformat()}|{features_str}"
|
||||||
|
return _hash_string(base)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_provenance(aggregated_proof: str, signals: List[MarketSignal]) -> bool:
|
||||||
|
"""Very lightweight verification: recompute proof from the first signal and compare."""
|
||||||
|
if not signals:
|
||||||
|
return False
|
||||||
|
expected = merkle_proof_for_signal(signals[0])
|
||||||
|
return expected == aggregated_proof
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from mercurymesh.contracts import MarketSignal, AggregatedSignal
|
||||||
|
from mercurymesh.pipeline import aggregate_signals
|
||||||
|
from mercurymesh.provenance import merkle_proof_for_signal, verify_provenance
|
||||||
|
|
||||||
|
|
||||||
|
def _make_signal(venue_id: str, symbol: str, t: datetime, f: dict) -> MarketSignal:
|
||||||
|
return MarketSignal(venue_id=venue_id, symbol=symbol, timestamp=t, features=f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_aggregate_signals_basic():
|
||||||
|
t = datetime.utcnow()
|
||||||
|
s1 = _make_signal("venue-a", "ABC", t, {"liquidity_proxy": 1.0, "order_flow_intensity": 0.5})
|
||||||
|
s2 = _make_signal("venue-b", "XYZ", t, {"liquidity_proxy": 0.5, "order_flow_intensity": 0.8})
|
||||||
|
|
||||||
|
agg = aggregate_signals([s1, s2])
|
||||||
|
|
||||||
|
# Basic sanity checks
|
||||||
|
assert isinstance(agg, AggregatedSignal)
|
||||||
|
assert len(agg.venues) == 2
|
||||||
|
assert "liquidity_proxy" in agg.feature_vector
|
||||||
|
assert agg.nonce is not None
|
||||||
|
# merkle_proof should be present (deterministic from first signal)
|
||||||
|
assert agg.merkle_proof is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_provenance_roundtrip():
|
||||||
|
s = MarketSignal(
|
||||||
|
venue_id="venue-a",
|
||||||
|
symbol="ABC",
|
||||||
|
timestamp=datetime.utcnow(),
|
||||||
|
features={"liquidity_proxy": 1.0, "order_flow_intensity": 0.5},
|
||||||
|
)
|
||||||
|
proof = merkle_proof_for_signal(s)
|
||||||
|
# Use verify_provenance to check it matches the first-signal-derived proof
|
||||||
|
assert verify_provenance(proof, [s])
|
||||||
Loading…
Reference in New Issue