diff --git a/.gitignore b/.gitignore index aa0b627..26ab601 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ env.ps1 .vscode github.ipynb -build.sh \ No newline at end of file +build.sh +*.pyc diff --git a/docs/superpowers/plans/2026-03-22-category-first-reorganization.md b/docs/superpowers/plans/2026-03-22-category-first-reorganization.md deleted file mode 100644 index e182bb4..0000000 --- a/docs/superpowers/plans/2026-03-22-category-first-reorganization.md +++ /dev/null @@ -1,1147 +0,0 @@ -# Category-First README Reorganization - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Reorganize README.md from language-first to category-first structure, so all backtesting tools (Python, R, Julia, Rust, etc.) live in one section instead of being scattered across 6+ language headings. - -**Architecture:** A migration script reads the current README.md, maps each entry to its new category section, prepends an inline language tag (e.g., `` `Python` ``), deduplicates, and writes the new README.md. Then `parse.py` and `site/generate.py` are updated to extract language from inline tags instead of `##` headings. The website gains multi-language filtering. - -**Tech Stack:** Python 3.11+, regex, existing parse.py/generate.py pipeline, GitHub Actions CI. - ---- - -## File Structure - -``` -README.md — Restructured: category-first with inline language tags -parse.py — Updated: extract language from backtick tags, h2=category -site/generate.py — Updated: parse_readme() + load_csv() + tags for new structure -site/static/main.js — Updated: multi-language filter support -site/static/style.css — Updated: language tag colors per language -scripts/migrate_readme.py — NEW: one-time migration script -CONTRIBUTING.md — Updated: new entry format with language tags -CLAUDE.md — Updated: reflect new structure -.claude/skills/review-pr/SKILL.md — Updated: new section list and format rules -``` - ---- - -## New Entry Format - -**Current:** -```markdown -- [nautilus_trader](https://github.com/nautechsystems/nautilus_trader) - High-performance algorithmic trading platform. -``` -(Language is implied by the `## Python` heading above it.) - -**New:** -```markdown -- [nautilus_trader](https://github.com/nautechsystems/nautilus_trader) - `Python` `Rust` - High-performance algorithmic trading platform. -``` - -**Regex change:** The existing regex `^\s*- \[(.*)\]\((.*)\) - (.*)$` still matches. Group 3 now captures `` `Python` `Rust` - Description. `` — a secondary regex splits language tags from description. - -**New secondary regex:** -```python -re_langs = re.compile(r'^((?:`[^`]+`\s*)+)-\s*(.*)$') -``` - ---- - -## New Section Structure - -| New Section (h2) | Sources (old sections mapped here) | -|---|---| -| Numerical Libraries & Data Structures | Python > Numerical, R > Numerical, Julia data libs | -| Financial Instruments & Pricing | Python > Financial Instruments, R > Financial Instruments, Java, Haskell, Scala, Julia pricing libs, Matlab > Frameworks | -| Technical Indicators | Python > Indicators, R > TTR, Julia indicator libs, JS > chart-patterns/orderflow, Go > IndicatorGo, Rust > SlidingFeatures, plus misplaced TA libs from Python > Trading (bta-lib, ta, TuneTA) | -| Trading & Backtesting | Python > Trading (minus portfolio/indicator tools), R > Trading + Backtesting, Julia trading libs, Ruby, Elixir/Erlang, Go > Kelp, Rust > Barter/LFEST, C++ > TradeFrame/PandoraTrader, C# > all, JS > ccxt/Ghostfolio/etc | -| Portfolio Optimization & Risk Analysis | Python > Risk Analysis + misplaced portfolio tools (PyPortfolioOpt, skfolio, riskparity.py, DeepDow, Eiten from Trading), R > Risk Analysis + PortfolioAnalytics/fPortfolio/riskParityPortfolio from Financial Instruments | -| Factor Analysis | Python > Factor Analysis, R > Factor Analysis | -| Sentiment Analysis & Alternative Data | Python > Sentiment Analysis | -| Time Series Analysis | Python > Time Series, R > Time Series | -| Market Data & Data Sources | Python > Data Sources, R > Data Sources, Julia > MarketData.jl/CryptoExchangeAPIs.jl, Rust > fin-stream/finalytics, JS > PENDAX/pmxt | -| Prediction Markets | pmxt (Python, JS), Polymarket Scanner API, polymarket-whales, SimpleFunctions, Parsec, Telonex | -| Calendars & Market Hours | Python > Calendars, R > Calendars | -| Visualization | Python > Visualization, Julia > LightweightCharts.jl/TechnicalIndicatorCharts.jl, JS > QUANTAXIS_Webkit | -| Excel & Spreadsheet Integration | Python > Excel Integration (unchanged) | -| Quant Research Environments | Python > Quant Research Environment (unchanged) | -| Cross-Language Frameworks | Current Frameworks section (QuantLib ecosystem, TA-Lib, XAD) | -| Reproducing Works, Training & Books | Unchanged | -| Commercial & Proprietary Services | Unchanged | -| Related Lists | Unchanged | - ---- - -## Duplicates to Remove - -| Project | Duplicate Locations | Keep | -|---|---|---| -| Hikyuu | Python > Trading, CPP | Single entry with `Python` `C++` tags in Trading & Backtesting | -| fast-trade | Python > Trading (lines 144, 149) | Single entry | -| wallstreet | Python > Data Sources (lines 245, 301) | Single entry | -| pmxt | Python > Numerical, JS (lines 42, 486, 487) | Single entry with `Python` `JavaScript` in Prediction Markets | -| QuantLib | CPP, Frameworks | Keep in Cross-Language Frameworks only | -| QuantLibRisks | Python > Risk, CPP | Single entry with `Python` `C++` | -| XAD | Python > Risk, CPP, Frameworks | Keep in Cross-Language Frameworks only | -| fecon235 | Python > Risk, Reproducing Works | Keep both (different contexts) | -| OpenFinClaw | Python > Trading, Rust | Single entry with `Python` `Rust` | -| RunMat | Matlab > Alternatives, Rust | Single entry with `Matlab` `Rust` | - -## Misplaced Projects to Move - -| Project | Current Section | Correct Section | Reason | -|---|---|---|---| -| PyPortfolioOpt | Trading & Backtesting | Portfolio Optimization & Risk | Portfolio optimization tool | -| skfolio | Trading & Backtesting | Portfolio Optimization & Risk | Portfolio optimization (scikit-learn based) | -| riskparity.py | Trading & Backtesting | Portfolio Optimization & Risk | Risk parity portfolio design | -| DeepDow | Trading & Backtesting | Portfolio Optimization & Risk | Deep learning portfolio optimization | -| Eiten | Trading & Backtesting | Portfolio Optimization & Risk | Eigen/Min Var/Max Sharpe portfolios | -| bta-lib | Trading & Backtesting | Technical Indicators | TA library | -| ta | Trading & Backtesting | Technical Indicators | TA library | -| TuneTA | Trading & Backtesting | Technical Indicators | TA indicator optimizer | -| TA-Lib (Python wrapper) | Trading & Backtesting | Technical Indicators | TA library wrapper | -| OpenBB Terminal | Financial Instruments | Market Data & Data Sources | Research terminal / data platform | -| Fincept Terminal | Financial Instruments | Market Data & Data Sources | Research terminal / data platform | - ---- - -## Task 1: Write the Migration Script - -**Files:** -- Create: `scripts/migrate_readme.py` - -This script automates the README restructure. It reads the current README, classifies each entry, adds language tags, deduplicates, and writes the new file. - -- [ ] **Step 1: Create the migration script** - -```python -#!/usr/bin/env python3 -"""Migrate README.md from language-first to category-first organization. - -Reads the current README.md, classifies entries into new category sections, -adds inline language tags, deduplicates, and writes the result. -""" - -import re -from pathlib import Path - -# --- Configuration --- - -# Map (old_language, old_category) → new_section -# Empty old_category means the language section had no subsections (flat list) -SECTION_MAP = { - # Python - ("Python", "Numerical Libraries & Data Structures"): "Numerical Libraries & Data Structures", - ("Python", "Financial Instruments and Pricing"): "Financial Instruments & Pricing", - ("Python", "Indicators"): "Technical Indicators", - ("Python", "Trading & Backtesting"): "Trading & Backtesting", - ("Python", "Risk Analysis"): "Portfolio Optimization & Risk Analysis", - ("Python", "Factor Analysis"): "Factor Analysis", - ("Python", "Sentiment Analysis"): "Sentiment Analysis & Alternative Data", - ("Python", "Quant Research Environment"): "Quant Research Environments", - ("Python", "Time Series"): "Time Series Analysis", - ("Python", "Calendars"): "Calendars & Market Hours", - ("Python", "Data Sources"): "Market Data & Data Sources", - ("Python", "Excel Integration"): "Excel & Spreadsheet Integration", - ("Python", "Visualization"): "Visualization", - # R - ("R", "Numerical Libraries & Data Structures"): "Numerical Libraries & Data Structures", - ("R", "Data Sources"): "Market Data & Data Sources", - ("R", "Financial Instruments and Pricing"): "Financial Instruments & Pricing", - ("R", "Trading"): "Trading & Backtesting", - ("R", "Backtesting"): "Trading & Backtesting", - ("R", "Risk Analysis"): "Portfolio Optimization & Risk Analysis", - ("R", "Factor Analysis"): "Factor Analysis", - ("R", "Time Series"): "Time Series Analysis", - ("R", "Calendars"): "Calendars & Market Hours", - # Matlab - ("Matlab", "Alternatives"): "Cross-Language Frameworks", - ("Matlab", "FrameWorks"): "Trading & Backtesting", - # Julia (flat) - ("Julia", ""): "CLASSIFY_JULIA", - # Java (flat) - ("Java", ""): "Financial Instruments & Pricing", - # JavaScript (flat + subcategory) - ("JavaScript", ""): "CLASSIFY_JS", - ("JavaScript", "Data Visualization"): "Visualization", - # Haskell - ("Haskell", ""): "Financial Instruments & Pricing", - # Scala - ("Scala", ""): "Financial Instruments & Pricing", - # Ruby - ("Ruby", ""): "Trading & Backtesting", - # Elixir/Erlang - ("Elixir/Erlang", ""): "Trading & Backtesting", - # Golang - ("Golang", ""): "CLASSIFY_GO", - # CPP - ("CPP", ""): "CLASSIFY_CPP", - # CSharp - ("CSharp", ""): "Trading & Backtesting", - # Rust - ("Rust", ""): "CLASSIFY_RUST", - # Frameworks - ("Frameworks", ""): "Cross-Language Frameworks", - # Reproducing Works - ("Reproducing Works, Training & Books", ""): "Reproducing Works, Training & Books", - # Commercial - ("Commercial & Proprietary Services", ""): "Commercial & Proprietary Services", - # Related - ("Related Lists", ""): "Related Lists", -} - -# Per-project overrides for misplaced entries or entries in flat sections -# that need manual classification. -# Key = (project_name, old_language) to avoid collisions between same-named -# projects in different sections (e.g., TA-Lib in Python vs Frameworks). -PROJECT_OVERRIDES = { - # Misplaced: should be Portfolio Optimization & Risk - ("PyPortfolioOpt", "Python"): "Portfolio Optimization & Risk Analysis", - ("skfolio", "Python"): "Portfolio Optimization & Risk Analysis", - ("riskparity.py", "Python"): "Portfolio Optimization & Risk Analysis", - ("DeepDow", "Python"): "Portfolio Optimization & Risk Analysis", - ("Eiten", "Python"): "Portfolio Optimization & Risk Analysis", - ("mlfinlab", "Python"): "Portfolio Optimization & Risk Analysis", - # Misplaced: should be Technical Indicators - ("bta-lib", "Python"): "Technical Indicators", - ("ta", "Python"): "Technical Indicators", - ("TuneTA", "Python"): "Technical Indicators", - ("TA-Lib", "Python"): "Technical Indicators", # Python wrapper → Indicators - # Note: ("TA-Lib", "Frameworks") is NOT overridden — stays in Cross-Language Frameworks - # Misplaced: should be Market Data - ("OpenBB Terminal", "Python"): "Market Data & Data Sources", - ("Fincept Terminal", "Python"): "Market Data & Data Sources", - # Prediction Markets - ("pmxt", "Python"): "Prediction Markets", - ("pmxt", "JavaScript"): "Prediction Markets", - ("Polymarket Scanner API", "Python"): "Prediction Markets", - ("polymarket-whales", "Python"): "Prediction Markets", - ("SimpleFunctions", "JavaScript"): "Prediction Markets", - ("Parsec", "Commercial & Proprietary Services"): "Commercial & Proprietary Services", # Keep commercial - ("Telonex", "Commercial & Proprietary Services"): "Commercial & Proprietary Services", # Keep commercial - # Julia classification - ("QuantLib.jl", "Julia"): "Financial Instruments & Pricing", - ("Miletus.jl", "Julia"): "Financial Instruments & Pricing", - ("Ito.jl", "Julia"): "Financial Instruments & Pricing", - ("Fastback.jl", "Julia"): "Trading & Backtesting", - ("Lucky.jl", "Julia"): "Trading & Backtesting", - ("Strategems.jl", "Julia"): "Trading & Backtesting", - ("TALib.jl", "Julia"): "Technical Indicators", - ("Indicators.jl", "Julia"): "Technical Indicators", - ("OnlineTechnicalIndicators.jl", "Julia"): "Technical Indicators", - ("TechnicalIndicatorCharts.jl", "Julia"): "Technical Indicators", - ("MarketTechnicals.jl", "Julia"): "Technical Indicators", - ("LightweightCharts.jl", "Julia"): "Visualization", - ("MarketData.jl", "Julia"): "Market Data & Data Sources", - ("CryptoExchangeAPIs.jl", "Julia"): "Market Data & Data Sources", - ("CcyConv.jl", "Julia"): "Market Data & Data Sources", - ("TimeSeries.jl", "Julia"): "Time Series Analysis", - ("Temporal.jl", "Julia"): "Numerical Libraries & Data Structures", - ("DataFrames.jl", "Julia"): "Numerical Libraries & Data Structures", - ("TSFrames.jl", "Julia"): "Numerical Libraries & Data Structures", - ("TimeArrays.jl", "Julia"): "Numerical Libraries & Data Structures", - ("TimeFrames.jl", "Julia"): "Time Series Analysis", - ("OnlinePortfolioAnalytics.jl", "Julia"): "Portfolio Optimization & Risk Analysis", - ("RiskPerf.jl", "Julia"): "Portfolio Optimization & Risk Analysis", - ("OnlineResamplers.jl", "Julia"): "Market Data & Data Sources", - # Java classification - ("ta4j", "Java"): "Technical Indicators", - # Golang classification - ("Kelp", "Golang"): "Trading & Backtesting", - ("marketstore", "Golang"): "Market Data & Data Sources", - ("IndicatorGo", "Golang"): "Technical Indicators", - # CPP classification - ("QuantLib", "CPP"): "Cross-Language Frameworks", - ("QuantLibRisks", "CPP"): "Cross-Language Frameworks", - ("XAD", "CPP"): "Cross-Language Frameworks", - ("TradeFrame", "CPP"): "Trading & Backtesting", - ("Hikyuu", "CPP"): "Trading & Backtesting", - ("PandoraTrader", "CPP"): "Trading & Backtesting", - ("OrderMatchingEngine", "CPP"): "Trading & Backtesting", - ("NexusFix", "CPP"): "Trading & Backtesting", - # Rust classification - ("QuantMath", "Rust"): "Financial Instruments & Pricing", - ("RustQuant", "Rust"): "Financial Instruments & Pricing", - ("Barter", "Rust"): "Trading & Backtesting", - ("LFEST", "Rust"): "Trading & Backtesting", - ("TradeAggregation", "Rust"): "Technical Indicators", - ("SlidingFeatures", "Rust"): "Technical Indicators", - ("fin-primitives", "Rust"): "Technical Indicators", - ("fin-stream", "Rust"): "Market Data & Data Sources", - ("finalytics", "Rust"): "Market Data & Data Sources", - ("OpenFinClaw", "Rust"): "Trading & Backtesting", - ("RunMat", "Rust"): "Cross-Language Frameworks", - ("Special-Relativity-in-Financial-Modeling", "Rust"): "Reproducing Works, Training & Books", - # JavaScript classification - ("finance.js", "JavaScript"): "Financial Instruments & Pricing", - ("portfolio-allocation", "JavaScript"): "Portfolio Optimization & Risk Analysis", - ("Ghostfolio", "JavaScript"): "Portfolio Optimization & Risk Analysis", - ("IndicatorTS", "JavaScript"): "Technical Indicators", - ("chart-patterns", "JavaScript"): "Technical Indicators", - ("orderflow", "JavaScript"): "Technical Indicators", - ("ccxt", "JavaScript"): "Trading & Backtesting", - ("PENDAX", "JavaScript"): "Market Data & Data Sources", - ("PreReason", "JavaScript"): "Market Data & Data Sources", - ("rebalance", "JavaScript"): "Portfolio Optimization & Risk Analysis", - ("QUANTAXIS_Webkit", "JavaScript"): "Visualization", - # R overrides for misplaced items - ("PortfolioAnalytics", "R"): "Portfolio Optimization & Risk Analysis", - ("riskParityPortfolio", "R"): "Portfolio Optimization & Risk Analysis", - ("portfolio", "R"): "Portfolio Optimization & Risk Analysis", - ("sparseIndexTracking", "R"): "Portfolio Optimization & Risk Analysis", - ("covFactorModel", "R"): "Factor Analysis", - ("TTR", "R"): "Technical Indicators", - # Note: fPortfolio is a sub-entry under Rmetrics, not a standalone entry. - # It will be preserved as a sub-line under Rmetrics in Financial Instruments. -} - -# Multi-language projects: name -> list of languages -MULTI_LANG = { - "Hikyuu": ["Python", "C++"], - "Lean": ["Python", "C#"], - "ccxt": ["JavaScript", "Python", "PHP"], - "OpenFinClaw": ["Python", "Rust"], - "RunMat": ["Matlab", "Rust"], - "pmxt": ["Python", "JavaScript"], - "nautilus_trader": ["Python", "Rust"], -} - -# Projects to remove as duplicates (keep the first occurrence) -DUPLICATES_TO_REMOVE = { - # (name, url) pairs to skip on second occurrence - ("fast-trade", "https://github.com/jrmeier/fast-trade"), - ("wallstreet", "https://github.com/mcdallas/wallstreet"), -} - -# Additional dedup: entries with same name but different URLs (keep first by default) -# pmxt has 3 entries: pmxt-dev (Python), pmxt-dev (JS), qoery-com (JS) -# Keep pmxt-dev/pmxt, skip qoery-com/pmxt -URLS_TO_SKIP = { - "https://github.com/qoery-com/pmxt", # duplicate of pmxt-dev/pmxt -} - -# Section display order -SECTION_ORDER = [ - "Numerical Libraries & Data Structures", - "Financial Instruments & Pricing", - "Technical Indicators", - "Trading & Backtesting", - "Portfolio Optimization & Risk Analysis", - "Factor Analysis", - "Sentiment Analysis & Alternative Data", - "Time Series Analysis", - "Market Data & Data Sources", - "Prediction Markets", - "Calendars & Market Hours", - "Visualization", - "Excel & Spreadsheet Integration", - "Quant Research Environments", - "Cross-Language Frameworks", - "Reproducing Works, Training & Books", - "Commercial & Proprietary Services", - "Related Lists", -] - -# Sections where we DON'T add language tags (not language-specific) -NO_LANG_TAG_SECTIONS = { - "Cross-Language Frameworks", - "Reproducing Works, Training & Books", - "Commercial & Proprietary Services", - "Related Lists", -} - - -def parse_current_readme(path: str): - """Parse the current language-first README and return entries.""" - re_h2 = re.compile(r"^## (.+)$") - re_h3 = re.compile(r"^### (.+)$") - re_entry = re.compile(r"^\s*- \[(.+?)\]\((.+?)\) - (.+)$") - re_badge = re.compile(r"\s*!\[[^\]]*\]\([^)]*\)\s*") - # Match indented sub-entries (e.g., Rmetrics sub-packages, QuantLib ports) - re_sub_entry = re.compile(r"^\s{2,}- ") - - entries = [] - current_language = "" - current_category = "" - skip_sections = {"Languages"} - # Track sub-entries that belong to a parent - pending_sub_lines = [] - last_entry = None - - with open(path, "r", encoding="utf-8") as f: - for line in f: - line = re_badge.sub(" ", line).rstrip("\n") - - m = re_h2.match(line) - if m: - current_language = m.group(1).strip() - current_category = "" - continue - - m = re_h3.match(line) - if m: - current_category = m.group(1).strip() - continue - - if current_language in skip_sections: - continue - - # Check for sub-entries (indented items under a parent) - if re_sub_entry.match(line) and last_entry: - last_entry["sub_lines"].append(line) - continue - - m = re_entry.match(line) - if m: - name = m.group(1).strip() - url = m.group(2).strip() - desc = m.group(3).strip() - - entry = { - "name": name, - "url": url, - "description": desc, - "language": current_language, - "category": current_category, - "raw_line": line, - "sub_lines": [], - } - entries.append(entry) - last_entry = entry - - return entries - - -def classify_entry(entry: dict) -> str: - """Determine the new section for an entry.""" - name = entry["name"] - lang = entry["language"] - cat = entry["category"] - - # Check project-level overrides first (composite key: name + language) - override_key = (name, lang) - if override_key in PROJECT_OVERRIDES: - return PROJECT_OVERRIDES[override_key] - - # Check section map - key = (lang, cat) - if key in SECTION_MAP: - section = SECTION_MAP[key] - if not section.startswith("CLASSIFY_"): - return section - - # Fallback: flag unmapped entries - print(f" WARNING: No mapping for ({lang!r}, {cat!r}) — project {name!r}") - print(f" Add to SECTION_MAP or PROJECT_OVERRIDES and rerun.") - return "Trading & Backtesting" - - -def get_languages(entry: dict) -> list[str]: - """Get language tags for an entry.""" - name = entry["name"] - if name in MULTI_LANG: - return MULTI_LANG[name] - lang = entry["language"] - # Don't tag entries from non-language sections - if lang in ( - "Frameworks", - "Reproducing Works, Training & Books", - "Commercial & Proprietary Services", - "Related Lists", - ): - return [] - return [lang] - - -def format_entry(entry: dict, section: str) -> str: - """Format an entry with inline language tags.""" - name = entry["name"] - url = entry["url"] - desc = entry["description"] - langs = get_languages(entry) - - # Build language tag string - if langs and section not in NO_LANG_TAG_SECTIONS: - lang_tags = " ".join(f"`{l}`" for l in langs) - line = f"- [{name}]({url}) - {lang_tags} - {desc}" - else: - line = f"- [{name}]({url}) - {desc}" - - # Append sub-lines (e.g., QuantLib ports, Rmetrics sub-packages) - result = line - for sub in entry.get("sub_lines", []): - result += "\n" + sub - - return result - - -def slugify(text: str) -> str: - """Convert text to lowercase hyphen-separated slug (matches site/generate.py).""" - text = text.lower().strip() - text = re.sub(r"[&/]+", "-", text) - text = re.sub(r"[^\w\s-]", "", text) - text = re.sub(r"[\s_]+", "-", text) - text = re.sub(r"-+", "-", text) - return text.strip("-") - - -def build_toc(sections: list[str]) -> str: - """Build the table of contents.""" - lines = ["## Contents", ""] - for s in sections: - lines.append(f"- [{s}](#{slugify(s)})") - return "\n".join(lines) - - -def main(): - root = Path(__file__).resolve().parent.parent - readme_path = root / "README.md" - output_path = root / "README.md.new" - - print(f"Parsing {readme_path}...") - entries = parse_current_readme(str(readme_path)) - print(f"Found {len(entries)} entries") - - # Classify and deduplicate - sections: dict[str, list[str]] = {s: [] for s in SECTION_ORDER} - seen = set() - - for entry in entries: - # Skip explicitly blocked URLs - if entry["url"] in URLS_TO_SKIP: - print(f" Skipping blocked URL: {entry['name']} ({entry['url']})") - continue - - key = (entry["name"], entry["url"]) - if key in DUPLICATES_TO_REMOVE and key in seen: - print(f" Skipping duplicate: {entry['name']}") - continue - seen.add(key) - - section = classify_entry(entry) - if section not in sections: - print(f" WARNING: Unknown section {section!r}, adding it") - sections[section] = [] - SECTION_ORDER.append(section) - - formatted = format_entry(entry, section) - sections[section].append(formatted) - - # Build output - header = [ - "# Awesome Quant", - "", - "A curated list of insanely awesome libraries, packages and resources " - "for Quants (Quantitative Finance).", - "", - "[](https://awesome.re)", - "", - ] - - active_sections = [s for s in SECTION_ORDER if sections.get(s)] - toc = build_toc(active_sections) - - body_parts = [] - for section_name in SECTION_ORDER: - items = sections.get(section_name, []) - if not items: - continue - body_parts.append(f"\n## {section_name}\n") - body_parts.append("\n".join(items)) - - output = "\n".join(header) + "\n" + toc + "\n" + "\n".join(body_parts) + "\n" - - output_path.write_text(output, encoding="utf-8") - print(f"\nWrote {output_path}") - print(f"Sections: {len(active_sections)}") - for s in active_sections: - print(f" {s}: {len(sections[s])} entries") - print(f"\nReview the output, then: mv README.md.new README.md") - - -if __name__ == "__main__": - main() -``` - -- [ ] **Step 2: Fix malformed entry before migration** - -The `quantra` entry at README.md line 72 is missing the ` - ` separator: -``` -- [quantra](https://github.com/joseprupi/quantraserver) High-performance pricing engine... -``` -Fix it to: -``` -- [quantra](https://github.com/joseprupi/quantraserver) - High-performance pricing engine... -``` - -- [ ] **Step 3: Run the migration script** - -```bash -cd /home/wilson/dev/python/awesome-quant -uv run python scripts/migrate_readme.py -``` - -Expected: Prints entry counts per section, creates `README.md.new`. - -- [ ] **Step 4: Review the output and fix classification issues** - -```bash -# Check section sizes -grep -c "^- \[" README.md.new -# Check for WARNING lines in the script output -# Diff to see what moved -diff <(grep "^- \[" README.md | sort) <(grep "^- \[" README.md.new | sort) -``` - -Iterate: fix `PROJECT_OVERRIDES` and `SECTION_MAP` in the script, rerun until all entries are correctly classified. - -- [ ] **Step 5: Replace README.md** - -```bash -mv README.md README.md.bak -mv README.md.new README.md -``` - -- [ ] **Step 6: Manual review pass** - -Read through each section in README.md. Check: -- All entries have correct language tags -- No duplicates remain -- Descriptions still end with periods -- Sub-entries (Rmetrics, QuantLib ecosystem) are preserved -- The QuantLib entry in Cross-Language Frameworks keeps all its sub-items - -- [ ] **Step 7: Commit** - -```bash -git add scripts/migrate_readme.py README.md -git commit -m "Reorganize README: category-first with inline language tags - -Restructure from language-first (## Python > ### Category) to -category-first (## Category) organization. Each entry now has -inline language tags (e.g. \`Python\` \`Rust\`). - -Deduplicates: Hikyuu, fast-trade, wallstreet, pmxt, OpenFinClaw. -Fixes misplacements: portfolio tools moved from Trading to Risk, -TA libs moved from Trading to Indicators." -``` - ---- - -## Task 2: Update parse.py — Extract Language from Inline Tags - -**Files:** -- Modify: `parse.py:225-270` (README parsing loop) -- Modify: `parse.py:157-223` (Project class) - -The parser currently gets `language` from `## heading` and `category` from `### heading`. In the new structure, `## heading` = category and language comes from inline backtick tags in the entry description. - -- [ ] **Step 1: Add language tag extraction function** - -Add after the existing `slugify` function (~line 138): - -```python -re_langs = re.compile(r'^((?:`[^`]+`\s*)+)-\s*(.*)$') - -def extract_languages(description: str) -> tuple[list[str], str]: - """Extract inline language tags from description. - - Returns (languages, clean_description). - E.g. "`Python` `Rust` - High-performance..." -> (["Python", "Rust"], "High-performance...") - """ - m = re_langs.match(description) - if m: - lang_str = m.group(1) - clean_desc = m.group(2) - langs = re.findall(r'`([^`]+)`', lang_str) - return langs, clean_desc - return [], description -``` - -- [ ] **Step 2: Update the README parsing loop** - -In the main parsing loop (~line 229+), change the heading logic: - -```python -# OLD: h2 = language, h3 = category -# NEW: h2 = category (no more h3 subcategories) -# Language comes from inline tags in each entry - -current_category = "" - -for line in f: - line = re_badge.sub(" ", line) - m = rex.match(line) - if m: - name = m.group(1).strip() - url = m.group(2).strip() - raw_desc = m.group(3).strip() - - # Extract language tags from description - languages, description = extract_languages(raw_desc) - primary_language = languages[0] if languages else "" - - p = Project(m, primary_language, current_category, current_category) - p.languages = languages - p.clean_description = description - p.start() - projects.append(p) - else: - m = ret.match(line) - if m: - hrs = m.group(1) - title = m.group(2).strip() - if len(hrs) == 2 and title != "Contents": - current_category = title -``` - -- [ ] **Step 3: Update the Project class to use extracted language** - -Update `Project.__init__` to accept the new fields and `Project.run` to write `clean_description` and `languages` to the CSV row: - -```python -# In Project.run(), update the regs dict: -self.regs["languages"] = ",".join(self.languages) -self.regs["description"] = self.clean_description -``` - -- [ ] **Step 4: Update CSV columns** - -In the DataFrame creation (~line 273), ensure the new `languages` column is included. The old `language` column stays (primary language) and `languages` is added (comma-separated list). - -- [ ] **Step 5: Test parse.py locally** - -```bash -cd /home/wilson/dev/python/awesome-quant -uv run python parse.py 2>&1 | head -20 -# Check CSV output -head -5 site/projects.csv -``` - -Expected: CSV has `languages` column. Primary `language` column still works. - -- [ ] **Step 6: Commit** - -```bash -git add parse.py -git commit -m "Update parse.py: extract language from inline backtick tags - -h2 headings are now categories (not languages). Language is extracted -from inline tags like \`Python\` \`Rust\` at the start of descriptions. -Adds 'languages' column to CSV for multi-language projects." -``` - ---- - -## Task 3: Update site/generate.py — New Structure Support - -**Files:** -- Modify: `site/generate.py:26-107` (parse_readme function) -- Modify: `site/generate.py:110-136` (load_csv function) -- Modify: `site/generate.py:146-191` (build_tags_html function) -- Modify: `site/generate.py:194-387` (generate_html function) - -- [ ] **Step 1: Update parse_readme() for new heading structure** - -```python -def parse_readme(path: str) -> list[dict]: - """Parse README.md and return a list of project entries (no API data).""" - entries = [] - current_category = "" - - re_h2 = re.compile(r"^## (.+)$") - re_entry = re.compile(r"^\s*- \[(.+?)\]\((.+?)\) - (.+)$") - re_github = re.compile(r"\[GitHub\]\((https://github\.com/[\w-]+/[-\w\.]+)\)") - re_badge = re.compile(r"\s*!\[[^\]]*\]\([^)]*\)\s*") - re_langs = re.compile(r'^((?:`[^`]+`\s*)+)-\s*(.*)$') - - skip_sections = {"Contents"} - - with open(path, "r", encoding="utf-8") as f: - for line in f: - line = re_badge.sub(" ", line).rstrip("\n") - - m = re_h2.match(line) - if m: - current_category = m.group(1).strip() - continue - - if current_category in skip_sections: - continue - - m = re_entry.match(line) - if m: - name = m.group(1).strip() - url = m.group(2).strip() - raw_desc = m.group(3).strip() - - # Extract inline language tags - m_lang = re_langs.match(raw_desc) - if m_lang: - lang_str = m_lang.group(1) - desc = m_lang.group(2) - languages = re.findall(r'`([^`]+)`', lang_str) - else: - desc = raw_desc - languages = [] - - primary_language = languages[0] if languages else "" - - github_url = "" - gh_match = re_github.search(desc) - if gh_match: - github_url = gh_match.group(1) - desc = re_github.sub("", desc).rstrip(". ").rstrip() + "." - elif "github.com" in url: - github_url = url - - repo = "" - if github_url: - repo_match = re.match( - r"https://github\.com/([\w-]+/[-\w\.]+)", github_url - ) - if repo_match: - repo = repo_match.group(1) - - is_cran = "cran.r-project.org" in url - is_pypi = "pypi.org" in url or "pypi.python.org" in url - is_commercial = current_category == "Commercial & Proprietary Services" - section_slug = slugify(current_category) - - entries.append( - { - "project": name, - "language": primary_language, - "languages": ",".join(languages), - "category": current_category, - "section_slug": section_slug, - "url": url, - "description": desc, - "github": bool(github_url), - "cran": is_cran, - "pypi": is_pypi, - "commercial": is_commercial, - "github_url": github_url, - "repo": repo, - "stars": 0, - "last_commit": "", - } - ) - - return entries -``` - -- [ ] **Step 2: Update load_csv() for languages column** - -Add to `load_csv()`: -```python -row["languages"] = row.get("languages", row.get("language", "")) -``` - -- [ ] **Step 3: Update build_tags_html() for language tags** - -Replace the single language tag with multi-language tags: - -```python -def build_tags_html(e: dict) -> str: - """Build tag pills for an entry.""" - esc = html.escape - tags = [] - - # Language tags (from inline backtick tags) - languages_str = e.get("languages", e.get("language", "")) - languages = [l.strip() for l in languages_str.split(",") if l.strip()] - skip_langs = {"Commercial & Proprietary Services", "Related Lists", - "Reproducing Works, Training & Books", "Cross-Language Frameworks"} - for lang in languages: - if lang and lang not in skip_langs: - tags.append( - f'' - ) - - # Category tag - category = e.get("category", "") - section_slug = e.get("section_slug", "") - if section_slug: - tags.append( - f'' - ) - - # Source tags (unchanged) - if e.get("github"): - tags.append( - '' - ) - if e.get("cran"): - tags.append( - '' - ) - if e.get("pypi"): - tags.append( - '' - ) - if e.get("commercial"): - tags.append( - '' - ) - - return "\n ".join(tags) -``` - -- [ ] **Step 4: Update data attributes in generate_html()** - -Change `data-language` to `data-languages` (space-separated) in the row template: - -```python -languages_attr = esc(" ".join( - l.strip() for l in e.get("languages", e.get("language", "")).split(",") if l.strip() -)) - -# In the row template: -f"""