Skip to content

Update Market Data: US #2697

Update Market Data: US

Update Market Data: US #2697

name: 'Update Market Data: US'
on:
# Pre-Market Trading: 9:00 AM - 2:30 PM UTC
# Regular Session: 2:30 PM - 9:00 PM UTC
# After-Hours Trading: 9:00 PM - 1:00 AM UTC (the following day)
schedule:
- cron: '55 9-23 * * 1-5'
- cron: '55 0,1 * * 2-6'
workflow_dispatch:
inputs:
start_date:
description: 'Start date, YYYY/MM/DD'
required: true
type: string
default: '2024/12/09'
end_date:
description: 'End date, YYYY/MM/DD'
required: true
type: string
default: '2025/03/10'
permissions:
contents: write
jobs:
fetch:
runs-on: ubuntu-latest
env:
API_URL: https://api.nasdaq.com/api/screener
USER_AGENT: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0'
CI_COMMIT_AUTHOR: github-actions[bot]
CI_COMMIT_AUTHOR_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
CI_COMMIT_MESSAGE: 'Update Market Data: US'
steps:
- name: Read inputs
run: |
if [ -z "${{ inputs.start_date }}" ]; then
START_DATE=$(TZ="US/Eastern" date +%Y/%m/%d)
END_DATE=$(TZ="US/Eastern" date +%Y/%m/%d)
else
START_DATE="${{ inputs.start_date }}"
END_DATE="${{ inputs.end_date }}"
fi
echo "START_DATE=$START_DATE" >> $GITHUB_ENV
echo "END_DATE=$END_DATE" >> $GITHUB_ENV
- name: Checkout
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Sparse Checkout
if: ${{ github.event_name == 'schedule' }}
uses: actions/checkout@v4
with:
fetch-depth: 1
filter: blob:none
sparse-checkout: |
marketdata/${{ env.START_DATE }}
marketdata/${{ env.START_DATE }}/raw
sectors
history
sparse-checkout-cone-mode: true
- name: Get data
if: ${{ github.event_name == 'schedule' }}
run: |
function get_data() {
exchange="$1"
response_code=$(curl --request GET \
--url "${{ env.API_URL }}/stocks?tableonly=true&limit=25&offset=0&download=true&exchange=${exchange}" \
--user-agent "${{ env.USER_AGENT }}" \
--silent \
--write-out "%{http_code}" \
--output "${exchange}-tmp.json")
if [ "${response_code}" -eq 200 ]; then
jq '.data.rows' ${exchange}-tmp.json > ${exchange}.json
rm ${exchange}-tmp.json
sed -i "s/\"symbol\": \"/\"symbol\": \"$exchange\:/g" ${exchange}.json
sed -i "s/\"marketCap\": \"\"/\"marketCap\": \"0\"/g" ${exchange}.json
else
echo "Request failed with response code: ${response_code}"
exit 1
fi
}
ex_list=("nasdaq" "nyse" "amex")
for ex in "${ex_list[@]}"; do
get_data "${ex}"
done
- name: Concat JSONs
if: ${{ github.event_name == 'schedule' }}
run: |
jq -s '[.[][]]' nasdaq.json nyse.json amex.json > us-all.json
mkdir -p "$GITHUB_WORKSPACE/marketdata/$(TZ="US/Eastern" date +"%Y/%m/%d")/raw"
mv *.json "$GITHUB_WORKSPACE/marketdata/$(TZ="US/Eastern" date +"%Y/%m/%d")/raw/"
- name: Get ETFs
if: ${{ github.event_name == 'schedule' }}
run: |
response_code=$(curl --request GET \
--url "${{ env.API_URL }}/etf?tableonly=true&limit=25&offset=0&download=true" \
--user-agent "${{ env.USER_AGENT }}" \
--silent \
--write-out "%{http_code}" \
--output "us-etf.json")
if [ "${response_code}" -ne 200 ]; then
echo "Request failed with response code: ${response_code}"
exit 1
fi
if [ "$(jq '.data.data.rows | length' us-etf.json)" -ne "0" ]; then
mv us-etf.json "$GITHUB_WORKSPACE/marketdata/$(TZ="US/Eastern" date +"%Y/%m/%d")/raw/"
fi
- name: Generate datafiles
run: |
function datafile_generator() {
data_folder="$1"
file="$2"
cd "${data_folder}"
jq --compact-output '{
"securities": {
"columns": [
"exchange",
"country",
"type",
"sector",
"industry",
"currencyId",
"ticker",
"nameEng",
"nameEngShort",
"nameOriginal",
"nameOriginalShort",
"priceOpen",
"priceLastSale",
"priceChangePct",
"volume",
"value",
"numTrades",
"marketCap",
"listedFrom",
"listedTill",
"wikiPageIdEng",
"wikiPageIdOriginal",
"nestedItemsCount"],
"data": [
.[] | [
(.symbol | split(":") | .[0]),
.country // "",
"stock",
(if .sector == "" then "Miscellaneous" else .sector end),
(if .industry == "" then "Miscellaneous" else .industry end),
"USD",
(.symbol | split(":") | .[1]),
.name,
"",
"",
"",
(if .lastsale == "" then 0
else if .netchange == "" then (.lastsale | gsub("\\$"; "") | tonumber)
else ((.lastsale | gsub("\\$"; "") | tonumber) - (.netchange | gsub("\\$"; "") | tonumber))
end
end),
(if .lastsale == "" then 0 else (.lastsale | gsub("\\$"; "") | tonumber) end),
(if .pctchange == "" then 0 else (.pctchange | gsub("%"; "") | tonumber) end),
(.volume | tonumber),
0,
0,
(.marketCap | tonumber),
.ipoyear // "",
"",
"",
"",
0
]
]
}
}' "raw/$file" > "tmp-$file"
cp "tmp-$file" "$file"
if [ -f "raw/us-etf.json" ]; then
jq --compact-output --slurp '
.[0].securities.data += (
.[1].data.data.rows | map([
"nasdaq",
"United States",
"etf",
"ETFS",
"ETFS",
"USD",
.symbol,
.companyName // "",
"",
"",
"",
(if .lastSalePrice == "$" then 0
else if .netChange == "" then (.lastSalePrice | gsub("\\$"; "") | tonumber)
else ((.lastSalePrice | gsub("\\$"; "") | tonumber) - (.netChange | gsub("\\$"; "") | tonumber))
end
end),
(if .lastSalePrice == "" or .lastSalePrice == "$" then 0 else (.lastSalePrice | gsub("\\$"; "") | tonumber) end),
(if .percentageChange == "" then 0 else (.percentageChange | gsub("%"; "") | tonumber) end),
0,
0,
0,
0,
"",
"",
"",
"",
0
])
)' "tmp-$file" "raw/us-etf.json" | jq .[0] > "$file"
fi
cp "$file" "tmp-$file"
# do math
jq --compact-output '{
securities: {
columns: .securities.columns,
data: (
.securities.data as $original
| [
.securities.data[]
| {
type: .[2],
sector: .[3],
priceChangePct: .[13],
volume: .[14],
value: .[15],
numTrades: .[16],
marketCap: .[17],
marketCapPrev: (.[17] / (1 + .[13] / 100))
}
] as $transformed
| ([$transformed[]]
| {
marketCap: (map(.marketCap) | add),
marketCapPrev: (map(.marketCapPrev) | add),
volume: (map(.volume) | add),
value: (map(.value) | add),
numTrades: (map(.numTrades) | add),
nestedItemsCount: (map(select(.type != "sector")) | length)
} as $totals
| [[
"",
"",
"sector",
"",
"",
"USD",
"Market",
"Market",
"",
"",
"",
0,
0,
(if $totals.marketCapPrev == 0 then null
else (100 * ($totals.marketCap - $totals.marketCapPrev) / $totals.marketCapPrev)
end),
$totals.volume,
$totals.value,
$totals.numTrades,
$totals.marketCap,
"",
"",
"",
"",
$totals.nestedItemsCount
]]
) as $marketTotal
| ($transformed
| group_by(.sector)
| map([
"",
"",
"sector",
"Market",
"",
"USD",
.[0].sector,
.[0].sector,
"",
"",
"",
0,
0,
(if (map(.marketCapPrev)|add) == 0 then null
else (100 * ((map(.marketCap)|add) - (map(.marketCapPrev)|add)) / (map(.marketCapPrev)|add))
end),
(map(.volume)|add),
(map(.value)|add),
(map(.numTrades)|add),
(map(.marketCap)|add),
"",
"",
"",
"",
(map(.marketCap)|length)
]) as $sectorTotals
| $marketTotal + $sectorTotals + $original
)
)
}
}' "tmp-$file" > "$file"
rm "tmp-$file"
}
start_seconds=$(TZ="US/Eastern" date -d "$START_DATE" +%s)
end_seconds=$(TZ="US/Eastern" date -d "$END_DATE" +%s)
current_seconds=$start_seconds
while [ $current_seconds -le $end_seconds ]; do
current_date=$(TZ="US/Eastern" date -d "@$current_seconds" +"%Y/%m/%d")
current_seconds=$((current_seconds + 86400))
# Check if the current date is a weekend day
if [ $(TZ="US/Eastern" date -d "$current_date" +%u) -ge 6 ]; then
continue
fi
data_folder=$GITHUB_WORKSPACE/marketdata/$current_date
mkdir -p "${data_folder}/raw/"
files=("nasdaq.json" "nyse.json" "amex.json" "us-all.json")
for file in "${files[@]}"; do
if [[ ! -e "${data_folder}/raw/$file" ]]; then
continue
fi
datafile_generator "$data_folder" "$file"
done
done
- name: Update Histogram Datafiles
uses: finmap-org/actions/update-histogram-data@main
with:
workdir: ${{ github.workspace }}
start_date: ${{ env.START_DATE }}
end_date: ${{ env.END_DATE }}
- name: Commit and push
run: |
git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"
git config --global user.email "${{ env.CI_COMMIT_AUTHOR_EMAIL }}"
# git add --all
# Do not commit empty files
# find . -type f -not -empty -exec git add {} +
# Do not commit files with size less than 5 bytes
find . -type f -size +5c -exec git add {} +
git commit -m "${{ env.CI_COMMIT_MESSAGE }}"
git push origin main