77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
"""Find Event B (12h <0.5% move) and Event A (>1.5% in <=60min) windows in April BTC."""
|
|
import os, glob
|
|
import pyarrow.parquet as pq
|
|
import pandas as pd
|
|
|
|
ROOT = "/mnt/d/PaperTrader/research/hl_data/minutes"
|
|
days = sorted(d for d in os.listdir(ROOT) if d.startswith("202604"))
|
|
|
|
frames = []
|
|
for day in days:
|
|
for f in sorted(glob.glob(os.path.join(ROOT, day, "*.parquet"))):
|
|
df = pq.read_table(f).to_pandas()
|
|
df = df[df["coin"] == "BTC"]
|
|
frames.append(df)
|
|
|
|
btc = pd.concat(frames, ignore_index=True).sort_values("minute").reset_index(drop=True)
|
|
btc = btc.drop_duplicates(subset=["minute"], keep="first")
|
|
print(f"BTC rows in April: {len(btc)}")
|
|
print(f"First minute: {btc['minute'].iloc[0]} Last: {btc['minute'].iloc[-1]}")
|
|
print(f"Mid range: ${btc['mid_price'].min():.2f} - ${btc['mid_price'].max():.2f}")
|
|
print(f"Continuous? span={btc['minute'].iloc[-1]-btc['minute'].iloc[0]+1} rows={len(btc)}")
|
|
|
|
# Convert minute -> UTC datetime
|
|
btc["ts"] = pd.to_datetime(btc["minute"] * 60, unit="s", utc=True)
|
|
|
|
# Event A: any rolling window where |move| >=1.5% within 60 min
|
|
print("\n=== EVENT A candidates (>=1.5% move in <=60 min) ===")
|
|
mids = btc["mid_price"].values
|
|
mins = btc["minute"].values
|
|
ts = btc["ts"].values
|
|
a_candidates = []
|
|
for i in range(len(mids) - 60):
|
|
base = mids[i]
|
|
window = mids[i:i+61] # 60-minute lookforward
|
|
high = window.max(); low = window.min()
|
|
up = (high - base) / base
|
|
dn = (low - base) / base
|
|
if up >= 0.015:
|
|
j = i + int(window.argmax())
|
|
a_candidates.append((i, j, base, mids[j], up, "UP"))
|
|
if dn <= -0.015:
|
|
j = i + int(window.argmin())
|
|
a_candidates.append((i, j, base, mids[j], dn, "DN"))
|
|
|
|
# Dedupe overlapping candidates (keep biggest move per ~2h cluster)
|
|
a_candidates.sort(key=lambda x: -abs(x[4]))
|
|
chosen_a = []
|
|
for c in a_candidates:
|
|
if all(abs(c[0] - ec[0]) > 120 for ec in chosen_a):
|
|
chosen_a.append(c)
|
|
if len(chosen_a) >= 8:
|
|
break
|
|
for i, j, b, p, m, d in chosen_a:
|
|
print(f" start_min={mins[i]} ({ts[i]}) -> end_min={mins[j]} ({ts[j]}) base=${b:,.2f} peak=${p:,.2f} move={m*100:+.2f}% [{d}] dur={(mins[j]-mins[i])}min")
|
|
|
|
# Event B: 12h window (720 min) where (end-start)/start < 0.5% AND max excursion < 0.5%
|
|
print("\n=== EVENT B candidates (12h with <0.5% move + tight) ===")
|
|
WIN = 720
|
|
b_candidates = []
|
|
for i in range(0, len(mids) - WIN, 30): # step every 30min
|
|
window = mids[i:i+WIN+1]
|
|
base = window[0]
|
|
end = window[-1]
|
|
total = abs(end - base) / base
|
|
excursion = (window.max() - window.min()) / base
|
|
if total < 0.005 and excursion < 0.01: # also keep excursion bounded
|
|
b_candidates.append((i, mins[i], mins[i+WIN], base, end, total, excursion))
|
|
|
|
# Sort by smallest excursion
|
|
b_candidates.sort(key=lambda x: x[6])
|
|
for i, m0, m1, b, e, tot, exc in b_candidates[:8]:
|
|
print(f" start_min={m0} ({ts[i]}) -> end_min={m1} ({ts[i+WIN]}) base=${b:,.2f} end=${e:,.2f} total={tot*100:+.3f}% excursion={exc*100:.3f}%")
|
|
|
|
# Save survey for reuse
|
|
btc.to_parquet("/mnt/d/Resonance_Engine/_tools_btc_april.parquet", index=False)
|
|
print(f"\nSaved {len(btc)} BTC rows to _tools_btc_april.parquet")
|