feat(infra): K8s CronJob for multi-strat rebalancer (production deployment)
Weekly in-cluster CronJob running multistrat_bot.py on IBKR paper via the existing ib-gateway (connects to ib-gateway:4002 directly, no port-forward). Lean: code via ConfigMap (no image build), python:3.12-slim + runtime pip. Includes PVC (state/HWM persistence across runs), egress NetworkPolicy (ib-gateway:4002 + 443 for pip/Yahoo + DNS), and ib-gateway ingress patched to allow the rebalancer. Schedule Mon 14:35 UTC (~1h after US open). MULTISTRAT_EXECUTE=false by default (cluster dry-run); flip to true after validating logs. Drawdown CB + leverage cap + idempotency + paper-guard enforced by the bot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,9 @@ spec:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: broker-gateway
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: multistrat-rebalancer
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 4002
|
||||
|
||||
122
infra/k8s/services/multistrat-rebalancer.yaml
Normal file
122
infra/k8s/services/multistrat-rebalancer.yaml
Normal file
@@ -0,0 +1,122 @@
|
||||
# Multi-strat rebalancer — weekly CronJob running the adaptive book on IBKR (paper) via the
|
||||
# in-cluster ib-gateway. Production deployment of scripts/surfer/multistrat_bot.py.
|
||||
#
|
||||
# Code is delivered via ConfigMap (no image build needed — lean). Create/refresh it with:
|
||||
# kubectl create configmap multistrat-bot-code -n foxhunt \
|
||||
# --from-file=scripts/surfer/multistrat_bot.py \
|
||||
# --from-file=scripts/surfer/multistrat_paper.py \
|
||||
# --dry-run=client -o yaml | kubectl apply -f -
|
||||
#
|
||||
# SAFETY: MULTISTRAT_EXECUTE defaults to "false" (cluster dry-run). After validating a few dry-run
|
||||
# CronJob logs, flip to "true" to place paper orders. Account is paper (ib-gateway TRADING_MODE=paper);
|
||||
# the bot additionally refuses any non-DU (live) account unless MULTISTRAT_ALLOW_LIVE_CONFIRMED is set.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: multistrat-state
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app: multistrat-rebalancer
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"] # sequential CronJob runs (Forbid concurrency) — RWO is fine
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: multistrat-rebalancer
|
||||
namespace: foxhunt
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: multistrat-rebalancer
|
||||
policyTypes: [Egress]
|
||||
egress:
|
||||
- to: [] # DNS
|
||||
ports:
|
||||
- {protocol: UDP, port: 53}
|
||||
- {protocol: TCP, port: 53}
|
||||
- to: # in-cluster ib-gateway
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: ib-gateway
|
||||
ports:
|
||||
- {protocol: TCP, port: 4002}
|
||||
- to: # internet 443: PyPI (pip) + Yahoo (prices), no internal ranges
|
||||
- ipBlock:
|
||||
cidr: 0.0.0.0/0
|
||||
except: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
|
||||
ports:
|
||||
- {protocol: TCP, port: 443}
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: multistrat-rebalancer
|
||||
namespace: foxhunt
|
||||
labels:
|
||||
app: multistrat-rebalancer
|
||||
app.kubernetes.io/part-of: foxhunt
|
||||
spec:
|
||||
schedule: "35 14 * * 1" # Monday 14:35 UTC (~1h after US equity open 13:30 UTC)
|
||||
concurrencyPolicy: Forbid
|
||||
startingDeadlineSeconds: 3600
|
||||
successfulJobsHistoryLimit: 5
|
||||
failedJobsHistoryLimit: 10
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 600
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: multistrat-rebalancer
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
nodeSelector:
|
||||
k8s.scaleway.com/pool-name: platform
|
||||
securityContext:
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: rebalancer
|
||||
image: python:3.12-slim
|
||||
command: ["/bin/sh", "-c"]
|
||||
args:
|
||||
- >-
|
||||
pip install --quiet --no-cache-dir ib_async==2.1.0 numpy &&
|
||||
python /app/multistrat_bot.py run
|
||||
env:
|
||||
- {name: IB_HOST, value: "ib-gateway"}
|
||||
- {name: IB_PORT, value: "4002"}
|
||||
- {name: IB_CLIENT_ID, value: "11"}
|
||||
- {name: MULTISTRAT_MAXLEV, value: "1.0"}
|
||||
- {name: MULTISTRAT_HYST, value: "0.03"}
|
||||
- {name: MULTISTRAT_REBALANCE_DAYS, value: "7"}
|
||||
- {name: MULTISTRAT_DD_HALT, value: "0.20"}
|
||||
- {name: MULTISTRAT_MAX_ORDER, value: "0.30"}
|
||||
- {name: MULTISTRAT_EXECUTE, value: "false"} # <-- flip to "true" after validating dry-run logs
|
||||
- {name: MULTISTRAT_STATE, value: "/data/state.json"}
|
||||
- {name: MULTISTRAT_LOG, value: "/data/bot.log"}
|
||||
- {name: PIP_DISABLE_PIP_VERSION_CHECK, value: "1"}
|
||||
- {name: PYTHONUNBUFFERED, value: "1"}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
resources:
|
||||
requests: {memory: "256Mi", cpu: "100m"}
|
||||
limits: {memory: "512Mi", cpu: "500m"}
|
||||
volumeMounts:
|
||||
- {mountPath: /app, name: code, readOnly: true}
|
||||
- {mountPath: /data, name: state}
|
||||
volumes:
|
||||
- name: code
|
||||
configMap:
|
||||
name: multistrat-bot-code
|
||||
- name: state
|
||||
persistentVolumeClaim:
|
||||
claimName: multistrat-state
|
||||
Reference in New Issue
Block a user