Download tests/test_fly_session.py from bryceweiner/fly-trader: direct link, hf CLI and curl.
- Browser
- Download file 5.07 kB
-
https://huggingface.co/bryceweiner/fly-trader/resolve/main/tests/test_fly_session.py
- Command line
-
hf download hf://bryceweiner/fly-trader/tests/test_fly_session.py
-
curl -L -o test_fly_session.py https://huggingface.co/bryceweiner/fly-trader/resolve/main/tests/test_fly_session.py
5.07 kB
| """The live fly learns from exactly the label it was trained on: its resolver equals ``decisions.build``'s ``fwd_pess`` | |
| row for row (next-open entry within two minutes else the close, last close at or before t + hold, exit cost at both | |
| ends), drops an exit after a 50× jump or without the row's own bar; tags can be filtered.""" | |
| from datetime import datetime, timedelta, timezone | |
| import numpy as np | |
| import pandas as pd | |
| import pyarrow as pa | |
| import pytest | |
| import torch | |
| from fly_trader.agent.fly_session import resolve_label | |
| from fly_trader.brain import plastic | |
| from fly_trader.market.features import FEATURES | |
| from fly_trader.train import decisions, mature | |
| def _part(tmp_path): | |
| rng = np.random.default_rng(0) | |
| base = datetime(2026, 9, 1, tzinfo=timezone.utc) | |
| rows = [] | |
| for mint in ("Apump", "Bpump"): | |
| offs = sorted(rng.choice(400, 220, replace=False).tolist()) # traded minutes with irregular gaps (some > 2 minutes) | |
| price = 1.0 | |
| for o in offs: | |
| price *= float(np.exp(rng.normal(0, 0.02))) | |
| rows.append({**{f: 0.0 for f in FEATURES}, "mint": mint, "ts": base + timedelta(minutes=int(o)), "open": price * float(np.exp(rng.normal(0, 0.005))), | |
| "close": price, "resq": 50.0, "age_h": 30.0, "traders_15m": 10.0, "traders_1h": 40.0, "n_trades_1m": 3.0, "logvol_15m": 3.0, | |
| "exit_cost_0p1": float(rng.uniform(0.005, 0.02))}) | |
| df = pd.DataFrame(rows) | |
| root = tmp_path / "feat" | |
| mature.write_part(pa.Table.from_pandas(df, preserve_index=False), root / "2026-09-01" / "part.parquet", mature.FEATURE_VERSION, {"fly_agg": mature.AGG_VERSION}) | |
| return df, root | |
| def test_resolver_equals_the_training_label(tmp_path): | |
| df, root = _part(tmp_path) | |
| ds = decisions.build(days=None, horizon_min=10, feature_dir=root) | |
| assert len(ds.y) > 100 | |
| from fly_trader.market.exit_cost import cost_at_size # the engine reprices before it hands a bar to the fly | |
| bars = {m: [(t.timestamp(), o, c, cost_at_size(e, rq)) for t, o, c, e, rq in | |
| zip(g["ts"], g["open"], g["close"], g["exit_cost_0p1"], g["resq"])] | |
| for m, g in df.sort_values("ts").groupby("mint")} | |
| got = np.array([resolve_label(bars[m], t, ds.horizon_s) for m, t in zip(ds.mint, ds.ts)], dtype=np.float64) | |
| assert np.allclose(got, ds.fwd_pess, rtol=1e-5, atol=1e-6) | |
| gaps = np.array([next((b[0] for b in bars[m] if b[0] > t), np.inf) - t for m, t in zip(ds.mint, ds.ts)]) | |
| assert (gaps > 120).any() and (gaps <= 120).any() # both entry rules were exercised | |
| def test_resolver_refuses_jumps_and_missing_rows(): | |
| bars = [(0.0, 1.0, 1.0, 0.01), (60.0, 1.0, 1.0, 0.01), (120.0, 1.0, 60.0, 0.01)] | |
| assert resolve_label(bars, 0.0, 120.0) is None # a 60× jump into the exit minute | |
| assert resolve_label(bars, 30.0, 120.0) is None # the row's own minute is not in the bars | |
| ok = [(0.0, 1.0, 1.0, 0.01), (300.0, 2.0, 1.1, 0.02)] | |
| assert resolve_label(ok, 0.0, 600.0) == pytest.approx(1.1 / 1.0 * 0.99 * 0.98 - 1) # next trade 5 min later: entry at the close | |
| def test_tags_subset_keeps_the_chosen_rows(): | |
| k = torch.rand(3, 8); t = plastic.Tags(keys=["a", "b", "c"], ts=np.array([0.0, 1.0, 2.0]), y_dn=torch.tensor([1.0, 2.0, 3.0]), u0=torch.rand(3, 4), k=k, | |
| w=torch.tensor([[1.0, 10.0, 1.0]])) | |
| s = t.subset(np.array([True, False, True])) | |
| assert s.keys == ["a", "c"] and list(s.ts) == [0.0, 2.0] and s.y_dn.tolist() == [1.0, 3.0] and torch.equal(s.k, k[[0, 2]]) and s.w.tolist() == [[1.0, 1.0]] | |
| def test_outcomes_join_both_books_by_the_rows_decision(db_conn): | |
| """A scored row (minute start t, mint, strategy) finds its book's and the wallet's positions through the entry | |
| decision stamped at the minute's end, with each position's status and, once closed, realized / cost.""" | |
| from types import SimpleNamespace | |
| from fly_trader.agent.fly_session import FlyBook | |
| t = datetime(2026, 9, 3, 12, 0, tzinfo=timezone.utc).timestamp(); mint = "OUTCOME_TEST" | |
| d = db_conn.execute("INSERT INTO decisions (ts, mint, kind, detail) VALUES (%s, %s, 'fly_enter', %s) RETURNING id", | |
| (datetime.fromtimestamp(t + 60, timezone.utc), mint, '{"strategy": "capitulation", "book": "paper_fly"}')).fetchone()["id"] | |
| for book, status, realized in (("paper_fly", "closed", 0.02), ("live", "open", None)): | |
| db_conn.execute("INSERT INTO positions (book, mint, entry_decision_id, qty, cost_sol, realized_sol, status) VALUES (%s, %s, %s, 1, 0.1, %s, %s)", | |
| (book, mint, d, realized, status)) | |
| me = SimpleNamespace(BOOK="paper_fly", KIND="fly", market=SimpleNamespace(live_book="live")) | |
| book, live = FlyBook._outcomes(me, db_conn, [(t, mint, "capitulation"), (t + 60, mint, "capitulation")]) | |
| assert book == {(t, mint, "capitulation"): ("closed", pytest.approx(0.2))} and live == {(t, mint, "capitulation"): ("open", None)} | |