{"id":50811995,"url":"https://github.com/kiquetal/bons-reader-py","last_synced_at":"2026-06-13T05:33:40.729Z","repository":{"id":352974114,"uuid":"1217217662","full_name":"kiquetal/bons-reader-py","owner":"kiquetal","description":"A Python-based web scraper that extracts the latest bond emissions from the Asunción Stock Exchange (BVA). It automatically sends email alerts for high-yield bonds and supports local data ingestion for AI-powered semantic search.","archived":false,"fork":false,"pushed_at":"2026-04-21T23:03:58.000Z","size":232,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T00:35:23.194Z","etag":null,"topics":["beautifulsoup","bonds","financial-data","github-actions","paraguay","python","requests","scraper"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kiquetal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-21T17:00:19.000Z","updated_at":"2026-04-21T23:04:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kiquetal/bons-reader-py","commit_stats":null,"previous_names":["kiquetal/bons-reader-py"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kiquetal/bons-reader-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiquetal%2Fbons-reader-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiquetal%2Fbons-reader-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiquetal%2Fbons-reader-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiquetal%2Fbons-reader-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kiquetal","download_url":"https://codeload.github.com/kiquetal/bons-reader-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kiquetal%2Fbons-reader-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34273788,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["beautifulsoup","bonds","financial-data","github-actions","paraguay","python","requests","scraper"],"created_at":"2026-06-13T05:33:38.849Z","updated_at":"2026-06-13T05:33:40.723Z","avatar_url":"https://github.com/kiquetal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📊 BVA Emissions Scraper\n\nScrapes the latest bond emissions from [Bolsa de Valores de Asunción](https://www.bolsadevalores.com.py/nuevas-emisiones/) (Paraguay), flattens multi-series emissions into individual entries, and sends email alerts when bonds exceed a configurable interest rate threshold.\n\n## Architecture\n\n![Architecture](docs/architecture.png)\n\nThe scraper fetches the listing page, handles AJAX pagination (JetEngine \"Cargar más\"), visits each detail page, and extracts per-series data into a flat JSON structure.\n\n## Execution Environments\n\n![Environments](docs/environments.png)\n\nRuns locally with optional [MemPalace](https://github.com/MemPalace/mempalace) RAG ingestion, or on GitHub Actions every Tuesday and Thursday.\n\n## Data Model\n\n![Data Model](docs/data-model.png)\n\nEach emission can contain multiple series. The scraper flattens them so every series is its own JSON entry with the parent emission's metadata.\n\n### JSON Output Fields\n\n| Field | Example | Description |\n|---|---|---|\n| `name` | BANCO FAMILIAR S.A.E.C.A. | Issuer name |\n| `instrument` | bono | Instrument type |\n| `qualification` | AApy Estable. | Risk rating |\n| `percentage` | 5,65% | Interest rate |\n| `date` | Jue, abril 23, 2026 | Emission date |\n| `duration` | 732 | Term in days |\n| `isin` | PYFAM03F0006 | ISIN code |\n| `agente_colocador` | Familiar Casa de Bolsa S.A | Placement agent |\n| `series_count` | 3 | Total series in the emission |\n| `url` | https://...banco-familiar.../ | Detail page URL |\n| `scraped_at` | 2026-04-21T16:00:13Z | UTC timestamp of scrape execution |\n\n## Pre-requisites\n\n- **Python 3.9+**\n- **pip** (comes with Python)\n- A [Resend](https://resend.com) account and API key (for email alerts)\n- *(Optional)* [MemPalace](https://github.com/MemPalace/mempalace) installed locally for RAG ingestion\n\n## Setup\n\n```bash\n# 1. Clone the repo\ngit clone \u003crepo-url\u003e \u0026\u0026 cd bons-reader-py\n\n# 2. Create virtual environment and install dependencies\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\n# 3. Configure environment variables\ncp .env.example .env\n# Edit .env with your values:\n#   RESEND_API_KEY=re_xxxxxxxxxxxx\n#   EMAIL_FROM=onboarding@resend.dev\n#   EMAIL_TO=you@example.com\n#   MEMPALACE_BIN=/path/to/mempalace  (optional, for --local mode)\n```\n\n## Usage\n\n```bash\n# Full run: scrape + save JSON + send email\n.venv/bin/python scraper.py\n\n# Scrape only, no email (for testing)\n.venv/bin/python scraper.py --no-email\n\n# Local run with MemPalace ingestion\n.venv/bin/python scraper.py --local\n```\n\nOutput is saved to `data/emisiones.json`.\n\n## Email Alert\n\nThe email includes:\n- **Summary** — total series count and how many exceed the interest threshold (default: 11%)\n- **Highlighted bonds** — listed at the top with ⚠️ warning\n- **Full table** — all series with rows above threshold highlighted in yellow\n\nColumns: Nombre, Instrumento, Calificación, Tasa, Fecha, Plazo, ISIN, Agente Colocador.\n\n## Testing Email Locally\n\n1. Create a free account at [resend.com](https://resend.com) and grab your API key.\n2. Copy and edit the env file:\n   ```bash\n   cp .env.example .env\n   ```\n3. Fill in your values:\n   ```\n   RESEND_API_KEY=re_your_actual_key\n   EMAIL_FROM=onboarding@resend.dev\n   EMAIL_TO=your-email@example.com\n   ```\n   \u003e **Note:** If you haven't verified a custom domain in Resend, use `onboarding@resend.dev` as the sender. In that case, `EMAIL_TO` must match your Resend account email.\n\n4. Run the scraper with email enabled:\n   ```bash\n   .venv/bin/python scraper.py\n   ```\n5. To test scraping without sending email:\n   ```bash\n   .venv/bin/python scraper.py --no-email\n   ```\n\n## MemPalace Integration\n\n[MemPalace](https://github.com/MemPalace/mempalace) is a local-first AI memory system that stores content as verbatim text and retrieves it via semantic search. It organizes data into *wings* (projects), *rooms* (topics), and *drawers* (content) — all indexed locally with no API calls required.\n\nWhen you run the scraper with `--local`, it automatically ingests the scraped emissions into MemPalace so you can later query them with natural language, e.g.:\n\n```bash\n# After running the scraper with --local\nmempalace search \"bonds with high interest rate\"\n```\n\nThe scraper calls `mempalace mine` automatically. If you want to manually ingest an updated `data/emisiones.json`, use:\n\n```bash\n./ingest-mempalace.sh\n```\n\nThis script reads your local `MEMPALACE_BIN` from the `.env` file.\n\n## GitHub Actions\n\nThe workflow runs every Tuesday and Thursday at 12:00 UTC (~8am Paraguay time) and can also be triggered manually.\n\n### Required Secrets\n\nAdd these in your repo → Settings → Secrets and variables → Actions:\n\n| Secret | Description |\n|---|---|\n| `RESEND_API_KEY` | Your Resend API key |\n| `EMAIL_FROM` | Sender email (e.g. `alerts@yourdomain.com`) |\n| `EMAIL_TO` | Recipient(s), comma-separated |\n\n### Manual Trigger\n\nGo to Actions → BVA Emissions Scraper → Run workflow.\n\n## Project Structure\n\n```\n.\n├── scraper.py                          # Main script\n├── requirements.txt                    # Python dependencies\n├── .env.example                        # Environment variables template\n├── AGENTS.md                           # LLM/MemPalace context file\n├── .gitignore\n├── data/\n│   ├── .gitkeep\n│   └── emisiones.json                  # Output (git-ignored)\n├── docs/\n│   ├── architecture.png\n│   ├── environments.png\n│   └── data-model.png\n├── .github/workflows/bva-scraper.yml   # GitHub Actions workflow\n├── PLAN.md                             # Implementation plan\n└── README.md\n```\n\n## How It Works\n\n1. **Fetch listing page** — GET `/nuevas-emisiones/`, extract detail URLs from `data-url` attributes\n2. **AJAX pagination** — POST to `admin-ajax.php` with JetEngine params and signature to load more entries beyond the initial 12\n3. **Parse detail pages** — For each emission, extract name, instrument, qualification, date, and per-series: ISIN, interest rate, duration, placement agent\n4. **Flatten** — Each series becomes its own JSON entry with parent emission metadata\n5. **Save** — Write to `data/emisiones.json`\n6. **Email** — Send via Resend with full table and highlighted rows for bonds \u003e 11%\n7. **MemPalace** *(local only)* — Ingest `data/` into mempalace for RAG search\n\n## Financial Formulas\n\nFor academic reference, here are the formulas used to understand bond returns and interest accumulation.\n\n### 1. Compound Interest\nUsed to calculate the total amount of an investment over time when interest is reinvested. In Paraguay, entities like **Cadiem** and **Investor** offer products like *Fondo Mutuo* that utilize compound interest by reinvesting daily yields.\n\n$$A = P \\left(1 + \\frac{r}{n}\\right)^{nt}$$\n\n- **A**: Final amount (Principal + Interest)\n- **P**: Principal (Initial investment)\n- **r**: Annual interest rate (decimal)\n- **n**: Number of times interest is compounded per year\n- **t**: Number of years\n\n### 2. Bond Interest (Coupon)\nThe periodic interest payment a bondholder receives from the bond's issuance date until it matures. Unlike compound interest, bond coupons are typically paid out (not reinvested automatically).\n\n**Practical Example:**\n- **Issuer:** TAPE RUVICHA S.A.E.C.A.\n- **Principal:** 30,000,000 PYG (Approx. **$4,000 USD** at 7,500 PYG/USD)\n- **Annual Rate:** 12.35%\n\n$$C = \\$4,000 \\times 0.1235 = \\$494 \\text{ per year}$$\n\n| Payment Period | Interest (USD) | Interest (PYG) |\n| :--- | :--- | :--- |\n| Quarterly | $123.50 | 926,250 PYG |\n| Semi-Annual | $247.00 | 1,852,500 PYG |\n| **Annual Total** | **$494.00** | **3,705,000 PYG** |\n\n### 🥊 The 5-Year Showdown: Compound vs. Bond\nWhat happens if you hold both for 5 years with a **$4,000** initial capital?\n\n| Investment Type | Annual Rate | Total Interest (5yr) | Final Value |\n| :--- | :--- | :--- | :--- |\n| **Fondo Mutuo** (Compound) | 9.00% | $2,154.48 | $6,154.48 |\n| **Tape Ruvicha Bond** (Simple) | 12.35% | $2,470.00 | $6,470.00 |\n| **Delta (Difference)** | **+3.35%** | **$315.52** | **Bond wins!** |\n\n*Note: In the long run (15+ years), the power of compounding usually overtakes higher simple interest rates, but for shorter terms, the raw yield of high-rate bonds is often superior.*\n\n$$C = F \\times c$$\n\n- **C**: Coupon payment amount\n- **F**: Face value (Par value) of the bond\n- **c**: Coupon rate (Annual interest rate)\n\n### Visual Representation\n\n![Compound Interest Growth ($1,000 at 9% Annual)](./docs/compound-interest-9pct-chart.png)\n\n| Year | Interest (9%) | Total Capital |\n| :--- | :--- | :--- |\n| 1 | $90.00 | $1,090.00 |\n| 2 | $98.10 | $1,188.10 |\n| 3 | $106.93 | $1,295.03 |\n| 4 | $116.55 | $1,411.58 |\n| 5 | $127.04 | $1,538.62 |\n| 6 | $138.48 | $1,677.10 |\n| 7 | $150.94 | $1,828.04 |\n| 8 | $164.52 | $1,992.56 |\n| 9 | $179.33 | $2,171.89 |\n| 10 | $195.47 | $2,367.36 |\n\n## License\n\nMade in 2026\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkiquetal%2Fbons-reader-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkiquetal%2Fbons-reader-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkiquetal%2Fbons-reader-py/lists"}