mirror of
https://github.com/wilsonfreitas/awesome-quant.git
synced 2026-07-27 20:07:50 +00:00
447 lines
17 KiB
Python
447 lines
17 KiB
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()
|