SniperGold_ML/ml/p3/p3_s223_m30_repair/s223_common.py

160 lines
No EOL
5.5 KiB
Python

# -*- coding: utf-8 -*-
"""P3-S22.3 M30 REPAIR - COMMON shared infrastructure.
Disposable / research-only. This namespace is ADDITIVE: it does NOT modify the
committed research M30 construction, frozen gate caches, P3-S16/P3-S17/P3-S18/
P3-S20 artifacts, FEATURE_CONTRACT, labels, TP/SL/horizon, or production MQL5.
It imports ONLY frozen commits + frozen chain-parity machinery. The corrected
UTC-clock M30 construction and its independent oracle live in this namespace.
Usage: imported by s223_part*.py modules.
"""
import datetime as dt
import hashlib
import json
import os
import sys
import numpy as np
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "output")
REPO = os.path.normpath(os.path.join(HERE, "..", "..", ".."))
SETUP = os.path.normpath(os.path.join(HERE, "..", "setup_dataset"))
SMC = os.path.normpath(os.path.join(HERE, "..", "smc_semantic"))
P3 = os.path.normpath(os.path.join(HERE, ".."))
for p in (P3, SETUP, SMC):
if p not in sys.path:
sys.path.insert(0, p)
M15 = 900 # seconds
M30 = 1800
H4 = 14400
E2_MIN_BARS = 80 # frozen Context-agent minimum cache (AF_Defines.mqh)
# Frozen F3 windows (P3-S.17R.2 contracts)
W_SWEEP = 40
W_CHOCH = 40
W_SETUP = 40
W_M3 = 2
SCOPE_START = dt.datetime(2017, 1, 3, tzinfo=dt.timezone.utc)
SCOPE_END = dt.datetime(2026, 7, 21, 23, 45, tzinfo=dt.timezone.utc)
def _sha16(obj):
return hashlib.sha256(
json.dumps(obj, sort_keys=True, default=str).encode()).hexdigest()[:16]
def sha256_of(obj):
return hashlib.sha256(
json.dumps(obj, sort_keys=True, default=str).encode()).hexdigest()
def now_utc():
return dt.datetime.now(dt.timezone.utc).isoformat()
def load_all():
import spec_tests_vectorized_primitives as VPR
return VPR.load_all()
def load_gates():
import spec_tests_vectorized_primitives as VPR
return VPR.load_gates()
def scope_mask(t):
import spec_tests_vectorized_primitives as VPR
return VPR.scope_mask(t)
def committed_m30_gate():
g = load_gates()
return g[1] # index-pair resample research M30 gate (committed)
# --------------------------------------------------------------------------
# CORRECTED RESEARCH M30 CONSTRUCTION (UTC-clock buckets)
# --------------------------------------------------------------------------
def corrected_m30_from_m15(t15, o15, h15, l15, c15):
"""Build corrected research M30 bars from M15 by FIXED UTC 30-minute
buckets:
t30_open = floor(t15 / 1800) * 1800 (UTC epoch, seconds)
Aggregate open / high / low / close within [open, open+1800).
Deterministic chronological output. A bucket is emitted only when it
contains >= 1 M15 bar. No forward fill, no look-ahead. Returns arrays.
"""
n = len(t15)
if n == 0:
return (np.array([], dtype=np.int64),
np.array([], dtype=np.float64),
np.array([], dtype=np.float64),
np.array([], dtype=np.float64),
np.array([], dtype=np.float64))
opens = (t15.astype(np.int64) // M30) * M30
change = np.empty(n, dtype=bool)
change[0] = True
change[1:] = opens[1:] != opens[:-1]
start = np.flatnonzero(change)
end = np.append(start[1:], n)
t30 = opens[start]
o30 = o15[start]
c30 = c15[end - 1]
h30 = np.empty(len(start), dtype=np.float64)
l30 = np.empty(len(start), dtype=np.float64)
for k, (a, b) in enumerate(zip(start, end)):
seg = slice(a, b)
h30[k] = h15[seg].max()
l30[k] = l15[seg].min()
return t30, o30, h30, l30, c30
def as_of_m30_gate(t15, m30_dir_per_bucket, t30=None):
"""Map per-bucket Context-agent direction to a per-M15-bar gate series
using the FROZEN as-of rule: newest CLOSED M30 bar with close_time <= t+900.
close_time = bucket_open + 1800 ; decision close = t + 900.
Implemented with np.searchsorted over the ACTUAL closed closes (gap-aware):
for every gap, each M15 bar still as-ofs the most-recently closed M30 bar
that precedes its decision close. This is the exact rule used by the
validated P3-S22.2 oracle (which reproduced the committed dirs 0-mismatch).
Requires t15 and t30 (bucket opens) for the closes.
"""
t15 = np.asarray(t15, dtype=np.int64)
if t30 is None:
# derive bucket opens from t15 (must be contiguous gap-free input;
# for gapped feeds the caller MUST supply the real t30)
t30 = (t15 // M30) * M30
m30_close = np.asarray(t30, dtype=np.int64) + M30
dc = t15 + M15 # decision close
idx = np.searchsorted(m30_close, dc, side="right") - 1
n = len(t15)
gate = np.zeros(n, dtype=int)
valid = (idx >= 0) & (idx < len(m30_dir_per_bucket))
gate[valid] = np.asarray(m30_dir_per_bucket, dtype=int)[
np.clip(idx[valid], 0, len(m30_dir_per_bucket) - 1)]
return gate
def invariant_checkpoint(t15, t30):
"""Assert the explicit M30 invariant:
M30_open == floor(M15_open / 1800) * 1800 (UTC)
verified for every research M15 bar that begins a complete UTC M30 bucket.
"""
opens = (t15.astype(np.int64) // M30) * M30
change = np.empty(len(t15), dtype=bool)
change[0] = True
change[1:] = opens[1:] != opens[:-1]
start = np.flatnonzero(change)
mismatch = int((t30 != opens[start]).sum())
return {"n_buckets": int(len(t30)),
"n_start_m15_checked": int(len(start)),
"mismatch": mismatch,
"invariant_holds": bool(mismatch == 0),
"formula": "M30_open = floor(M15_open / 1800) * 1800 (UTC clock)"}