Files
ahad-quant/export_unified.py
2026-06-25 14:00:20 +03:00

122 lines
4.5 KiB
Python

"""
AHAD QUANT — Export Modèle Unifié
Combine rl_agent.zip + model_ensemble.pkl + rl_scaler.pkl
en un seul fichier ahad_quant_unified.zip.
Usage :
python export_unified.py
python export_unified.py --output mon_modele.zip
Structure du zip produit :
ahad_quant_unified.zip
├── ppo/ ← contenu SB3 intact (policy.pth, optimizer, ...)
├── ensemble.pkl ← ensemble supervisé
├── rl_scaler.pkl ← scaler z-score features
└── metadata.json ← version, date, config
"""
import os
import sys
import json
import zipfile
import pickle
import argparse
import io
from datetime import datetime
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
import config
def export_unified(
ppo_path: str = None,
ensemble_path: str = None,
scaler_path: str = None,
output_path: str = "ahad_quant_unified.zip",
):
ppo_path = ppo_path or getattr(config, "RL_MODEL_PATH", "rl_agent")
ensemble_path = ensemble_path or getattr(config, "ENSEMBLE_MODEL_PATH", "model_ensemble.pkl")
scaler_path = scaler_path or getattr(config, "RL_SCALER_PATH", "rl_scaler.pkl")
# Ajouter .zip si absent
ppo_zip = ppo_path if ppo_path.endswith(".zip") else ppo_path + ".zip"
# ── Vérifications ────────────────────────────────────────────────────────
missing = []
for label, path in [("PPO", ppo_zip), ("Ensemble", ensemble_path), ("Scaler", scaler_path)]:
if not os.path.exists(path):
missing.append(f" ❌ {label} : {path}")
if missing:
print("\n".join(missing))
print("\nExécuter dans l'ordre :")
print(" 1. python train.py")
print(" 2. python rl_train.py")
sys.exit(1)
print("=" * 55)
print(" AHAD QUANT — Export Modèle Unifié")
print("=" * 55)
print(f" PPO : {ppo_zip}")
print(f" Ensemble : {ensemble_path}")
print(f" Scaler : {scaler_path}")
print(f" Sortie : {output_path}")
print()
# ── Metadata ─────────────────────────────────────────────────────────────
metadata = {
"version": "ahad_quant-v6-unified",
"exported_at": datetime.now().isoformat(),
"ppo_source": ppo_zip,
"ensemble_source": ensemble_path,
"scaler_source": scaler_path,
"coins": getattr(config, "COINS", []),
"leverage": getattr(config, "LEVERAGE", 30),
"obs_dim": 69,
}
# ── Construction du zip unifié ────────────────────────────────────────────
with zipfile.ZipFile(output_path, "w", compression=zipfile.ZIP_DEFLATED) as out_zip:
# 1. Contenu PPO → sous-dossier ppo/
with zipfile.ZipFile(ppo_zip, "r") as ppo_zip_f:
for item in ppo_zip_f.namelist():
data = ppo_zip_f.read(item)
out_zip.writestr(f"ppo/{item}", data)
print(f" ✅ PPO intégré (ppo/)")
# 2. Ensemble pkl
with open(ensemble_path, "rb") as f:
out_zip.writestr("ensemble.pkl", f.read())
print(f" ✅ Ensemble intégré (ensemble.pkl)")
# 3. Scaler pkl
with open(scaler_path, "rb") as f:
out_zip.writestr("rl_scaler.pkl", f.read())
print(f" ✅ Scaler intégré (rl_scaler.pkl)")
# 4. Metadata
out_zip.writestr("metadata.json", json.dumps(metadata, indent=2))
print(f" ✅ Metadata intégrée (metadata.json)")
size_mb = os.path.getsize(output_path) / (1024 * 1024)
print()
print(f" 💾 {output_path} ({size_mb:.1f} MB)")
print("=" * 55)
print(" Chargement :")
print(f" from rl_agent import RLAgent")
print(f" agent = RLAgent(unified_path='{output_path}')")
print("=" * 55)
return output_path
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--ppo", type=str, default=None)
parser.add_argument("--ensemble", type=str, default=None)
parser.add_argument("--scaler", type=str, default=None)
parser.add_argument("--output", type=str, default="ahad_quant_unified.zip")
args = parser.parse_args()
export_unified(args.ppo, args.ensemble, args.scaler, args.output)