116 lines
4.8 KiB
Python
116 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import json
|
|
import os
|
|
import concurrent.futures
|
|
from optimizer import run_optimized_backtest
|
|
import optimizers
|
|
|
|
|
|
def load_symbols_from_file(path):
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
return [line.strip().upper() for line in f if line.strip()]
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Run optimizer for multiple tickers')
|
|
parser.add_argument('--tickers', type=str, help='Comma-separated list of tickers, e.g. AAPL,MSFT')
|
|
parser.add_argument('--file', type=str, help='Path to a file containing one ticker per line')
|
|
parser.add_argument('--symbols', nargs='*', help='Space-separated tickers')
|
|
parser.add_argument('--json', type=str, help='Path to a JSON file with either an array of tickers or an object with a "tickers" key')
|
|
parser.add_argument('--workers', type=int, default=1, help='Number of parallel workers (processes) to use')
|
|
parser.add_argument('--optimizers', type=str, help='Comma-separated optimizer names from optimizers.py, e.g. RsiOptimizer')
|
|
args = parser.parse_args()
|
|
|
|
symbols = []
|
|
if args.tickers:
|
|
symbols = [s.strip().upper() for s in args.tickers.split(',') if s.strip()]
|
|
elif args.file:
|
|
if os.path.exists(args.file):
|
|
symbols = load_symbols_from_file(args.file)
|
|
else:
|
|
print(f"Ticker file not found: {args.file}")
|
|
raise SystemExit(1)
|
|
elif args.json:
|
|
if os.path.exists(args.json):
|
|
with open(args.json, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
if isinstance(data, list):
|
|
symbols = [s.strip().upper() for s in data if isinstance(s, str) and s.strip()]
|
|
elif isinstance(data, dict):
|
|
# support {'tickers': [...]} or {'symbols': [...]} keys
|
|
arr = data.get('tickers') or data.get('symbols')
|
|
if isinstance(arr, list):
|
|
symbols = [s.strip().upper() for s in arr if isinstance(s, str) and s.strip()]
|
|
else:
|
|
print(f"JSON file does not contain a list under 'tickers' or 'symbols': {args.json}")
|
|
raise SystemExit(1)
|
|
else:
|
|
print(f"JSON root must be an array or object: {args.json}")
|
|
raise SystemExit(1)
|
|
else:
|
|
print(f"JSON file not found: {args.json}")
|
|
raise SystemExit(1)
|
|
elif args.symbols:
|
|
symbols = [s.upper() for s in args.symbols]
|
|
else:
|
|
parser.print_help()
|
|
raise SystemExit(1)
|
|
|
|
# Resolve optimizers
|
|
if args.optimizers:
|
|
optimizer_names = [s.strip() for s in args.optimizers.split(',') if s.strip()]
|
|
else:
|
|
optimizer_names = ['RsiOptimizer']
|
|
|
|
# Build job list (symbol, optimizer_name) pairs
|
|
jobs = [(sym, opt_name) for sym in symbols for opt_name in optimizer_names]
|
|
|
|
workers = max(1, int(args.workers or 1))
|
|
results = []
|
|
|
|
if workers > 1:
|
|
print(f"Running optimizations in parallel with {workers} workers...")
|
|
with concurrent.futures.ProcessPoolExecutor(max_workers=workers) as executor:
|
|
# Use module-level runner to ensure picklable callable
|
|
future_to_job = {executor.submit(optimizers.run_optimizer, sym, opt_name): (sym, opt_name) for (sym, opt_name) in jobs}
|
|
for fut in concurrent.futures.as_completed(future_to_job):
|
|
sym, opt_name = future_to_job[fut]
|
|
try:
|
|
res = fut.result()
|
|
if isinstance(res, dict):
|
|
# tag result with optimizer name
|
|
res['optimizer'] = opt_name
|
|
results.append(res)
|
|
except Exception as e:
|
|
print(f"Error optimizing {sym} with {opt_name}: {e}")
|
|
else:
|
|
for sym, opt_name in jobs:
|
|
try:
|
|
print(f"\n--- Optimizing {sym} using {opt_name} ---")
|
|
res = optimizers.run_optimizer(sym, opt_name)
|
|
if isinstance(res, dict):
|
|
res['optimizer'] = opt_name
|
|
results.append(res)
|
|
except Exception as e:
|
|
print(f"Error optimizing {sym} with {opt_name}: {e}")
|
|
|
|
# Save results to Excel
|
|
if results:
|
|
try:
|
|
import pandas as pd
|
|
from datetime import datetime
|
|
|
|
ts = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
out_name = os.path.join('output', f'optimized_summary_{ts}.xlsx')
|
|
df = pd.DataFrame(results)
|
|
os.makedirs('output', exist_ok=True)
|
|
df.to_excel(out_name, index=False)
|
|
print(f"\n✅ Optimized summary saved: {out_name}")
|
|
except Exception as e:
|
|
print(f"Failed to write Excel summary: {e}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|