forked from chiki2bum2/SniperGold_ML
134 lines
No EOL
4.2 KiB
Python
134 lines
No EOL
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 |