mech.app
Financial

Daily Stock Analysis: LLM Orchestration on GitHub Actions for Multi-Market Data

How a zero-cost scheduled agent system coordinates A/H/US stock APIs, news feeds, and multi-channel push delivery using GitHub Actions as runtime.

Source: github.com
Daily Stock Analysis: LLM Orchestration on GitHub Actions for Multi-Market Data

A scheduled LLM agent that analyzes stocks across three markets, aggregates real-time news, and pushes decision dashboards to six messaging platforms sounds like a cloud bill waiting to happen. Daily Stock Analysis runs the entire pipeline on GitHub Actions for free, treating CI/CD infrastructure as an agent runtime. With 37,000 stars and trending #7 in Python, it demonstrates practical orchestration for financial data without Kubernetes, queues, or persistent compute.

The architecture exposes three plumbing concerns: coordinating multiple data sources into a single LLM decision pipeline, managing execution constraints when GitHub Actions is your scheduler, and handling multi-channel delivery with retry logic.

Orchestration Flow

The system runs as a scheduled GitHub Actions workflow. Each execution follows a linear pipeline:

  1. Data collection phase: Parallel API calls to market data providers (A-stock, H-stock, US equities), news aggregators, and quantitative indicators.
  2. LLM decision phase: Aggregated data feeds into a prompt template, LLM generates structured analysis with scores, buy/sell signals, and risk warnings.
  3. Delivery phase: Formatted report pushes to configured channels (WeChat Work, Feishu, Telegram, Discord, Slack, email).

State lives in two places: GitHub Actions secrets for API keys and webhook URLs, and the repository itself for configuration files. No external database. Each run is stateless except for optional persistence of reports as workflow artifacts.

Data Source Coordination

The agent coordinates multiple data sources without race conditions by running collection tasks sequentially within a single workflow job. Python’s asyncio handles concurrent API calls, but the workflow itself is single-threaded.

async def collect_market_data(symbols: list[str]) -> dict:
    tasks = [
        fetch_a_stock_data(symbols),
        fetch_h_stock_data(symbols),
        fetch_us_stock_data(symbols),
        fetch_news_feed(symbols)
    ]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    # Merge results, handle partial failures
    merged = {}
    for result in results:
        if isinstance(result, Exception):
            log_error(result)
            continue
        merged.update(result)
    
    return merged

Partial failures are logged but don’t halt the pipeline. If one market API times out, the LLM still receives data from other sources. This trades completeness for availability: better to generate a report with 80% of the data than fail entirely.

The system does not implement distributed locking or coordination primitives. GitHub Actions guarantees only one instance of a scheduled workflow runs at a time per repository, so concurrent execution is not a concern. Manual triggers can queue multiple runs, but each executes in isolation.

GitHub Actions as Agent Runtime

Using GitHub Actions as a scheduled agent runtime introduces specific constraints:

ConstraintLimitMitigation
Execution time6 hours per jobPipeline completes in 5-10 minutes; timeout is not binding
API rate limits1,000 requests/hour (authenticated)Batch API calls, cache static data in repository
Concurrent jobs20 (free tier)Single job per workflow, no parallelism needed
Storage500 MB artifacts per runReports are small (< 1 MB), artifacts expire after 90 days
Secrets100 per repositoryOne secret per channel webhook, well under limit

The 6-hour timeout is the most visible constraint, but the real failure mode is API rate limits. If the workflow makes 50 API calls per run and runs every hour, you hit 1,200 calls per day. GitHub’s rate limit resets hourly, so bursts are fine, but sustained high-frequency execution requires caching.

The system caches static reference data (stock symbols, sector mappings) in the repository. Dynamic data (prices, news) is fetched fresh each run. This keeps API calls under 30 per execution.

State persistence is optional. The workflow can commit generated reports back to the repository or upload them as artifacts. Most deployments skip persistence and rely on push notifications as the primary output.

Multi-Channel Delivery

The agent supports six delivery channels. Each channel has a webhook URL stored in GitHub Actions secrets. The delivery phase iterates through configured channels and posts the formatted report.

async def deliver_report(report: str, channels: dict):
    results = {}
    for channel_name, config in channels.items():
        try:
            if channel_name == "wechat":
                await send_wechat_work(config["webhook"], report)
            elif channel_name == "feishu":
                await send_feishu(config["webhook"], report)
            elif channel_name == "telegram":
                await send_telegram(config["bot_token"], config["chat_id"], report)
            # ... other channels
            results[channel_name] = "success"
        except Exception as e:
            results[channel_name] = f"failed: {e}"
            # No retry, log and continue
    
    return results

Retry logic is minimal. If a webhook call fails, the error is logged and the workflow continues to the next channel. No exponential backoff, no dead-letter queue. This design prioritizes workflow completion over guaranteed delivery.

The trade-off: if Slack is down, that channel misses the report. The workflow does not re-run. Users who need guaranteed delivery should add external retry logic or use a message queue, but that defeats the zero-cost goal.

Formatting varies by channel. Markdown for Telegram and Discord, plain text for email, structured JSON for WeChat Work and Feishu. The report generator produces a base Markdown document, and each channel adapter transforms it.

LLM Decision Pipeline

The LLM receives a structured prompt with:

  • Market data (open, close, high, low, volume) for each symbol
  • Recent news headlines and summaries
  • Technical indicators (moving averages, RSI, MACD)
  • Sector performance context

The prompt template asks for:

  1. A 1-5 score for each stock
  2. Buy/sell/hold recommendation
  3. Key catalysts (earnings, news events)
  4. Risk warnings (volatility, sector headwinds)
  5. An action checklist (set stop-loss, monitor resistance levels)

The LLM output is parsed as structured JSON. If parsing fails, the workflow falls back to plain text and logs a warning. No validation beyond JSON schema. The system does not verify that recommendations are rational or consistent.

This is a deliberate choice. The agent is a decision support tool, not an automated trader. Users review the report before acting. Strict validation would add complexity without meaningful safety gains.

Failure Modes

The most common failure is API rate limiting. If a data source returns 429, the workflow logs the error and continues with partial data. The LLM report includes a disclaimer listing unavailable sources.

The second failure mode is LLM timeouts. If the model takes longer than 60 seconds to respond, the request is canceled and the workflow retries once. After two failures, the workflow exits with an error code. GitHub Actions sends a notification email to the repository owner.

The third failure mode is webhook delivery errors. These are logged but do not fail the workflow. Users discover missing reports when they check their messaging app and see no update.

The fourth failure mode is GitHub Actions downtime. If the platform is unavailable during a scheduled run, the workflow is skipped entirely. No automatic retry. Users can manually trigger the workflow after the outage.

Security Boundaries

API keys and webhook URLs live in GitHub Actions secrets. The workflow reads them at runtime but never logs or exposes them in artifacts. This is standard practice, but the risk is accidental leakage through debug logs or error messages.

The system does not implement secret rotation. If a webhook URL is compromised, the user must manually update the secret in GitHub settings. No automatic detection of unauthorized access.

The LLM prompt includes user-provided stock symbols. If a user adds a malicious symbol (e.g., a string designed to manipulate the LLM output), the prompt is not sanitized. The LLM could generate misleading recommendations. This is a known risk. The mitigation is user education: only add symbols you trust.

The workflow runs in a sandboxed GitHub Actions environment. It cannot access other repositories or secrets. The attack surface is limited to the data sources and LLM API.

Deployment Shape

Deployment is a fork and configure workflow:

  1. Fork the repository
  2. Add GitHub Actions secrets for API keys and webhook URLs
  3. Edit the configuration file to specify stock symbols and channels
  4. Enable GitHub Actions in the repository settings
  5. The workflow runs on the default schedule (daily at market close)

No infrastructure provisioning. No Docker images to build. No cloud accounts to create. The entire system runs on GitHub’s free tier.

The configuration file is a YAML document:

symbols:
  a_stock: ["000001", "600519"]
  h_stock: ["00700", "09988"]
  us_stock: ["AAPL", "TSLA"]

channels:
  telegram:
    enabled: true
    bot_token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
    chat_id: ${{ secrets.TELEGRAM_CHAT_ID }}
  
  wechat:
    enabled: false

schedule: "0 21 * * 1-5"  # 9 PM UTC, weekdays

The schedule uses cron syntax. Users can adjust frequency, but running more than once per hour risks rate limits.

Observability

The workflow logs each step: data collection, LLM invocation, delivery results. Logs are visible in the GitHub Actions UI. No external logging service. No metrics or traces.

If a run fails, GitHub sends an email notification. Users can view the workflow run in the Actions tab to see which step failed and why.

The system does not track historical performance. Each run is independent. If you want to compare today’s recommendations to yesterday’s, you must manually save reports or enable artifact persistence.

Technical Verdict

Use Daily Stock Analysis if you are a solo investor or small research team tracking 10-50 symbols across multiple markets, you check your messaging app at least once per day, and you can tolerate 1-2 missed reports per month due to API failures or webhook downtime. It works well for personal portfolio tracking, weekly research synthesis, and educational projects exploring LLM-driven financial analysis. The zero-cost infrastructure makes it ideal for side projects and learning exercises where budget constraints matter more than uptime guarantees.

Use it if you want to experiment with scheduled LLM agents without provisioning cloud resources, you are comfortable reading GitHub Actions logs to debug failures, and you do not need historical report storage beyond 90 days.

Avoid it if you need sub-hourly updates, guaranteed delivery SLAs, or integration with live trading systems that execute automatically based on agent output. The lack of retry logic, state persistence, and real-time alerting makes it unsuitable for production financial services, algorithmic trading platforms, or high-frequency monitoring workflows. Avoid it if you require audit trails, compliance reporting, or multi-user access controls. If your use case demands those features, run the pipeline on a dedicated server with a message queue, database, and proper observability stack.

Avoid it if you are building a commercial product where users expect 99.9% uptime or if you need to process more than 100 symbols per run (API rate limits become binding). The architecture is a useful reference for anyone building scheduled agents on GitHub Actions, but it is not a replacement for production-grade infrastructure.


Tags

agentic-ai orchestration infrastructure github-actions financial-data

Primary Source

github.com