Daily Stock Analysis #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Daily Stock Analysis | |
| on: | |
| schedule: | |
| # 18:00 Asia/Shanghai, Monday-Friday. | |
| - cron: "0 10 * * 1-5" | |
| workflow_dispatch: | |
| inputs: | |
| stocks: | |
| description: "Optional comma-separated stock list, e.g. 600519,hk00700,AAPL" | |
| required: false | |
| default: "" | |
| force_run: | |
| description: "Run even when the trading-day check would skip" | |
| required: false | |
| type: boolean | |
| default: false | |
| dry_run: | |
| description: "Fetch data without running LLM analysis" | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: daily-analysis-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| analyze: | |
| name: Run daily analysis | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| env: | |
| CI: "true" | |
| GITHUB_ACTIONS: "true" | |
| PYTHONUNBUFFERED: "1" | |
| STOCK_LIST: ${{ vars.STOCK_LIST || secrets.STOCK_LIST }} | |
| TRADING_DAY_CHECK_ENABLED: ${{ vars.TRADING_DAY_CHECK_ENABLED || secrets.TRADING_DAY_CHECK_ENABLED || 'true' }} | |
| ENABLE_CHIP_DISTRIBUTION: ${{ vars.ENABLE_CHIP_DISTRIBUTION || secrets.ENABLE_CHIP_DISTRIBUTION || 'false' }} | |
| LITELLM_MODEL: ${{ vars.LITELLM_MODEL || secrets.LITELLM_MODEL }} | |
| AGENT_LITELLM_MODEL: ${{ vars.AGENT_LITELLM_MODEL || secrets.AGENT_LITELLM_MODEL }} | |
| LITELLM_FALLBACK_MODELS: ${{ vars.LITELLM_FALLBACK_MODELS || secrets.LITELLM_FALLBACK_MODELS }} | |
| LLM_CHANNELS: ${{ vars.LLM_CHANNELS || secrets.LLM_CHANNELS }} | |
| VISION_MODEL: ${{ vars.VISION_MODEL || secrets.VISION_MODEL }} | |
| GEMINI_API_KEYS: ${{ secrets.GEMINI_API_KEYS || vars.GEMINI_API_KEYS }} | |
| GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY || vars.GEMINI_API_KEY }} | |
| GEMINI_MODEL: ${{ vars.GEMINI_MODEL || secrets.GEMINI_MODEL }} | |
| GEMINI_MODEL_FALLBACK: ${{ vars.GEMINI_MODEL_FALLBACK || secrets.GEMINI_MODEL_FALLBACK }} | |
| OPENAI_API_KEYS: ${{ secrets.OPENAI_API_KEYS || vars.OPENAI_API_KEYS }} | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY || vars.OPENAI_API_KEY }} | |
| OPENAI_BASE_URL: ${{ vars.OPENAI_BASE_URL || secrets.OPENAI_BASE_URL }} | |
| OPENAI_MODEL: ${{ vars.OPENAI_MODEL || secrets.OPENAI_MODEL }} | |
| AIHUBMIX_KEY: ${{ secrets.AIHUBMIX_KEY || vars.AIHUBMIX_KEY }} | |
| ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY || vars.ANTHROPIC_API_KEY }} | |
| ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL || secrets.ANTHROPIC_MODEL }} | |
| DEEPSEEK_API_KEYS: ${{ secrets.DEEPSEEK_API_KEYS || vars.DEEPSEEK_API_KEYS }} | |
| DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY || vars.DEEPSEEK_API_KEY }} | |
| TAVILY_API_KEYS: ${{ secrets.TAVILY_API_KEYS || vars.TAVILY_API_KEYS }} | |
| ANSPIRE_API_KEYS: ${{ secrets.ANSPIRE_API_KEYS || vars.ANSPIRE_API_KEYS }} | |
| MINIMAX_API_KEYS: ${{ secrets.MINIMAX_API_KEYS || vars.MINIMAX_API_KEYS }} | |
| BOCHA_API_KEYS: ${{ secrets.BOCHA_API_KEYS || vars.BOCHA_API_KEYS }} | |
| BRAVE_API_KEYS: ${{ secrets.BRAVE_API_KEYS || vars.BRAVE_API_KEYS }} | |
| SERPAPI_API_KEYS: ${{ secrets.SERPAPI_API_KEYS || vars.SERPAPI_API_KEYS }} | |
| SEARXNG_BASE_URLS: ${{ vars.SEARXNG_BASE_URLS || secrets.SEARXNG_BASE_URLS }} | |
| SEARXNG_PUBLIC_INSTANCES_ENABLED: ${{ vars.SEARXNG_PUBLIC_INSTANCES_ENABLED || secrets.SEARXNG_PUBLIC_INSTANCES_ENABLED }} | |
| TUSHARE_TOKEN: ${{ secrets.TUSHARE_TOKEN || vars.TUSHARE_TOKEN }} | |
| LONGBRIDGE_APP_KEY: ${{ secrets.LONGBRIDGE_APP_KEY || vars.LONGBRIDGE_APP_KEY }} | |
| LONGBRIDGE_APP_SECRET: ${{ secrets.LONGBRIDGE_APP_SECRET || vars.LONGBRIDGE_APP_SECRET }} | |
| LONGBRIDGE_ACCESS_TOKEN: ${{ secrets.LONGBRIDGE_ACCESS_TOKEN || vars.LONGBRIDGE_ACCESS_TOKEN }} | |
| LONGBRIDGE_STATIC_INFO_TTL_SECONDS: ${{ vars.LONGBRIDGE_STATIC_INFO_TTL_SECONDS || secrets.LONGBRIDGE_STATIC_INFO_TTL_SECONDS }} | |
| LONGBRIDGE_HTTP_URL: ${{ vars.LONGBRIDGE_HTTP_URL || secrets.LONGBRIDGE_HTTP_URL }} | |
| LONGBRIDGE_QUOTE_WS_URL: ${{ vars.LONGBRIDGE_QUOTE_WS_URL || secrets.LONGBRIDGE_QUOTE_WS_URL }} | |
| LONGBRIDGE_TRADE_WS_URL: ${{ vars.LONGBRIDGE_TRADE_WS_URL || secrets.LONGBRIDGE_TRADE_WS_URL }} | |
| LONGBRIDGE_REGION: ${{ vars.LONGBRIDGE_REGION || secrets.LONGBRIDGE_REGION }} | |
| LONGBRIDGE_ENABLE_OVERNIGHT: ${{ vars.LONGBRIDGE_ENABLE_OVERNIGHT || secrets.LONGBRIDGE_ENABLE_OVERNIGHT }} | |
| LONGBRIDGE_PUSH_CANDLESTICK_MODE: ${{ vars.LONGBRIDGE_PUSH_CANDLESTICK_MODE || secrets.LONGBRIDGE_PUSH_CANDLESTICK_MODE }} | |
| LONGBRIDGE_PRINT_QUOTE_PACKAGES: ${{ vars.LONGBRIDGE_PRINT_QUOTE_PACKAGES || secrets.LONGBRIDGE_PRINT_QUOTE_PACKAGES }} | |
| MARKET_REVIEW_ENABLED: ${{ vars.MARKET_REVIEW_ENABLED || secrets.MARKET_REVIEW_ENABLED }} | |
| MARKET_REVIEW_REGION: ${{ vars.MARKET_REVIEW_REGION || secrets.MARKET_REVIEW_REGION }} | |
| SINGLE_STOCK_NOTIFY: ${{ vars.SINGLE_STOCK_NOTIFY || secrets.SINGLE_STOCK_NOTIFY }} | |
| REPORT_TYPE: ${{ vars.REPORT_TYPE || secrets.REPORT_TYPE }} | |
| REPORT_LANGUAGE: ${{ vars.REPORT_LANGUAGE || secrets.REPORT_LANGUAGE }} | |
| REPORT_SUMMARY_ONLY: ${{ vars.REPORT_SUMMARY_ONLY || secrets.REPORT_SUMMARY_ONLY }} | |
| REPORT_RENDERER_ENABLED: ${{ vars.REPORT_RENDERER_ENABLED || secrets.REPORT_RENDERER_ENABLED }} | |
| REPORT_INTEGRITY_ENABLED: ${{ vars.REPORT_INTEGRITY_ENABLED || secrets.REPORT_INTEGRITY_ENABLED }} | |
| REPORT_INTEGRITY_RETRY: ${{ vars.REPORT_INTEGRITY_RETRY || secrets.REPORT_INTEGRITY_RETRY }} | |
| REPORT_HISTORY_COMPARE_N: ${{ vars.REPORT_HISTORY_COMPARE_N || secrets.REPORT_HISTORY_COMPARE_N }} | |
| ANALYSIS_DELAY: ${{ vars.ANALYSIS_DELAY || secrets.ANALYSIS_DELAY }} | |
| MERGE_EMAIL_NOTIFICATION: ${{ vars.MERGE_EMAIL_NOTIFICATION || secrets.MERGE_EMAIL_NOTIFICATION }} | |
| MARKDOWN_TO_IMAGE_CHANNELS: ${{ vars.MARKDOWN_TO_IMAGE_CHANNELS || secrets.MARKDOWN_TO_IMAGE_CHANNELS }} | |
| MARKDOWN_TO_IMAGE_MAX_CHARS: ${{ vars.MARKDOWN_TO_IMAGE_MAX_CHARS || secrets.MARKDOWN_TO_IMAGE_MAX_CHARS }} | |
| MD2IMG_ENGINE: ${{ vars.MD2IMG_ENGINE || secrets.MD2IMG_ENGINE }} | |
| PREFETCH_REALTIME_QUOTES: ${{ vars.PREFETCH_REALTIME_QUOTES || secrets.PREFETCH_REALTIME_QUOTES }} | |
| WECHAT_WEBHOOK_URL: ${{ secrets.WECHAT_WEBHOOK_URL || vars.WECHAT_WEBHOOK_URL }} | |
| WECHAT_MSG_TYPE: ${{ vars.WECHAT_MSG_TYPE || secrets.WECHAT_MSG_TYPE }} | |
| FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL || vars.FEISHU_WEBHOOK_URL }} | |
| FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET || vars.FEISHU_WEBHOOK_SECRET }} | |
| FEISHU_WEBHOOK_KEYWORD: ${{ vars.FEISHU_WEBHOOK_KEYWORD || secrets.FEISHU_WEBHOOK_KEYWORD }} | |
| FEISHU_APP_ID: ${{ secrets.FEISHU_APP_ID || vars.FEISHU_APP_ID }} | |
| FEISHU_APP_SECRET: ${{ secrets.FEISHU_APP_SECRET || vars.FEISHU_APP_SECRET }} | |
| FEISHU_FOLDER_TOKEN: ${{ secrets.FEISHU_FOLDER_TOKEN || vars.FEISHU_FOLDER_TOKEN }} | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN || vars.TELEGRAM_BOT_TOKEN }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID || vars.TELEGRAM_CHAT_ID }} | |
| TELEGRAM_MESSAGE_THREAD_ID: ${{ vars.TELEGRAM_MESSAGE_THREAD_ID || secrets.TELEGRAM_MESSAGE_THREAD_ID }} | |
| DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL || vars.DISCORD_WEBHOOK_URL }} | |
| DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN || vars.DISCORD_BOT_TOKEN }} | |
| DISCORD_MAIN_CHANNEL_ID: ${{ secrets.DISCORD_MAIN_CHANNEL_ID || vars.DISCORD_MAIN_CHANNEL_ID }} | |
| DISCORD_CHANNEL_ID: ${{ secrets.DISCORD_CHANNEL_ID || vars.DISCORD_CHANNEL_ID }} | |
| SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL || vars.SLACK_WEBHOOK_URL }} | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN || vars.SLACK_BOT_TOKEN }} | |
| SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID || vars.SLACK_CHANNEL_ID }} | |
| EMAIL_SENDER: ${{ secrets.EMAIL_SENDER || vars.EMAIL_SENDER }} | |
| EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD || vars.EMAIL_PASSWORD }} | |
| EMAIL_RECEIVERS: ${{ secrets.EMAIL_RECEIVERS || vars.EMAIL_RECEIVERS }} | |
| EMAIL_SENDER_NAME: ${{ vars.EMAIL_SENDER_NAME || secrets.EMAIL_SENDER_NAME }} | |
| PUSHOVER_USER_KEY: ${{ secrets.PUSHOVER_USER_KEY || vars.PUSHOVER_USER_KEY }} | |
| PUSHOVER_API_TOKEN: ${{ secrets.PUSHOVER_API_TOKEN || vars.PUSHOVER_API_TOKEN }} | |
| PUSHPLUS_TOKEN: ${{ secrets.PUSHPLUS_TOKEN || vars.PUSHPLUS_TOKEN }} | |
| PUSHPLUS_TOPIC: ${{ vars.PUSHPLUS_TOPIC || secrets.PUSHPLUS_TOPIC }} | |
| SERVERCHAN3_SENDKEY: ${{ secrets.SERVERCHAN3_SENDKEY || vars.SERVERCHAN3_SENDKEY }} | |
| CUSTOM_WEBHOOK_URLS: ${{ secrets.CUSTOM_WEBHOOK_URLS || vars.CUSTOM_WEBHOOK_URLS }} | |
| CUSTOM_WEBHOOK_BEARER_TOKEN: ${{ secrets.CUSTOM_WEBHOOK_BEARER_TOKEN || vars.CUSTOM_WEBHOOK_BEARER_TOKEN }} | |
| WEBHOOK_VERIFY_SSL: ${{ vars.WEBHOOK_VERIFY_SSL || secrets.WEBHOOK_VERIFY_SSL }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| cache: "pip" | |
| cache-dependency-path: | | |
| requirements.txt | |
| requirements-ci.txt | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -r requirements-ci.txt | |
| - name: Run analysis | |
| env: | |
| MANUAL_STOCKS: ${{ github.event.inputs.stocks || '' }} | |
| FORCE_RUN: ${{ github.event.inputs.force_run || 'false' }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} | |
| run: | | |
| python - <<'PY' | |
| import os | |
| import subprocess | |
| import sys | |
| args = [sys.executable, "main.py"] | |
| stocks = (os.environ.get("MANUAL_STOCKS") or "").strip() | |
| if stocks: | |
| args.extend(["--stocks", stocks]) | |
| if (os.environ.get("FORCE_RUN") or "").lower() == "true": | |
| args.append("--force-run") | |
| if (os.environ.get("DRY_RUN") or "").lower() == "true": | |
| args.append("--dry-run") | |
| raise SystemExit(subprocess.call(args)) | |
| PY | |
| - name: Upload logs and reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: analysis-reports | |
| path: | | |
| logs/ | |
| reports/ | |
| if-no-files-found: ignore |