159 lines
7.1 KiB
Python
159 lines
7.1 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""GEN HUMAN PACKAGE — buat paket annotation blind (P3-S.1 §6-7).
|
||
|
|
|
||
|
|
HUMAN PACKAGE (human_package/):
|
||
|
|
cases.csv : case_id, symbol, decision_timestamp, decision_tf, note
|
||
|
|
human_annotation_template_f7.csv : template blank (60 case, format §7)
|
||
|
|
HUMAN_ANNOTATION_PROTOCOL.md : aturan blind + chart rule + prosedur A/B + adjudikasi
|
||
|
|
context_m15/<case_id>.csv : (opsional) 200 bar M15 terakhir s.d. decision_timestamp
|
||
|
|
utk review offline; snapshot BERAKHIR di decision_timestamp
|
||
|
|
|
||
|
|
MACHINE PACKAGE (machine_package/ — BLINDED, jangan dibuka annotator):
|
||
|
|
machine_annotations_f7_v2.csv : salinan annotation machine
|
||
|
|
machine_reasons_f7_v2.txt : alasan readable per case
|
||
|
|
README_BLINDED.md : peringatan blinding
|
||
|
|
|
||
|
|
Tidak ada keputusan machine di HUMAN PACKAGE. Sampling TIDAK dijalankan ulang.
|
||
|
|
Usage: python gen_human_package.py
|
||
|
|
"""
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import csv
|
||
|
|
import json
|
||
|
|
import shutil
|
||
|
|
import datetime as dt
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
sys.path.insert(0, HERE)
|
||
|
|
import smc_semantic_common as SC
|
||
|
|
|
||
|
|
OUT = os.path.join(HERE, "output")
|
||
|
|
HUMAN = os.path.join(HERE, "human_package")
|
||
|
|
MACH = os.path.join(HERE, "machine_package")
|
||
|
|
CTX = os.path.join(HUMAN, "context_m15")
|
||
|
|
|
||
|
|
CTX_BARS = 200 # bar M15 sebelum & termasuk decision_timestamp (snapshot TIDAK melewati)
|
||
|
|
|
||
|
|
|
||
|
|
def iso(ts):
|
||
|
|
return dt.datetime.fromtimestamp(int(ts), tz=dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
meta = json.load(open(os.path.join(OUT, "cases_meta.json"), encoding="utf-8"))
|
||
|
|
cases = meta["cases"]
|
||
|
|
|
||
|
|
for p in (HUMAN, MACH, CTX):
|
||
|
|
os.makedirs(p, exist_ok=True)
|
||
|
|
|
||
|
|
# ---------- HUMAN PACKAGE ----------
|
||
|
|
# cases.csv
|
||
|
|
with open(os.path.join(HUMAN, "cases.csv"), "w", newline="", encoding="utf-8") as f:
|
||
|
|
w = csv.writer(f)
|
||
|
|
w.writerow(["case_id", "symbol", "decision_timestamp", "decision_tf", "note"])
|
||
|
|
for c in cases:
|
||
|
|
w.writerow([c["case_id"], SC.SYMBOL, c["decision_timestamp"], SC.DECISION_TF,
|
||
|
|
"REVIEW HANYA SAMPAI decision_timestamp. Jangan lihat future."])
|
||
|
|
|
||
|
|
# template blank
|
||
|
|
fields = ["case_id", "liquidity_sweep", "reference", "direction", "timeframe",
|
||
|
|
"reference_price", "sweep_price", "close_back_rejection", "reason",
|
||
|
|
"confidence", "notes"]
|
||
|
|
with open(os.path.join(HUMAN, "human_annotation_template_f7.csv"), "w",
|
||
|
|
newline="", encoding="utf-8") as f:
|
||
|
|
w = csv.writer(f)
|
||
|
|
w.writerow(fields)
|
||
|
|
for c in cases:
|
||
|
|
w.writerow([c["case_id"], "", "", "", "", "", "", "", "", "", ""])
|
||
|
|
|
||
|
|
# context pack M15 (snapshot berakhir di decision_timestamp)
|
||
|
|
t, o, h, l, c, v, htf = SC.load_data()
|
||
|
|
for case in cases:
|
||
|
|
r = int(case["bar_idx"])
|
||
|
|
s = max(0, r - CTX_BARS + 1)
|
||
|
|
with open(os.path.join(CTX, case["case_id"] + ".csv"), "w",
|
||
|
|
newline="", encoding="utf-8") as f:
|
||
|
|
w = csv.writer(f)
|
||
|
|
w.writerow(["time", "open", "high", "low", "close", "tick_volume"])
|
||
|
|
for i in range(s, r + 1):
|
||
|
|
w.writerow([iso(t[i]), round(o[i], 2), round(h[i], 2),
|
||
|
|
round(l[i], 2), round(c[i], 2), int(v[i])])
|
||
|
|
|
||
|
|
# ---------- MACHINE PACKAGE (BLINDED) ----------
|
||
|
|
shutil.copy(os.path.join(OUT, "machine_annotations_f7_v2.csv"),
|
||
|
|
os.path.join(MACH, "machine_annotations_f7_v2.csv"))
|
||
|
|
rows = list(csv.DictReader(open(os.path.join(OUT, "machine_annotations_f7_v2.csv"),
|
||
|
|
encoding="utf-8-sig")))
|
||
|
|
with open(os.path.join(MACH, "machine_reasons_f7_v2.txt"), "w", encoding="utf-8") as f:
|
||
|
|
for r in rows:
|
||
|
|
f.write(f"{r['case_id']} | {r['decision_timestamp']} | "
|
||
|
|
f"{r['machine_decision']} | {r['primary_primitive']} | "
|
||
|
|
f"age={r['f7_state_age']} | {r['machine_reason']}\n")
|
||
|
|
with open(os.path.join(MACH, "README_BLINDED.md"), "w", encoding="utf-8") as f:
|
||
|
|
f.write("# MACHINE PACKAGE — BLINDED\n\n"
|
||
|
|
"Folder ini berisi hasil machine annotation (f7_v2, source_commit b519a34).\n"
|
||
|
|
"**DILARANG dibuka oleh annotator manusia sebelum human annotation selesai.**\n"
|
||
|
|
"Hanya tim riset/adjudicator pasca-konsensus yang boleh membaca folder ini.\n")
|
||
|
|
|
||
|
|
# protocol doc
|
||
|
|
protocol = f"""# HUMAN ANNOTATION PROTOCOL — LIQUIDITY SWEEP (P3-S.1)
|
||
|
|
|
||
|
|
## Prinsip Blind
|
||
|
|
- Annotator HANYA menerima folder `human_package/`.
|
||
|
|
- Folder `machine_package/` berisi keputusan machine — **DILARANG dibuka sebelum annotasi selesai**.
|
||
|
|
- Human A (primary) dan Human B (independent reviewer) mengerjakan secara independen.
|
||
|
|
|
||
|
|
## Chart Data Rule (WAJIB)
|
||
|
|
- Buka chart XAUUSD (M15 utk keputusan; H4/M30/M3 sbg konteks) di terminal MT5.
|
||
|
|
- Scroll HINGGA `decision_timestamp` pada kolom `cases.csv`.
|
||
|
|
- **TIDAK boleh melihat candle setelah decision_timestamp** (future).
|
||
|
|
- Snapshot/context `context_m15/<case_id>.csv` berakhir TEPAT di decision_timestamp.
|
||
|
|
|
||
|
|
## Kolom Template (human_annotation_template_f7.csv)
|
||
|
|
| Kolom | Nilai |
|
||
|
|
|---|---|
|
||
|
|
| liquidity_sweep | YES / NO / AMBIGUOUS |
|
||
|
|
| reference | EQH / EQL / Swing High / Swing Low / Other / None |
|
||
|
|
| direction | Bullish / Bearish / None |
|
||
|
|
| timeframe | H4 / M30 / M15 / M3 / None |
|
||
|
|
| reference_price | (numeric opsional) |
|
||
|
|
| sweep_price | (numeric opsional) |
|
||
|
|
| close_back_rejection | YES / NO / AMBIGUOUS / N/A |
|
||
|
|
| reason | teks bebas — fakta yang teramati, bukan opini |
|
||
|
|
| confidence | HIGH / MEDIUM / LOW |
|
||
|
|
|
||
|
|
## Definisi kerja utk annotator (bukan definisi final — utk konsistensi labeling)
|
||
|
|
- Liquidity Sweep = harga menembus (wick) level likuiditas (equal high/low / swing) lalu
|
||
|
|
menunjukkan rejection (close kembali) — nilai apakah ini TERJADI pada window sekitar
|
||
|
|
decision_timestamp.
|
||
|
|
- Reference = jenis level likuiditas yang disapu.
|
||
|
|
- Direction = arah intent setelah sweep (Bullish = sell-side swept; Bearish = buy-side swept).
|
||
|
|
|
||
|
|
## Prosedur
|
||
|
|
1. Salin `human_annotation_template_f7.csv` -> `human_A_f7.csv` (Human A).
|
||
|
|
2. Human B mengerjakan salinan -> `human_B_f7.csv` (tanpa melihat A).
|
||
|
|
3. Simpan hasil di `ml/p3/smc_semantic/output/`.
|
||
|
|
4. Setelah A & B selesai: hitung inter-rater (human_interrater_f7_report.json).
|
||
|
|
5. Kasus berbeda -> adjudikasi -> `human_adjudicated_f7.csv` (adjudicator melihat chart,
|
||
|
|
protokol, metadata — TIDAK melihat machine result).
|
||
|
|
6. Baru setelah consensus: machine vs human (human_machine_f7_comparison.json).
|
||
|
|
|
||
|
|
## Larangan
|
||
|
|
- Jangan mengubah f7/threshold/InpSeqWindow berdasarkan hasil annotasi (validation, bukan optimasi).
|
||
|
|
- Jangan menampilkan machine result ke annotator sebelum selesai.
|
||
|
|
|
||
|
|
## Case Set
|
||
|
|
- 60 kasus (SGML_SWEEP_001..060), XAUUSD M15, 2018-2026, seed 42 (case set hash
|
||
|
|
{__import__('hashlib').sha256(open(os.path.join(OUT, 'cases_meta.json'),'rb').read()).hexdigest()[:16]}).
|
||
|
|
"""
|
||
|
|
with open(os.path.join(HUMAN, "HUMAN_ANNOTATION_PROTOCOL.md"), "w", encoding="utf-8") as f:
|
||
|
|
f.write(protocol)
|
||
|
|
|
||
|
|
print("[saved] human_package/ (cases.csv, template, protocol, context_m15/)")
|
||
|
|
print("[saved] machine_package/ (BLINDED: annotations + reasons)")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|