#  python3 parametrisable_optimiser.py   --target-trades 288,258,231,148,203,342,321,188,385,363   --target-profit 1507.0,944.5,232.7,225.3,166.6,22311.1,1020.8,827.7,3756.5,3172.4   --date 2025-09-17,2025-09-18,2025-09-19,2025-09-20,2025-09-21,2025-09-22,2025-09-23,2025-09-24,2025-09-25,2025-09-26   --gbm-drift 0.0046,0.0137,-0.0261,0.0043,-0.0054,-0.0588,-0.0063,-0.0082,-0.0545,0.0275   --gbm-sigma 0.0295,0.0175,0.0172,0.0096,0.0110,0.0345,0.0204,0.0194,0.0380,0.0296   --runs 1 --grid 8  --output results_optimised.json --range-pct 0.8 --drift-factor 1.0,1.0,1.0,1.0,1.0,10.0,1.0,1.0,1.0,1.0 --sigma-factor 1.0,1.0,1.0,1.0,1.0,0.5,1.0,1.0,1.0,1.0
#!/usr/bin/env python3
import argparse
import numpy as np
import json
from datetime import datetime
import subprocess
import os
import re
from multiprocessing import Pool, Manager, cpu_count
import itertools
from functools import partial
def sim(p00, p11, poi, jmp, runs=100, gbm_drift=0.00, gbm_sigma=0.0377, drift_factor=1.0, sigma_factor=1.0):
    tr_total, pr_total = 0, 0
    for _ in range(runs):
        env = os.environ.copy()
        env['POISSON_INTENSITY'] = str(poi)
        env['JUMP_SIZE'] = str(jmp)
        env['P_00'] = str(p00)
        env['P_11'] = str(p11)
        env['GBM_DRIFT'] = str(gbm_drift)
        env['GBM_SIGMA'] = str(gbm_sigma)
        env['DRIFT_FACTOR'] = str(drift_factor)
        env['SIGMA_FACTOR'] = str(sigma_factor)
        try:
            result = subprocess.run(['bash', 'monte_carlo.sh'], capture_output=True, text=True, env=env, timeout=30)
            output = result.stdout
            tr_match = re.search(r'number_of_trades:\s*(\d+)', output)
            pr_match = re.search(r'profit:\s*([\d.]+)', output)
            if tr_match and pr_match:
                tr_total += int(tr_match.group(1))
                pr_total += float(pr_match.group(1))
        except:
            pass
    return tr_total / runs if runs > 0 else 0, pr_total / runs if runs > 0 else 0
def loss_fn(params, t_tr, t_pr, runs, gbm_drift, gbm_sigma, drift_factor, sigma_factor):
    p00, p11, poi, jmp = params
    tr, pr = sim(p00, p11, poi, jmp, runs, gbm_drift, gbm_sigma, drift_factor, sigma_factor)
    tr_error = abs(tr - t_tr) / max(t_tr, 1) * 100 if t_tr > 0 else abs(tr - t_tr)
    pr_error = abs(pr - t_pr) / max(abs(t_pr), 1) * 100 if t_pr != 0 else abs(pr - t_pr)
    return tr_error + pr_error
def eval_chunk(chunk_id, param_chunk, t_tr, t_pr, runs, gbm_drift, gbm_sigma, drift_factor, sigma_factor, progress_dict):
    best_loss = float('inf')
    best_params = None
    for i, params in enumerate(param_chunk):
        loss = loss_fn(params, t_tr, t_pr, runs, gbm_drift, gbm_sigma, drift_factor, sigma_factor)
        if loss < best_loss:
            best_loss = loss
            best_params = params
        progress_dict[chunk_id] = i + 1
    return best_params, best_loss
def print_prog(progress_dict, chunk_sizes, total_combinations):
    completed = sum(progress_dict.get(i, 0) for i in range(len(chunk_sizes)))
    pct = 100 * completed / total_combinations
    bar_len = 50
    filled = int(bar_len * pct / 100)
    bar = '█' * filled + '░' * (bar_len - filled)
    print(f"\r[{bar}] {pct:.1f}% ({completed}/{total_combinations})", end='', flush=True)
def opt_par(p00_r, p11_r, poi_r, jmp_r, t_tr, t_pr, runs, grid, gbm_drift, gbm_sigma, drift_factor, sigma_factor, n_cores=8):
    p00_vals = np.linspace(p00_r[0], p00_r[1], grid)
    p11_vals = np.linspace(p11_r[0], p11_r[1], grid)
    poi_vals = np.linspace(poi_r[0], poi_r[1], grid)
    jmp_vals = np.linspace(jmp_r[0], jmp_r[1], grid)
    all_combinations = list(itertools.product(p00_vals, p11_vals, poi_vals, jmp_vals))
    total_combinations = len(all_combinations)
    print(f"Összesen {total_combinations} kombináció, {n_cores} mag használatával...")
    chunk_size = max(1, total_combinations // n_cores)
    param_chunks = [all_combinations[i:i + chunk_size] for i in range(0, total_combinations, chunk_size)]
    with Manager() as manager:
        progress_dict = manager.dict()
        chunk_sizes = [len(chunk) for chunk in param_chunks]
        with Pool(processes=n_cores) as pool:
            results = []
            for i, chunk in enumerate(param_chunks):
                result = pool.apply_async(eval_chunk, args=(i, chunk, t_tr, t_pr, runs, gbm_drift, gbm_sigma, drift_factor, sigma_factor, progress_dict))
                results.append(result)
            import time
            while not all(r.ready() for r in results):
                print_prog(progress_dict, chunk_sizes, total_combinations)
                time.sleep(0.5)
            print_prog(progress_dict, chunk_sizes, total_combinations)
            print()
            chunk_results = [r.get() for r in results]
    best_loss = float('inf')
    best_params = None
    for params, loss in chunk_results:
        if params is not None and loss < best_loss:
            best_loss = loss
            best_params = params
    return best_params, best_loss
def create_latex(results):
    tex = "\\begin{table*}[h]\n\\centering\n\\caption{Monte Carlo Trading Strategy Parameter Optimization Results}\n"
    tex += "\\begin{tabular}{|l|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}\n\\hline\n"
    tex += "Date & P\\_00 & P\\_11 & Poisson & Jump & $\\mu$ & $\\sigma$ & DF & SF & Target Tr & Final Tr & Target Pr & Final Pr & Loss \\\\\n\\hline\n"
    for r in results:
        tex += f"{r['date']} & {r['p00']:.4f} & {r['p11']:.4f} & {r['poi']:.6f} & {r['jmp']:.6f} & {r['mu']:.4f} & {r['sigma']:.4f} & {r['df']:.2f} & {r['sf']:.2f} & {r['t_tr']} & {r['final_tr']:.1f} & {r['t_pr']} & {r['final_pr']:.1f} & {r['loss']:.1f}\\% \\\\\n"
    tex += "\\hline\n\\end{tabular}\n\\end{table*}"
    return tex
def main():
    init_vals = {'2025-09-17': (0.9500, 0.7222, 0.002300, 0.003567)}
    parser = argparse.ArgumentParser()
    parser.add_argument('--target-trades', type=str, required=True)
    parser.add_argument('--target-profit', type=str, required=True)
    parser.add_argument('--gbm-drift', type=str, default='0.00')
    parser.add_argument('--gbm-sigma', type=str, default='0.0377')
    parser.add_argument('--drift-factor', type=str, default='1.0')
    parser.add_argument('--sigma-factor', type=str, default='1.0')
    parser.add_argument('--output', type=str, required=True)
    parser.add_argument('--runs', type=int, default=200)
    parser.add_argument('--grid', type=int, default=20)
    parser.add_argument('--date', type=str, default=datetime.now().strftime('%Y-%m-%d'))
    parser.add_argument('--range-pct', type=float, default=0.15)
    parser.add_argument('--cores', type=int, default=8)
    parser.add_argument('--n-runs', type=int, default=5)
    args = parser.parse_args()
    available_cores = cpu_count()
    if args.cores > available_cores:
        print(f"Figyelem: {args.cores} mag kérve, de csak {available_cores} érhető el. {available_cores} mag használata.")
        args.cores = available_cores
    target_trades = [int(x.strip()) for x in args.target_trades.split(',')]
    target_profits = [float(x.strip()) for x in args.target_profit.split(',')]
    gbm_drifts = [float(x.strip()) for x in args.gbm_drift.split(',')]
    gbm_sigmas = [float(x.strip()) for x in args.gbm_sigma.split(',')]
    drift_factors = [float(x.strip()) for x in args.drift_factor.split(',')]
    sigma_factors = [float(x.strip()) for x in args.sigma_factor.split(',')]
    dates = [x.strip() for x in args.date.split(',')] if ',' in args.date else [args.date] * len(target_trades)
    if len(target_trades) != len(target_profits): return
    if len(dates) == 1 and len(target_trades) > 1: dates = dates * len(target_trades)
    elif len(dates) != len(target_trades): return
    if len(gbm_drifts) == 1 and len(target_trades) > 1: gbm_drifts = gbm_drifts * len(target_trades)
    elif len(gbm_drifts) != len(target_trades): return
    if len(gbm_sigmas) == 1 and len(target_trades) > 1: gbm_sigmas = gbm_sigmas * len(target_trades)
    elif len(gbm_sigmas) != len(target_trades): return
    if len(drift_factors) == 1 and len(target_trades) > 1: drift_factors = drift_factors * len(target_trades)
    elif len(drift_factors) != len(target_trades): return
    if len(sigma_factors) == 1 and len(target_trades) > 1: sigma_factors = sigma_factors * len(target_trades)
    elif len(sigma_factors) != len(target_trades): return
    all_results = []
    for i, (date, t_tr, t_pr, gbm_d, gbm_s, df, sf) in enumerate(zip(dates, target_trades, target_profits, gbm_drifts, gbm_sigmas, drift_factors, sigma_factors)):
        print(f"\n=== OPTIMIZATION {i+1}/{len(target_trades)} ===")
        print(f"Date: {date}, Target: {t_tr} trades, {t_pr} profit")
        print(f"GBM_d: {gbm_d}, GBM_s: {gbm_s}, DF: {df}, SF: {sf}")
        print(f"Használt magok: {args.cores}")
        if date in init_vals:
            p00_c, p11_c, poi_c, jmp_c = init_vals[date]
            r_pct = args.range_pct
            p00_range = (max(0.20, p00_c * (1 - r_pct)), min(0.99, p00_c * (1 + r_pct)))
            p11_range = (max(0.10, p11_c * (1 - r_pct)), min(0.90, p11_c * (1 + r_pct)))
            poi_range = (max(0.0001, poi_c * (1 - r_pct)), min(0.01, poi_c * (1 + r_pct)))
            jmp_range = (max(0.0001, jmp_c * (1 - r_pct)), min(0.001, jmp_c * (1 + r_pct)))
            print(f"Refining around: P00={p00_c:.4f}, P11={p11_c:.4f}, Poi={poi_c:.6f}, Jmp={jmp_c:.6f}")
        else:
            p00_range = (0.20, 0.99)
            p11_range = (0.10, 0.90)
            poi_range = (0.0001, 0.01)
            jmp_range = (0.0001, 0.09)
            print("Using full parameter ranges")
        best_params, best_loss = opt_par(p00_range, p11_range, poi_range, jmp_range, t_tr, t_pr, args.runs, args.grid, gbm_d, gbm_s, df, sf, n_cores=args.cores)
        print(f"\nFinal verification with {args.n_runs} runs...")
        tr_sum, pr_sum = 0, 0
        for run_idx in range(args.n_runs):
            tr, pr = sim(best_params[0], best_params[1], best_params[2], best_params[3], args.runs * 2, gbm_d, gbm_s, df, sf)
            tr_sum += tr
            pr_sum += pr
            print(f"Run {run_idx+1}/{args.n_runs}: {tr:.1f} trades, {pr:.1f} profit")
        final_tr = tr_sum / args.n_runs
        final_pr = pr_sum / args.n_runs
        result = {'date': date, 'target_trades': t_tr, 'target_profit': t_pr, 'gbm_drift': gbm_d, 'gbm_sigma': gbm_s, 'drift_factor': df, 'sigma_factor': sf, 'best_params': {'P_00': best_params[0], 'P_11': best_params[1], 'POISSON_INTENSITY': best_params[2], 'JUMP_SIZE': best_params[3]}, 'best_loss': best_loss, 'final_trades': final_tr, 'final_profit': final_pr, 'runs': args.runs, 'grid': args.grid, 'cores_used': args.cores, 'n_runs': args.n_runs}
        all_results.append(result)
        print(f"Optimal: P00={best_params[0]:.6f}, P11={best_params[1]:.6f}, Poi={best_params[2]:.6f}, Jmp={best_params[3]:.6f}")
        print(f"Loss: {best_loss:.4f}, Final avg: {final_tr:.1f} trades, {final_pr:.1f} profit")
    with open(args.output, 'w') as f:
        json.dump(all_results, f, indent=2)
    latex_results = []
    for r in all_results:
        latex_results.append({'date': r['date'], 'p00': r['best_params']['P_00'], 'p11': r['best_params']['P_11'], 'poi': r['best_params']['POISSON_INTENSITY'], 'jmp': r['best_params']['JUMP_SIZE'], 'mu': r['gbm_drift'], 'sigma': r['gbm_sigma'], 'df': r['drift_factor'], 'sf': r['sigma_factor'], 't_tr': r['target_trades'], 'final_tr': r['final_trades'], 't_pr': r['target_profit'], 'final_pr': r['final_profit'], 'loss': r['best_loss']})
    latex_table = create_latex(latex_results)
    print("\n" + "="*80)
    print("RESULTS:")
    print("="*80)
    print(latex_table)
    print(f"\nSaved to {args.output}")
if __name__ == "__main__":
    main()
