# How to Schedule Daily Stock Analysis Tasks Using Python and the Schedule Library

> Schedule daily stock analysis tasks with Python and the schedule library. This lightweight scheduler offers configurable jobs, immediate execution, and dynamic reloads for efficient workflows.

- Repository: [mumu/daily_stock_analysis](https://github.com/ZhuLinsen/daily_stock_analysis)
- Tags: how-to-guide
- Published: 2026-04-30

---

**The ZhuLinsen/daily_stock_analysis repository implements a lightweight single-process scheduler built on the `schedule` library that registers daily jobs at configurable times, supports immediate execution on startup, and dynamically reloads schedule settings without requiring a process restart.**

The daily-stock-analysis system provides a flexible framework for automating equity analysis workflows without external cron jobs or complex orchestration. To schedule daily stock analysis tasks efficiently, the project uses a Python-native scheduling mechanism that handles daily pipeline execution, background task management, and graceful shutdown handling through a cohesive set of modules.

## Execution Modes Overview

The system supports three distinct execution modes controlled via CLI flags and configuration settings in [`main.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/main.py):

- **One-shot run**: Execute the analysis pipeline immediately and exit. Triggered by running `python main.py` without the `--schedule` flag when `config.run_immediately` is enabled.
- **Scheduled run**: Continuously run the scheduler loop, executing the daily task at the configured time. Activated by the `--schedule` CLI flag or setting `config.schedule_enabled=True` (lines 883-883).
- **Web-UI/API only**: Serve the web interface without executing analysis tasks. Triggered by `--serve` or `--serve-only` flags (lines 678-698).

## How the Scheduler Works

When scheduled mode is activated, [`main.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/main.py) instantiates a `Scheduler` from **[`src/scheduler.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/scheduler.py)** and configures it with the analysis pipeline task and optional background workers.

### Daily Task Registration

The scheduler registers the primary analysis task using `_configure_daily_task()` (lines 30-45), which validates the configured time against a regex pattern and registers the job via:

```python
schedule.every().day.at(schedule_time).do(daily_task)

```

By default, the system executes at **18:00** (6:00 PM), though this is configurable via the `SCHEDULE_TIME` environment variable.

### Immediate Execution on Startup

The `schedule_run_immediately` setting (default `True`) controls whether the daily task runs once immediately upon scheduler startup. When enabled, `Scheduler.set_daily_task()` calls `_safe_run_task()` (lines 92-107) before entering the main loop. Disable this behavior using the `--no-run-immediately` CLI flag, which overrides the configuration setting.

### Dynamic Schedule Time Updates

Unlike static cron jobs, the scheduler supports runtime schedule modifications. The `_refresh_daily_schedule_if_needed()` method (lines 56-71) checks a schedule-time provider function before each loop iteration. This provider reads the latest `SCHEDULE_TIME` from the persisted `.env` file or process-level overrides, allowing Web-UI edits to take effect instantly without restarting the process.

### Main Loop and Background Tasks

The `Scheduler.run()` method (lines 73-88) executes three operations on each iteration:

1. Refreshes the daily schedule time via the provider.
2. Executes pending daily jobs via `self.schedule.run_pending()`.
3. Launches elapsed background tasks via `_run_background_tasks()`.

The loop sleeps for **30 seconds** between iterations, which caps the minimum background task interval at 30 seconds (clamped in `add_background_task()`, lines 22-27).

## Configuring the Schedule

Configuration fields are defined in **[`src/config.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/config.py)** (line 817):

```python
schedule_enabled: bool = False            # Enable daily scheduled execution

schedule_time: str = "18:00"              # Daily execution time (HH:MM)

schedule_run_immediately: bool = True     # Run once immediately on startup

```

These values can be overridden in the `.env` file at the project root:

```dotenv

# .env configuration

SCHEDULE_ENABLED=true
SCHEDULE_TIME=07:30
SCHEDULE_RUN_IMMEDIATELY=false

```

The `ConfigManager.read_config_map()` method in [`src/core/config_manager.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/core/config_manager.py) provides the runtime configuration reloading capability used by the schedule-time provider.

## Command-Line Interface

Start the scheduler using the `--schedule` flag to force scheduled mode regardless of `.env` settings:

```bash

# Force scheduled mode via CLI

python main.py --schedule

# Disable immediate execution on startup

python main.py --schedule --no-run-immediately

```

If `SCHEDULE_ENABLED` is set to `true` in `.env`, running `python main.py` without flags will automatically start the scheduler.

## Implementing Custom Background Tasks

The scheduler supports concurrent background tasks running in daemon threads. To add a custom task, define a function and pass it to `run_with_schedule()` before initialization:

```python

# custom_tasks.py

import logging
import datetime

def health_monitor():
    logging.info(f"[HealthCheck] {datetime.datetime.now()} – System operational")

```

```python

# In main.py, before calling run_with_schedule()

from custom_tasks import health_monitor

background_tasks = [
    {
        "task": health_monitor,
        "interval_seconds": 300,          # 5 minutes

        "run_immediately": True,
        "name": "health_monitor",
    },
]

run_with_schedule(
    task=scheduled_task,
    schedule_time=config.schedule_time,
    run_immediately=should_run_immediately,
    background_tasks=background_tasks,
    schedule_time_provider=schedule_time_provider,
)

```

Background tasks automatically start in daemon threads and respect the 30-second minimum interval constraint enforced by `add_background_task()`.

## Key Implementation Files

| File | Purpose |
|------|---------|
| **[`src/scheduler.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/scheduler.py)** | Core `Scheduler` class, daily task registration, background task handling, and graceful shutdown logic. |
| **[`main.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/main.py)** | CLI entry point that parses `--schedule` and `--no-run-immediately` flags, builds the scheduler, and wires configuration providers. |
| **[`src/config.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/config.py)** | Defines `schedule_enabled`, `schedule_time`, and `schedule_run_immediately` configuration fields. |
| **[`src/core/config_manager.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/core/config_manager.py)** | Provides `ConfigManager.read_config_map()` for runtime configuration reloading. |

## Summary

- **The scheduler** uses the lightweight `schedule` library to run daily stock analysis at configurable times without external cron dependencies.
- **Three execution modes** support one-shot runs, continuous scheduled execution, and web-only serving via CLI flags in [`main.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/main.py).
- **Runtime configurability** allows changing the schedule time via `.env` edits without process restarts, implemented through the `_build_schedule_time_provider()` mechanism.
- **Immediate execution** can be enabled or disabled via `schedule_run_immediately` or the `--no-run-immediately` CLI flag.
- **Background tasks** run concurrently in daemon threads with a minimum 30-second interval, managed by `_run_background_tasks()`.

## Frequently Asked Questions

### How do I change the daily execution time without restarting the scheduler?

Edit the `SCHEDULE_TIME` value in your `.env` file (e.g., `SCHEDULE_TIME=09:30`). The `_refresh_daily_schedule_if_needed()` method in [`src/scheduler.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/scheduler.py) checks the schedule-time provider on each loop iteration and updates the job automatically. This design allows Web-UI changes to take effect immediately without interrupting the running process.

### What is the minimum interval for background tasks?

The minimum interval is **30 seconds**, enforced by the `add_background_task()` method in [`src/scheduler.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/scheduler.py) (lines 22-27). The scheduler's main loop sleeps for 30 seconds between iterations, which dictates this limitation. If you specify a smaller interval, the code clamps it to 30 seconds automatically.

### How does the system handle graceful shutdown?

The `GracefulShutdown` class in [`src/scheduler.py`](https://github.com/ZhuLinsen/daily_stock_analysis/blob/main/src/scheduler.py) (lines 27-54) registers signal handlers for `SIGINT` and `SIGTERM`. When triggered, these set an internal flag that causes the `Scheduler.run()` loop to exit cleanly after completing the current iteration, ensuring that running analysis tasks or background jobs are not forcibly terminated mid-execution.

### Can I run the analysis immediately without waiting for the scheduled time?

Yes. Set `SCHEDULE_RUN_IMMEDIATELY=true` in your `.env` file or omit the `--no-run-immediately` flag when starting with `--schedule`. This triggers `_safe_run_task()` during scheduler initialization, executing the full analysis pipeline once before entering the waiting loop for the next scheduled time.