Iterative SDD — Each phase builds on the previous one. Implement in order.
- Project Overview
- Phase 1: Basic Stock Price CLI
- Phase 2: SQLite Historical Database (
--his_insert) - Phase 3: Single Ticker Query (
--ticker) - Phase 4: Moving Average Breakout Detection (
--avg_break) - Risks & Notes
Goal: Build a Python CLI tool named stockcheck.py that fetches live stock prices from Google Finance, with optional support for a local SQLite historical database and basic technical indicator analysis.
Constraints:
- Free public data sources only (Google Finance scraping, yfinance)
- No paid APIs
- Dependencies limited to:
requests,beautifulsoup4,yfinance,sqlite3(stdlib)
The diagram below shows the full decision path from CLI argument parsing through to final output, covering all four phases.
flowchart TD
A[Start stockcheck.py] --> B{Parse CLI arguments}
B -->|--ticker specified| C[Use single ticker]
B -->|filename specified| D[Read stocklist file]
D --> E[Iterate over each ticker]
C --> F{--avg_break?}
E --> F
F -->|Yes| G[Load history from SQLite]
G --> H[Compute moving averages]
H --> I{Live price > any MA?}
I -->|Yes| J[Print ticker name]
I -->|No| K[Skip]
F -->|No| L{--output csv?}
L -->|Yes| M[Write to CSV file]
L -->|No| N[Print formatted table]
- Accept a
filenamepositional argument pointing to a stock list file - Fetch the live price for each stock from Google Finance
- Print results as a formatted table to stdout
- Optionally write results to a CSV file via
--output csv
python stockcheck.py stocklist.txt
python stockcheck.py stocklist.txt --output csv- Plain text, one stock per line in
TICKER:EXCHANGEformat - Lines starting with
#are comments; blank lines are ignored
Example:
# US stocks
MSFT:NASDAQ
AAPL:NASDAQ
GOOGL:NASDAQ
# TW stocks
2330:TPE
2454:TPE
- URL pattern:
https://www.google.com/finance/quote/{TICKER}:{EXCHANGE} - Use
requestsfor HTTP andBeautifulSoupto parse the HTML response - On failure or missing ticker, print a warning and continue to the next entry
stdout table:
Ticker Exchange Price Currency
------ -------- ----- --------
MSFT NASDAQ 425.52 USD
2330 TPE 1,050.00 TWD
CSV output (--output csv):
Ticker,Exchange,Price,Currency
MSFT,NASDAQ,425.52,USD
2330,TPE,1050.00,TWDrequests
beautifulsoup4
yfinance
Extend Phase 1 with the following:
- Enable database mode with the
--his_insertflag - Create a dedicated SQLite table for each ticker (table name = ticker symbol)
- Column schema mirrors the DataFrame returned by
yfinance.download() - If the table does not exist → create it and download 1 year of history, then insert
- If the table already exists → check whether the most recent day's data has been inserted
# Live price only (Phase 1 behavior)
python stockcheck.py stocklist.txt
# Enable historical database mode
python stockcheck.py stocklist.txt --his_insertOne table per ticker, named after the ticker symbol (e.g., MSFT, stock_2330):
| Column | Type | Description |
|---|---|---|
Date |
TEXT | Date (Primary Key) |
Open |
REAL | Opening price |
High |
REAL | Daily high |
Low |
REAL | Daily low |
Close |
REAL | Closing price |
Adj Close |
REAL | Adjusted closing price |
Volume |
INTEGER | Trading volume |
Schema corresponds to
yfinance.download(ticker, period="1y")output columns.
for each ticker in stocklist:
if table NOT exists:
CREATE TABLE {ticker}
data = yfinance.download(ticker, period="1y")
INSERT data into table
else:
last_date = SELECT MAX(Date) FROM {ticker}
if last_date != today:
# Optional: fetch and insert latest day
WARN "Last record: {last_date}, data may not be up-to-date"
The following sequence diagram illustrates the interaction between the CLI, yfinance API, and SQLite DB when --his_insert is active.
sequenceDiagram
participant CLI as stockcheck.py
participant YF as yfinance API
participant DB as SQLite DB
loop For each ticker
CLI->>DB: Check if table exists
alt Table does not exist
CLI->>DB: CREATE TABLE
CLI->>YF: download(1y history)
YF-->>CLI: Return DataFrame
CLI->>DB: INSERT historical data
else Table already exists
CLI->>DB: Query last recorded date
CLI->>YF: download(incremental data)
CLI->>DB: INSERT new rows
end
end
Add an optional --ticker argument to query a single stock directly from the command line, bypassing the stock list file.
python stockcheck.py --ticker MSFT:NASDAQ| Scenario | Behavior |
|---|---|
--ticker provided |
Skip filename reading; fetch live price for that ticker |
--ticker not provided |
Fall back to Phase 1/2 behavior using filename |
--ticker + --his_insert |
--ticker takes precedence; database operations skipped |
The price-fetching behavior of
--tickeris identical to processing a single line from the stock list. Output format is the same.
- When
--tickeris active,filenameis optional (may be omitted) - When
--tickeris active,--his_inserthas no effect
Add an optional --avg_break flag that compares each stock's live price against its short-term moving averages and reports breakout signals.
Argument help string:
"to check if the price of the ticker breaks through its short/mid moving average"
# With stock list file
python stockcheck.py stocklist.txt --avg_break
# With single ticker
python stockcheck.py --ticker MSFT:NASDAQ --avg_breakFor each ticker, evaluate the following three conditions:
| MA Period | Source Column | Condition |
|---|---|---|
| 5-day | Adj Close |
live_price > MA5 |
| 10-day | Adj Close |
live_price > MA10 |
| 20-day | Adj Close |
live_price > MA20 |
- If any condition is
True→ print the ticker name - If all conditions are
False→ skip (no output)
⚠️ Moving averages are computed from the local SQLite database, not fetched from the network, in order to minimize HTTP requests.Prerequisite: The user must have already populated the database using
--his_insertbefore invoking--avg_break.
check_avg_break()does not validate data completeness or recency. If historical data is missing or stale, the computed averages will be incorrect and may produce false signals.
This is the user's responsibility. Always run--his_insertbefore using--avg_break.
--avg_break can be combined with either of the following:
- List mode:
python stockcheck.py stocklist.txt --avg_break - Single ticker mode:
python stockcheck.py --ticker MSFT:NASDAQ --avg_break
| Risk | Description |
|---|---|
Using --avg_break without prior --his_insert |
MA calculations will use empty or stale data, producing incorrect signals |
| Google Finance HTML structure changes | The scraping logic may break after page redesigns; BeautifulSoup selectors need periodic maintenance |
| yfinance API instability | Intermittent failures may occur; consider adding a retry mechanism |
Phase 1 → Verify scraping works correctly
Phase 2 → Verify DB write and read operations
Phase 3 → Verify --ticker interaction with Phase 1/2
Phase 4 → Verify --avg_break behavior with and without DB data
| Argument | Type | Required | Description |
|---|---|---|---|
filename |
positional | Conditional | Path to stock list file (optional when --ticker is active) |
--output csv |
flag | No | Write results to a CSV file instead of stdout |
--his_insert |
flag | No | Enable SQLite database mode (download or update historical data) |
--ticker |
value | No | Query a single stock (TICKER:EXCHANGE format); bypasses list |
--avg_break |
flag | No | Compare live price to MA5/MA10/MA20; print tickers that break through |