This repository runs a portfolio genetic algorithm (GA) and then applies a soft-optimization layer where an LLM scores Hall-of-Fame portfolios for strategic alignment. The active end-to-end entrypoint is main_ga_soft_opt.py.
The benchmark data follows the Chang et al. portfolio-optimization format:
Chang, T.-J., Meade, N., Beasley, J.E., Sharaiha, Y.M. (2000). Heuristics for cardinality constrained portfolio optimisation. Computers & Operations Research, 27(13), 1271-1302.
Install the Python dependencies used by the active workflow:
python -m pip install openai numpy pandas matplotlib deap adjustTextCreate a .env file or set these environment variables for the LLM stage:
AZURE_OPENAI_ENDPOINT=...
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_API_VERSION=...
AZURE_OPENAI_DEPLOYMENT=...
Run the full GA + soft-optimization workflow:
python main_ga_soft_opt.pyTo change the experiment, edit GA_RUNS and the global settings at the top of
main_ga_soft_opt.py.
main_ga_soft_opt.py orchestrates the current pipeline:
- Runs the GA through RichPOP_GA_DEAP.py for each target return.
- Saves the GA frontier, Hall-of-Fame logs, and comparison plot/report.
- Converts GA Hall-of-Fame portfolios into H.O.S. candidates with verified asset facts.
- Calls prompt_generator.py to create JSON and TXT prompt batches.
- Calls run_evaluation.py to score each prompt batch with Azure OpenAI.
- Summarizes LLM score statistics with LLM_eval_Stats.py.
- Runs lambda_sweep.py for each configured scaling method.
The script reuses existing hard-optimization outputs when all required GA files for the run label already exist. If timestamped LLM evaluation runs exist, it prints them and lets you choose one to reuse. Press Enter to create a new timestamped prompt/evaluation run.
- main_ga_soft_opt.py: active end-to-end orchestrator.
- RichPOP_GA_DEAP.py: DEAP-based GA solver.
- comparator.py: compares the heuristic frontier against the benchmark frontier.
- prompt_generator.py: creates LLM prompt payloads from H.O.S. candidates.
- run_evaluation.py: calls Azure OpenAI and writes evaluation outputs.
- lambda_sweep.py: combines GA risk/return with LLM strategic scores.
- LLM_eval_Stats.py: summarizes raw and averaged LLM scores.
- asset_token_validator.py: validates LLM-returned asset tokens.
- uef_deviation_eval.py: utilities for unconstrained frontier analysis.
- PreviousFiles/: archived older entrypoints and experiments.
Most outputs are written under results/ and include a run label such as
port1_g200_p50.
GA and H.O.S. outputs:
results/ga_frontier_<run_label>.txtresults/ga_frontier_<run_label>_comparison.txtresults/ga_frontier_<run_label>_comparison.pngresults/ga_hof_<run_label>.txtresults/ga_hof_points_<run_label>.txtresults/ga_hof_verification_<run_label>.txtresults/hos_candidates_<run_label>.json
Timestamped prompt and LLM evaluation outputs:
results/prompts_<run_label>/<run_stamp>/prompts_all.jsonresults/prompts_<run_label>/<run_stamp>/prompts_all.txtresults/prompts_<run_label>/<run_stamp>/prompt_batch_XX_of_YY.jsonresults/prompts_<run_label>/<run_stamp>/prompt_batch_XX_of_YY.txtresults/evaluations_<run_label>/<run_stamp>/evaluations_all.jsonresults/evaluations_<run_label>/<run_stamp>/evaluation_batch_XX_of_YY.jsonresults/evaluations_<run_label>/<run_stamp>/sent_prompts_txt/
Lambda sweep outputs:
results/lambda_sweep/<run_label>/minmax/lambda_sweep_all_minmax.csvresults/lambda_sweep/<run_label>/minmax/lambda_sweep_frontiers_minmax.csvresults/lambda_sweep/<run_label>/minmax/lambda_frontiers_minmax.pngresults/lambda_sweep/<run_label>/minmax/strategic_tradeoff_minmax.pngresults/lambda_sweep/<run_label>/proportional/...results/lambda_sweep/<run_label>/llm_eval_stats/
The active workflow is designed to avoid unnecessary GA and LLM reruns:
- GA outputs are reused only when all required files for the run label exist and are non-empty.
- Existing timestamped LLM evaluations are discovered under
results/evaluations_<run_label>/. - If you choose an existing evaluation run, the matching prompt timestamp is reused and the LLM is not called.
- If you create a new timestamped run, prompts are generated only if missing and
run_evaluation.pyskips existing outputs unless--forceis passed. - Lambda sweep receives the exact timestamped evaluation folder selected or created by
main_ga_soft_opt.py. - If
run_lambda_sweep_if_ready()is used without an explicit evaluation folder, it falls back to directevaluation_batch_*.jsonfolders or the newest timestamped evaluation folder.
By default, LAMBDA_SWEEP_REUSE_EXISTING = False, so lambda sweep outputs are
regenerated when the pipeline reaches that stage. Set it to True to reuse
existing lambda sweep CSV/PNG outputs when all required files exist.
- Prompt batches are written as both JSON and human-readable TXT.
run_evaluation.pyprefers TXT prompt batches when present, then falls back to JSON.- Each LLM call is saved as one
evaluation_batch_XX_of_YY.jsonentry. evaluations_all.jsoncontains the same entries as one combined list.- Repeats keep the canonical
batch_index, but even repeats reverse call order and reverse the candidate block to reduce ordering bias. - Every prompt actually sent to the model is logged under
sent_prompts_txt/.
Regenerate LLM score statistics from an evaluation path:
python LLM_eval_Stats.py --eval-path results/evaluations_port1_g200_p50/20260523_142515 --output-dir results/lambda_sweep/port1_g200_p50/llm_eval_statsRun a lambda sweep directly:
python lambda_sweep.py --hof results/ga_hof_port1_g200_p50.txt --eval results/evaluations_port1_g200_p50/20260523_142515 --out results/lambda_sweep/port1_g200_p50/minmax --scaling minmax --no-interactive-labelsRun asset-token validation:
python asset_token_validator.py --results-root results --output-root resultsChang-format instance files in data/ are structured as:
N, the number of assets.Nlines withmean_return std_dev.- Triangular correlation rows as
i j corr(i,j)using 1-based asset indices.
Benchmark frontier files such as data/portef1.txt are used by
comparator.py and lambda_sweep.py as
reference frontiers.
The LLM stage embeds these files into prompt payloads:
data_for_soft_optimization/port1_semistructured_information.csvdata_for_soft_optimization/eval_rubric.txt
Older exploratory drivers are kept in PreviousFiles/. They are useful for historical comparison, but the maintained workflow in this workspace is main_ga_soft_opt.py.
If you use this repository in research reports, cite:
Chang, T.-J., Meade, N., Beasley, J.E., Sharaiha, Y.M. (2000). Heuristics for cardinality constrained portfolio optimisation. Computers & Operations Research, 27(13), 1271-1302.