Skip to content

Meridian.AI for Stocks #492

Meridian.AI for Stocks

Meridian.AI for Stocks #492

name: Meridian.AI for Stocks
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
# Only one stock training run at a time — queued runs wait, never cancel in-progress
concurrency:
group: stock-training
cancel-in-progress: false
permissions:
contents: write
issues: write
env:
PYTHON_VERSION: '3.11'
CACHE_VERSION: v3
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
setup:
name: Setup & Fetch Stock Data
runs-on: ubuntu-latest
outputs:
has_data: ${{ steps.check_data.outputs.has_data }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-${{ env.CACHE_VERSION }}-
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install comet-ml
- name: Free Disk Space
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/share/boost
df -h
- name: Fetch Stock Data
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
echo "Fetching stock data..."
python scripts/fetch_and_store_data.py \
--db-file training.db \
--asset-type stock \
--limit 50
- name: Check if Data was Fetched
id: check_data
run: |
if [ -f training.db ]; then
COUNT=$(sqlite3 training.db "SELECT COUNT(*) FROM market_data;")
echo "Total rows: $COUNT"
if [ "$COUNT" -gt "0" ]; then
echo "has_data=true" >> $GITHUB_OUTPUT
else
echo "has_data=false" >> $GITHUB_OUTPUT
fi
else
echo "has_data=false" >> $GITHUB_OUTPUT
fi
- name: Upload Database
if: steps.check_data.outputs.has_data == 'true'
uses: actions/upload-artifact@v4
with:
name: stock-training-db-${{ github.run_number }}
path: training.db
retention-days: 1
train:
name: Train Stock Model
needs: setup
if: needs.setup.outputs.has_data == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Download Database
uses: actions/download-artifact@v4
with:
name: stock-training-db-${{ github.run_number }}
- name: Cache Dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install comet-ml huggingface_hub
- name: Download Existing Model
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
mkdir -p models
python -c "
import os
from huggingface_hub import hf_hub_download
try:
hf_hub_download(repo_id='meridianal/ARA.AI', filename='models/Meridian.AI_Stocks.pt', local_dir='.', token=os.environ.get('HF_TOKEN'))
print('Downloaded existing stock model (loader will skip if old version).')
except Exception as e:
print(f'No existing model found, will train fresh: {e}')
"
- name: Train Model
env:
COMET_API_KEY: ${{ secrets.COMET_API_KEY }}
run: |
echo "Training Stock Model (45min budget)"
python scripts/train_stock_model.py \
--db-file training.db \
--output models/Meridian.AI_Stocks.pt \
--use-all-data \
--epochs 999 \
--max-time 45 \
--comet-api-key "$COMET_API_KEY"
- name: Upload Model
uses: actions/upload-artifact@v4
with:
name: stock-model-${{ github.run_number }}
path: models/Meridian.AI_Stocks.pt
retention-days: 7
deploy:
name: Push to Hugging Face
needs: [train, setup]
if: needs.setup.outputs.has_data == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Install Dependencies
run: |
pip install huggingface_hub python-dotenv
- name: Download Model
uses: actions/download-artifact@v4
with:
name: stock-model-${{ github.run_number }}
path: models/
- name: Upload to HF Hub
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
run: |
echo "Uploading Stock Model to Hugging Face..."
python scripts/push_elite_models.py \
--model-path models/Meridian.AI_Stocks.pt \
--model-type stock
cleanup:
name: Cleanup & Report
needs: [setup, train, deploy]
if: always()
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Delete Artifacts
uses: geekyeggo/delete-artifact@v5
with:
name: stock-training-db-${{ github.run_number }}
continue-on-error: true
- name: Close Previous Failure Issues on Success
if: needs.train.result == 'success'
uses: actions/github-script@v7
with:
script: |
// Find open automated stock-training issues and close them
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'stock-training,automated',
state: 'open',
per_page: 10,
});
for (const issue of issues.data) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'completed',
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: `Resolved by successful training run [#${context.runNumber}](${context.payload.repository.html_url}/actions/runs/${context.runId}).`,
});
}
- name: Create Failure Issue
if: needs.train.result == 'failure'
uses: actions/github-script@v7
with:
script: |
// Check if an open issue already exists to avoid duplicates
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'stock-training,automated',
state: 'open',
per_page: 5,
});
if (existing.data.length > 0) {
// Comment on existing issue instead of creating a new one
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.data[0].number,
body: `Still failing — run [#${context.runNumber}](${context.payload.repository.html_url}/actions/runs/${context.runId})\n- Setup: ${{ needs.setup.result }}\n- Training: ${{ needs.train.result }}\n- Deploy: ${{ needs.deploy.result }}`,
});
return;
}
const title = `Stock Training Failed — Run ${context.runNumber}`;
const body = `## Stock Model Training Failure
**Workflow:** [Run #${context.runNumber}](${context.payload.repository.html_url}/actions/runs/${context.runId})
**Triggered by:** ${context.eventName}
**Commit:** ${context.sha.substring(0, 7)}
### Job Results
| Job | Result |
|-----|--------|
| Setup | ${{ needs.setup.result }} |
| Training | ${{ needs.train.result }} |
| Deploy | ${{ needs.deploy.result }} |
> This issue will auto-close when the next training run succeeds.
[View Logs](${context.payload.repository.html_url}/actions/runs/${context.runId})
`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: title,
body: body,
labels: ['bug', 'stock-training', 'automated']
});