generated from mathias/template-go-web
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c9bc1be31 | ||
|
|
308f71b566 | ||
|
|
6fb77f5263 | ||
|
|
8eb0358c01 | ||
|
|
2d412790bf | ||
|
|
8539ec3a85 | ||
|
|
c9b22db3b8 | ||
|
|
731673061e | ||
|
|
54b1bc216d | ||
|
|
fa6dcaae0a |
@@ -0,0 +1,75 @@
|
|||||||
|
name: Autoresearch Loop
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
fixture:
|
||||||
|
description: 'Fixture name in fixtures/ (without .json)'
|
||||||
|
required: true
|
||||||
|
default: 'phase-a-toy'
|
||||||
|
rq_id:
|
||||||
|
description: 'Run ID — defaults to fixture name if blank'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
iters:
|
||||||
|
description: 'Max iterations'
|
||||||
|
required: false
|
||||||
|
default: '3'
|
||||||
|
model:
|
||||||
|
description: 'LiteLLM model override (leave blank for default berget/gemma4-31b)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
name: Autoresearch — ${{ inputs.fixture }}
|
||||||
|
runs-on: self-hosted
|
||||||
|
timeout-minutes: 90
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Resolve run ID
|
||||||
|
id: vars
|
||||||
|
run: |
|
||||||
|
RQ_ID="${{ inputs.rq_id }}"
|
||||||
|
[ -z "$RQ_ID" ] && RQ_ID="${{ inputs.fixture }}"
|
||||||
|
echo "rq_id=$RQ_ID" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Clean stale run dir
|
||||||
|
run: rm -rf "runs/${{ steps.vars.outputs.rq_id }}"
|
||||||
|
|
||||||
|
- name: Set up Python venv
|
||||||
|
run: |
|
||||||
|
[ -d .venv ] || python3 -m venv .venv
|
||||||
|
# torch must come from the cu130 wheel index (koala Blackwell sm_120);
|
||||||
|
# requirements.txt deliberately excludes it. Install it first.
|
||||||
|
.venv/bin/pip install -q torch --index-url https://download.pytorch.org/whl/cu130
|
||||||
|
.venv/bin/pip install -q -r requirements.txt
|
||||||
|
|
||||||
|
- name: Scaffold run dir
|
||||||
|
run: |
|
||||||
|
.venv/bin/python scripts/autoresearch_start.py \
|
||||||
|
"fixtures/${{ inputs.fixture }}.json" \
|
||||||
|
"${{ steps.vars.outputs.rq_id }}"
|
||||||
|
|
||||||
|
- name: Run autoresearch loop
|
||||||
|
env:
|
||||||
|
LITELLM_KEY: ${{ secrets.LITELLM_KEY }}
|
||||||
|
LITELLM_BASE: ${{ secrets.LITELLM_BASE }}
|
||||||
|
NTFY_URL: ${{ secrets.NTFY_URL }}
|
||||||
|
run: |
|
||||||
|
ARGS="--run-dir runs/${{ steps.vars.outputs.rq_id }} --iters ${{ inputs.iters }}"
|
||||||
|
[ -n "${{ inputs.model }}" ] && ARGS="$ARGS --model ${{ inputs.model }}"
|
||||||
|
.venv/bin/python loop.py $ARGS
|
||||||
|
|
||||||
|
- name: Upload run artifacts
|
||||||
|
if: always()
|
||||||
|
uses: https://gitea.com/actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: run-${{ steps.vars.outputs.rq_id }}-${{ github.run_number }}
|
||||||
|
path: |
|
||||||
|
runs/${{ steps.vars.outputs.rq_id }}/STATUS.md
|
||||||
|
runs/${{ steps.vars.outputs.rq_id }}/metrics.json
|
||||||
|
runs/${{ steps.vars.outputs.rq_id }}/program.md
|
||||||
|
retention-days: 30
|
||||||
@@ -7,9 +7,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGE: hostexecutor
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
name: Lint / Test / Vet
|
name: Lint / Test / Vet
|
||||||
@@ -31,85 +28,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Run checks
|
- name: Run checks
|
||||||
run: task check
|
run: task check
|
||||||
|
|
||||||
build:
|
|
||||||
name: Build & Import
|
|
||||||
needs: check
|
|
||||||
runs-on: self-hosted
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
outputs:
|
|
||||||
image-tag: ${{ steps.meta.outputs.sha-tag }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Derive image tags
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
SHA=$(git rev-parse --short HEAD)
|
|
||||||
echo "sha-tag=${SHA}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Build and push to local registry
|
|
||||||
run: |
|
|
||||||
REGISTRY="localhost:5000"
|
|
||||||
REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}"
|
|
||||||
buildah build \
|
|
||||||
--label "org.opencontainers.image.revision=${{ github.sha }}" \
|
|
||||||
-t ${REF} \
|
|
||||||
-t ${REGISTRY}/${{ env.IMAGE }}:latest \
|
|
||||||
.
|
|
||||||
buildah push --tls-verify=false ${REF}
|
|
||||||
buildah push --tls-verify=false ${REGISTRY}/${{ env.IMAGE }}:latest
|
|
||||||
echo "✓ Image pushed to ${REF}"
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
name: Deploy via GitOps
|
|
||||||
needs: build
|
|
||||||
runs-on: self-hosted
|
|
||||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
||||||
steps:
|
|
||||||
- name: Update image tag in infra repo
|
|
||||||
env:
|
|
||||||
IMAGE_TAG: ${{ needs.build.outputs.image-tag }}
|
|
||||||
DEPLOY_KEY: ${{ secrets.INFRA_DEPLOY_KEY }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "$DEPLOY_KEY" > ~/.ssh/id_infra
|
|
||||||
chmod 600 ~/.ssh/id_infra
|
|
||||||
ssh-keyscan -p 30022 10.0.1.20 >> ~/.ssh/known_hosts 2>/dev/null
|
|
||||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_infra -o IdentitiesOnly=yes"
|
|
||||||
rm -rf /tmp/infra
|
|
||||||
git clone -b main ssh://git@10.0.1.20:30022/mathias/infra.git /tmp/infra
|
|
||||||
cd /tmp/infra
|
|
||||||
DEPLOYMENT="k3s/apps/hostexecutor/deployment.yaml"
|
|
||||||
sed -i "s|image: localhost:5000/hostexecutor:.*|image: localhost:5000/hostexecutor:${IMAGE_TAG}|" "$DEPLOYMENT"
|
|
||||||
grep -q "localhost:5000/hostexecutor:${IMAGE_TAG}" "$DEPLOYMENT" \
|
|
||||||
|| { echo "✗ image tag patch failed"; exit 1; }
|
|
||||||
if git diff --quiet "$DEPLOYMENT"; then
|
|
||||||
echo "ℹ image tag unchanged — skipping push"
|
|
||||||
else
|
|
||||||
git -c user.name="hostexecutor CI" \
|
|
||||||
-c user.email="ci@hostexecutor.local" \
|
|
||||||
commit -m "chore(deploy): hostexecutor → ${IMAGE_TAG}" "$DEPLOYMENT"
|
|
||||||
git push origin main
|
|
||||||
echo "✓ pushed to infra repo"
|
|
||||||
fi
|
|
||||||
shred -u ~/.ssh/id_infra
|
|
||||||
|
|
||||||
- name: Trigger Flux reconcile
|
|
||||||
run: |
|
|
||||||
kubectl -n flux-system annotate gitrepository flux-system \
|
|
||||||
reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite
|
|
||||||
kubectl -n flux-system annotate kustomization apps \
|
|
||||||
reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite
|
|
||||||
|
|
||||||
- name: Verify rollout
|
|
||||||
run: |
|
|
||||||
kubectl rollout status deployment/hostexecutor \
|
|
||||||
--namespace hostexecutor \
|
|
||||||
--timeout=120s \
|
|
||||||
|| {
|
|
||||||
kubectl get pods -n hostexecutor -o wide
|
|
||||||
kubectl get events -n hostexecutor --sort-by='.lastTimestamp' | tail -20
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|||||||
+17
@@ -34,3 +34,20 @@ bin/
|
|||||||
|
|
||||||
# downloaded + processed market data (track via DVC/MinIO, #10 — not git)
|
# downloaded + processed market data (track via DVC/MinIO, #10 — not git)
|
||||||
data/
|
data/
|
||||||
|
|
||||||
|
# autoresearch run dirs (ephemeral; each scaffold rebuilds from fixtures/)
|
||||||
|
runs/
|
||||||
|
|
||||||
|
# ephemeral experiment outputs (generated by train.py / loop.py)
|
||||||
|
metrics.json
|
||||||
|
embeddings.json
|
||||||
|
HEARTBEAT
|
||||||
|
STATUS.md
|
||||||
|
|
||||||
|
# python caches
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# built Go binaries
|
||||||
|
eval
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ tasks:
|
|||||||
cmds: [.venv/bin/python scripts/fetch_multipair.py]
|
cmds: [.venv/bin/python scripts/fetch_multipair.py]
|
||||||
data:prepare:pair:
|
data:prepare:pair:
|
||||||
desc: "Build {PAIR}_hourly.parquet from data/raw/{PAIR}/ (e.g. PAIR=gbpusd)"
|
desc: "Build {PAIR}_hourly.parquet from data/raw/{PAIR}/ (e.g. PAIR=gbpusd)"
|
||||||
cmds: [PAIR={{.PAIR}} .venv/bin/python scripts/prepare_hourly.py {{.EXTRA_ARGS}}]
|
cmds: ["PAIR={{.PAIR}} .venv/bin/python scripts/prepare_hourly.py {{.EXTRA_ARGS}}"]
|
||||||
vars:
|
vars:
|
||||||
PAIR: '{{default "eurusd" .PAIR}}'
|
PAIR: '{{default "eurusd" .PAIR}}'
|
||||||
data:prepare:multipair:
|
data:prepare:multipair:
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"strategic_question": "What is the highest-leverage path to a JEPA-based FX tail-risk system that beats a GARCH/EWMA baseline on out-of-sample VaR-breach calibration, given one GPU and a solo researcher?",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "phase-a-toy",
|
||||||
|
"status": "autoresearch-ready",
|
||||||
|
"question": "Improve the OOS linear-probe R² (val_vol_r2) of the HEPA encoder on EUR/USD daily realized vol. The encoder is a small causal transformer trained with VICReg. Vary one hyperparameter or architectural choice per iteration — model size, learning rate, window, patch length, depth, VICReg loss weights — to push val_vol_r2 as high as possible on the 2022-2023 OOS slice.",
|
||||||
|
"candidate_metric": "val_vol_r2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -40,11 +40,11 @@ func kupiecPOF(n, n1 int, p0 float64) float64 {
|
|||||||
phat := float64(n1) / float64(n)
|
phat := float64(n1) / float64(n)
|
||||||
|
|
||||||
var lr float64
|
var lr float64
|
||||||
switch {
|
switch n1 {
|
||||||
case n1 == 0:
|
case 0:
|
||||||
// 0 × ln(0/p0) = 0 by convention; only the n0 term contributes
|
// 0 × ln(0/p0) = 0 by convention; only the n0 term contributes
|
||||||
lr = 2 * float64(n0) * math.Log((1-phat)/(1-p0))
|
lr = 2 * float64(n0) * math.Log((1-phat)/(1-p0))
|
||||||
case n1 == n:
|
case n:
|
||||||
// n0 term vanishes
|
// n0 term vanishes
|
||||||
lr = 2 * float64(n1) * math.Log(phat/p0)
|
lr = 2 * float64(n1) * math.Log(phat/p0)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ def run_train() -> "tuple[float | None, float, str]":
|
|||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env["METRICS_OUT"] = str(METRICS_JSON.resolve())
|
env["METRICS_OUT"] = str(METRICS_JSON.resolve())
|
||||||
|
# train.py is copied into the run dir, so sys.path[0] is that run dir — which
|
||||||
|
# has no scripts/. Put the project root (where loop.py + scripts/ live) on
|
||||||
|
# PYTHONPATH so train.py's `from scripts.var_breach import ...` resolves.
|
||||||
|
env["PYTHONPATH"] = str(Path(__file__).resolve().parent) + os.pathsep + env.get("PYTHONPATH", "")
|
||||||
try:
|
try:
|
||||||
r = subprocess.run(
|
r = subprocess.run(
|
||||||
[sys.executable, str(TRAIN_PY.resolve())],
|
[sys.executable, str(TRAIN_PY.resolve())],
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"val_vol_r2": 0.3641397896593044,
|
|
||||||
"phase1_r2": 0.3908407688140869,
|
|
||||||
"n_test": 11641,
|
|
||||||
"knobs": {
|
|
||||||
"WINDOW": 120,
|
|
||||||
"PATCH_LEN": 24,
|
|
||||||
"D_MODEL": 128,
|
|
||||||
"DEPTH": 2,
|
|
||||||
"ALPHA": 0.1,
|
|
||||||
"DELTA_T_MAX": 3,
|
|
||||||
"EPOCHS": 300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -5,6 +5,6 @@
|
|||||||
numpy>=2.0
|
numpy>=2.0
|
||||||
pandas>=2.2
|
pandas>=2.2
|
||||||
pyarrow>=16
|
pyarrow>=16
|
||||||
histdata>=1.3 # histdata.com downloader (handles the tk token politely)
|
histdata>=1.1 # histdata.com downloader (1.1 is newest on PyPI; 1.3 never existed)
|
||||||
hmmlearn>=0.3 # regime detector (prepare_regime.py, jepa-fx-risk#13)
|
hmmlearn>=0.3 # regime detector (prepare_regime.py, jepa-fx-risk#13)
|
||||||
scikit-learn>=1.4 # HMM dependency
|
scikit-learn>=1.4 # HMM dependency
|
||||||
|
|||||||
Reference in New Issue
Block a user