forked from chiki2bum2/SniperGold_ML
134 lines
4.2 KiB
Python
134 lines
4.2 KiB
Python
# -*- coding: utf-8 -*-
| |||
"""P3-S22.2 COMMON — shared infrastructure for the discrepancy impact
| |||
assessment (ADJ-1 / ADJ-2).
| |||
| |||
Disposable / research-only. NO production modification. Reuses only the
| |||
FROZEN committed research inputs (caches, npz) and the frozen F3 oracle
| |||
(canonical_oracle / F3SetupEngine) as the semantic reference. The independent
| |||
oracles for the discrepancies live in this namespace and do NOT call the
| |||
MQL5 runtime.
| |||
| |||
Usage: imported by s222_partA_*.py / s222_partB_*.py / s222_*.py
| |||
"""
| |||
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"))
| |||
for p in (SETUP, SMC, os.path.normpath(os.path.join(HERE, ".."))):
| |||
if p not in sys.path:
| |||
sys.path.insert(0, p)
| |||
| |||
M15 = 900 # seconds
| |||
M30 = 1800
| |||
H4 = 14400
| |||
| |||
# F2 zone-state enumeration (frozen AF_Defines / AF_Engine2_Agents.mqh)
| |||
UNMITIGATED = 0
| |||
PARTIALLY = 1
| |||
FULLY = 2
| |||
| |||
# Frozen F3 windows (P3-S.17R.2)
| |||
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)
| |||
| |||
| |||
# ------------------------------------------------------------------
| |||
# Independent UTC-clock M30 aggregation from M15 bars (ADJ-2 oracle)
| |||
# ------------------------------------------------------------------
| |||
def utc_clock_m30(t15, o15, h15, l15, c15):
| |||
"""Aggregate M15 bars into fixed UTC 30-minute buckets purely by
| |||
wall-clock timestamp. An M30 bar covers [open, open+1800) with open a
| |||
multiple of 1800 s from the Unix epoch. Every M15 bar belongs to the
| |||
bucket whose interval contains its OPEN time.
| |||
Returns (t30, o30, h30, l30, c30) chronological."""
| |||
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 // M30) * M30 # UTC bucket open (int64)
| |||
# group starts where the bucket changes
| |||
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()
| |||
# drop any trailing bucket with no closed bar (should not happen for a
| |||
# complete feed; a bucket is always closed once its M15 bar is present)
| |||
return t30, o30, h30, l30, c30
| |||
| |||
| |||
def as_of_m30_utc_gate(t15, m30_dir_per_bucket):
| |||
"""Map per-bucket context dir to a per-M15-bar gate series using the
| |||
frozen as-of rule (newest CLOSED M30 bar with close_time <= t+900)."""
| |||
n = len(t15)
| |||
| |||
def bucket_id(t):
| |||
return int(((t + M15 - M30) // M30)) # newest closed M30 id at t+900
| |||
| |||
ids = np.array([bucket_id(int(x)) for x in t15], dtype=np.int64)
| |||
base = int(ids[0]) if n else 0
| |||
rel = ids - base
| |||
gate = np.zeros(n, dtype=int)
| |||
for i, ridx in enumerate(rel):
| |||
if 0 <= ridx < len(m30_dir_per_bucket):
| |||
gate[i] = int(m30_dir_per_bucket[ridx])
| |||
elif ridx < 0:
| |||
gate[i] = 0
| |||
else:
| |||
gate[i] = int(m30_dir_per_bucket[-1])
| |||
return gate
|