summaryrefslogtreecommitdiff
path: root/scripts/fetch.sh
diff options
context:
space:
mode:
authorJSDurand <mmemmew@gmail.com>2026-10-09 16:12:46 +0800
committerJSDurand <mmemmew@gmail.com>2026-10-09 16:12:46 +0800
commite02b8c1369799eee31fed5c5d8b44a1b044e2780 (patch)
tree0f559ea030c6b814e36f04501c2dbdd15427d418 /scripts/fetch.sh
first commit
This is code written by claude for a static stock analysis page generator. I intend to test difference analysis indicators to see if some claims make any sense.
Diffstat (limited to 'scripts/fetch.sh')
-rwxr-xr-xscripts/fetch.sh83
1 files changed, 83 insertions, 0 deletions
diff --git a/scripts/fetch.sh b/scripts/fetch.sh
new file mode 100755
index 0000000..5ed4901
--- /dev/null
+++ b/scripts/fetch.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# Download daily OHLCV history from Yahoo Finance into CSV files readable by `stock`.
+#
+# scripts/fetch.sh [-d outdir] [-f symbols.txt] START END SYMBOL...
+#
+# START/END are YYYY-MM-DD (END inclusive). Symbols use Yahoo naming:
+# 2330.TW TWSE-listed 6488.TWO TPEx/OTC ^TWII Taiwan index AAPL US stock
+# A bare 4-6 digit code (e.g. 2330) is treated as 2330.TW.
+# Existing CSVs are extended incrementally (only newer days are fetched).
+set -u
+
+outdir=data; symfile=
+while getopts "d:f:" o; do
+ case $o in d) outdir=$OPTARG;; f) symfile=$OPTARG;; *) sed -n '2,10p' "$0"; exit 2;; esac
+done
+shift $((OPTIND - 1))
+[ $# -ge 2 ] || { sed -n '2,10p' "$0"; exit 2; }
+start=$1; end=$2; shift 2
+symbols=("$@")
+[ -n "$symfile" ] && while IFS= read -r l; do l=${l%%#*}; l=${l//[[:space:]]/}; [ -n "$l" ] && symbols+=("$l"); done < "$symfile"
+[ ${#symbols[@]} -gt 0 ] || { echo "no symbols given" >&2; exit 2; }
+for cmd in curl jq; do command -v $cmd >/dev/null || { echo "$cmd is required" >&2; exit 1; }; done
+
+epoch() { # YYYY-MM-DD -> epoch seconds (UTC); BSD date, then GNU date
+ date -j -u -f "%Y-%m-%d %H:%M:%S" "$1 00:00:00" +%s 2>/dev/null || date -u -d "$1" +%s
+}
+next_day() { date -j -v+1d -f "%Y-%m-%d" "$1" +%Y-%m-%d 2>/dev/null || date -d "$1 +1 day" +%Y-%m-%d; }
+
+mkdir -p "$outdir"
+: > "$outdir/failed.txt"
+header="Date,Open,High,Low,Close,Volume"
+p2=$(( $(epoch "$end") + 86400 )) # make END inclusive
+
+fetch_one() { # prints CSV rows (no header) for $1 between $2 and $3(epoch)
+ local sym=$1 from=$2 to=$3 body code tries=0
+ local url="https://query1.finance.yahoo.com/v8/finance/chart/${sym}?period1=${from}&period2=${to}&interval=1d&events=history"
+ while :; do
+ body=$(curl -s -A "Mozilla/5.0" -w '\n%{http_code}' "$url"); code=${body##*$'\n'}; body=${body%$'\n'*}
+ case $code in
+ 200) break;;
+ 404) echo " $sym: not found (404)" >&2; return 1;;
+ 429|5??) tries=$((tries + 1)); [ $tries -gt 5 ] && { echo " $sym: giving up (HTTP $code)" >&2; return 1; }
+ echo " $sym: HTTP $code, retry $tries in $((5 * 2 ** (tries - 1)))s" >&2; sleep $((5 * 2 ** (tries - 1)));;
+ *) echo " $sym: HTTP $code" >&2; return 1;;
+ esac
+ done
+ printf '%s' "$body" | jq -r '
+ if .chart.error != null then error(.chart.error.description) else . end
+ | .chart.result[0] as $r
+ | if ($r.timestamp // null) == null then empty else
+ ($r.meta.gmtoffset // 0) as $off
+ | $r.indicators.quote[0] as $q
+ | range(0; $r.timestamp | length) as $i
+ | [($r.timestamp[$i] + $off | strftime("%Y-%m-%d")), $q.open[$i], $q.high[$i], $q.low[$i], $q.close[$i], $q.volume[$i]]
+ | select(all(.[]; . != null))
+ | map(if type == "number" then (. * 10000 | round / 10000) else . end)
+ | @csv | gsub("\""; "") end'
+}
+
+n=0
+for raw in "${symbols[@]}"; do
+ sym=$raw; [[ $sym =~ ^[0-9]{4,6}$ ]] && sym=$sym.TW
+ file="$outdir/$sym.csv"
+ from=$start
+ if [ -s "$file" ]; then
+ last=$(tail -n 1 "$file" | cut -d, -f1)
+ [[ $last =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] && from=$(next_day "$last")
+ fi
+ [ $n -gt 0 ] && sleep $((1 + RANDOM % 2))
+ n=$((n + 1))
+ p1=$(epoch "$from")
+ if [ "$p1" -ge "$p2" ]; then echo "$sym: up to date"; continue; fi
+ echo "$sym: $from .. $end"
+ if rows=$(fetch_one "$sym" "$p1" "$p2"); then
+ [ -s "$file" ] || echo "$header" > "$file"
+ [ -n "$rows" ] && printf '%s\n' "$rows" >> "$file"
+ echo " $(printf '%s' "$rows" | grep -c .) new rows -> $file"
+ else
+ echo "$sym" >> "$outdir/failed.txt"
+ fi
+done
+[ -s "$outdir/failed.txt" ] && { echo "failed symbols listed in $outdir/failed.txt" >&2; exit 1; }
+rm -f "$outdir/failed.txt"