Cron Job Stock Price Alert Every 5 Minutes
Poll a stock price or market API every 5 minutes during trading hours and fire a Slack alert when a threshold is crossed.
What this schedule does
The expression */5 9-17 * * 1-5 fires every 5 minutes during any hour from 9 to 17 (9am–5:59pm), Monday through Friday. Breaking it down: */5 means "every 5th minute" (0, 5, 10, …, 55), 9-17 restricts it to those hours, * * matches every day-of-month and month, and 1-5 limits it to weekdays.
This covers the standard US stock market session (9:30am–4pm ET) and gives you a comfortable buffer on either side. The job runs roughly 132 times per trading day — frequent enough to catch sudden moves, infrequent enough to stay well within free-tier API rate limits on most data providers.
When your script detects a price breach (above a target, below a stop, or outside a band), it sends a Slack webhook message. Outside these hours the cron never fires, so you avoid noisy alerts at 3am and unnecessary API calls on weekends.
What to monitor and how
Most retail-accessible market data APIs return JSON with a last-trade price field. Common free and low-cost options:
- Alpha Vantage — free tier, 25 requests/day; use
GLOBAL_QUOTEendpoint - Polygon.io — free tier covers previous-close; paid tier covers real-time
- Yahoo Finance (unofficial) — no key required, but unofficial and rate-limited
- Finnhub — free tier includes real-time US equities via WebSocket or REST
- CoinGecko — crypto prices; generous free tier, no key needed
Your script should: fetch the current price, compare it to a stored threshold (env var or config file), and POST to a Slack incoming webhook URL if the condition is met. Keep it stateless — write a last_alerted timestamp to a file or environment variable if you want to suppress repeated alerts within a cooldown window.
Platform snippets
#!/usr/bin/env python3
"""
Stock price alert — runs every 5 min via cron.
Sends a Slack message when price crosses the configured threshold.
Env vars:
TICKER — stock symbol, e.g. AAPL
THRESHOLD — alert when price drops below this value
FINNHUB_TOKEN — API key from finnhub.io
SLACK_WEBHOOK — incoming webhook URL from Slack app settings
COOLDOWN_FILE — path to a file that tracks last alert time (optional)
"""
import os, sys, time, json, urllib.request
TICKER = os.environ["TICKER"]
THRESHOLD = float(os.environ["THRESHOLD"])
TOKEN = os.environ["FINNHUB_TOKEN"]
WEBHOOK = os.environ["SLACK_WEBHOOK"]
COOLDOWN_FILE = os.environ.get("COOLDOWN_FILE", "/tmp/price_alert_last.txt")
COOLDOWN_SECS = 900 # 15-minute cooldown between alerts
def get_price(ticker: str) -> float:
url = f"https://finnhub.io/api/v1/quote?symbol={ticker}&token={TOKEN}"
with urllib.request.urlopen(url, timeout=10) as r:
data = json.loads(r.read())
return float(data["c"]) # "c" = current price
def within_cooldown() -> bool:
try:
last = float(open(COOLDOWN_FILE).read().strip())
return time.time() - last < COOLDOWN_SECS
except (FileNotFoundError, ValueError):
return False
def record_alert():
with open(COOLDOWN_FILE, "w") as f:
f.write(str(time.time()))
def send_slack(message: str):
payload = json.dumps({"text": message}).encode()
req = urllib.request.Request(WEBHOOK, data=payload,
headers={"Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=10)
price = get_price(TICKER)
print(f"{TICKER} current price: ${price:.2f} (threshold: ${THRESHOLD:.2f})")
if price < THRESHOLD:
if within_cooldown():
print("Alert suppressed — within cooldown window.")
sys.exit(0)
msg = f":rotating_light: *{TICKER}* dropped to *${price:.2f}* (below threshold of ${THRESHOLD:.2f})"
send_slack(msg)
record_alert()
print("Alert sent.")
else:
print("No alert — price is above threshold.")
# crontab -e # Runs every 5 min during market hours Mon–Fri (adjust timezone with TZ= prefix) */5 9-17 * * 1-5 TICKER=AAPL THRESHOLD=170 FINNHUB_TOKEN=xxx SLACK_WEBHOOK=https://hooks.slack.com/... /usr/bin/python3 /opt/scripts/price_alert.py
on:
schedule:
- cron: '*/5 14-21 * * 1-5' # UTC: 9am–5pm ET = 14:00–21:00 UTC
jobs:
price-alert:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check price and alert
run: python scripts/price_alert.py
env:
TICKER: ${{ vars.TICKER }}
THRESHOLD: ${{ vars.THRESHOLD }}
FINNHUB_TOKEN: ${{ secrets.FINNHUB_TOKEN }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
# EventBridge cron (6 fields — adds year, UTC) # 9am–5pm ET = 14:00–22:00 UTC cron(*/5 14-21 ? * MON-FRI *)
apiVersion: batch/v1
kind: CronJob
metadata:
name: price-alert
spec:
schedule: "*/5 9-17 * * 1-5"
timeZone: "America/New_York" # Kubernetes 1.27+
jobTemplate:
spec:
template:
spec:
containers:
- name: alert
image: your-image:latest
command: ["python", "price_alert.py"]
env:
- name: TICKER
value: "AAPL"
- name: THRESHOLD
value: "170"
- name: FINNHUB_TOKEN
valueFrom:
secretKeyRef:
name: market-secrets
key: finnhub-token
- name: SLACK_WEBHOOK
valueFrom:
secretKeyRef:
name: market-secrets
key: slack-webhook
restartPolicy: OnFailure
Timezone note
Standard crontab uses the system timezone. GitHub Actions and AWS EventBridge run in UTC. US Eastern time is UTC-5 (EST) or UTC-4 (EDT), so 9am–5pm ET translates to 14-21 or 13-21 UTC depending on daylight saving time. The safest approach is to run slightly wider hours in UTC (e.g., 13-22) so you never miss the open or close during DST transitions. Kubernetes 1.27+ supports spec.timeZone: "America/New_York", which handles DST automatically.