feat(devops): add Solana quant bot K8s/Helm, CI/CD, observability, and funding
- Add solana-quant-bot deployment + ClusterIP service templates - Add solana-rpc ExternalName service for Helius/QuickNode - Add Alembic migration init job template - Update values.yaml: solana-rpc, bot config, new Kafka topics, observability - Update configmap with Solana RPC endpoint via internal DNS - Update network policies: bot ingress/egress, postgres/redis/kafka access - Update CI/CD workflow: build/push/deploy solana-quant-bot service - Add Prometheus ServiceMonitor, PrometheusRules (alerts) - Add Grafana dashboard ConfigMap (trades, latency, balance, P&L) - Add initial funding script with dry-run and verification modes
This commit is contained in:
parent
99a864171b
commit
da8fe22f6e
13 changed files with 927 additions and 3 deletions
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
service: [execute-service, data-service, news-service]
|
||||
service: [execute-service, data-service, news-service, solana-quant-bot]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: trading-platform/${{ matrix.service }}
|
||||
|
|
@ -109,7 +109,7 @@ jobs:
|
|||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
strategy:
|
||||
matrix:
|
||||
service: [execute-service, data-service, news-service, api-gateway, dashboard]
|
||||
service: [execute-service, data-service, news-service, api-gateway, dashboard, solana-quant-bot]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -184,6 +184,7 @@ jobs:
|
|||
kubectl rollout status deployment/news-service -n customer1-staging --timeout=5m
|
||||
kubectl rollout status deployment/api-gateway -n customer1-staging --timeout=5m
|
||||
kubectl rollout status deployment/dashboard -n customer1-staging --timeout=5m
|
||||
kubectl rollout status deployment/solana-quant-bot -n customer1-staging --timeout=5m
|
||||
|
||||
deploy-production:
|
||||
needs: [deploy-staging]
|
||||
|
|
|
|||
|
|
@ -19,3 +19,7 @@ data:
|
|||
# PostgreSQL connection
|
||||
POSTGRES_HOST: postgres-primary.{{ .Values.global.namespace }}.svc.cluster.local
|
||||
POSTGRES_PORT: "5432"
|
||||
# Solana RPC (ExternalName service)
|
||||
{{- if .Values.solanaRpc.enabled }}
|
||||
SOLANA_RPC_URL: "http://solana-rpc.{{ .Values.global.namespace }}.svc.cluster.local:{{ .Values.solanaRpc.port }}"
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -179,6 +179,9 @@ spec:
|
|||
- podSelector:
|
||||
matchLabels:
|
||||
app: data-service
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: solana-quant-bot
|
||||
ports:
|
||||
- port: 5432
|
||||
protocol: TCP
|
||||
|
|
@ -204,6 +207,9 @@ spec:
|
|||
- podSelector:
|
||||
matchLabels:
|
||||
app: data-service
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: solana-quant-bot
|
||||
ports:
|
||||
- port: 6379
|
||||
protocol: TCP
|
||||
|
|
@ -231,6 +237,9 @@ spec:
|
|||
- podSelector:
|
||||
matchLabels:
|
||||
app: data-service
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: solana-quant-bot
|
||||
ports:
|
||||
- port: 9092
|
||||
protocol: TCP
|
||||
|
|
@ -308,4 +317,93 @@ spec:
|
|||
protocol: TCP
|
||||
---
|
||||
{{- end }}
|
||||
# Solana Quant Bot networking
|
||||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: solana-quant-bot-networking
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: solana-quant-bot
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
ports:
|
||||
- port: {{ .Values.solanaQuantBot.port }}
|
||||
protocol: TCP
|
||||
- port: {{ .Values.solanaQuantBot.metricsPort }}
|
||||
protocol: TCP
|
||||
egress:
|
||||
# PostgreSQL
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
ports:
|
||||
- port: 5432
|
||||
protocol: TCP
|
||||
# Redis
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
ports:
|
||||
- port: 6379
|
||||
protocol: TCP
|
||||
# Kafka
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: kafka
|
||||
ports:
|
||||
- port: 9092
|
||||
protocol: TCP
|
||||
# Solana RPC (ExternalName — allows outbound to external RPC provider)
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app: solana-rpc
|
||||
ports:
|
||||
- port: {{ .Values.solanaRpc.port }}
|
||||
protocol: TCP
|
||||
# External RPC provider (Helius/QuickNode — outbound HTTPS)
|
||||
- to: []
|
||||
ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
---
|
||||
{{- end }}
|
||||
# Solana RPC networking (ExternalName allows outbound to external provider)
|
||||
{{- if .Values.solanaRpc.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: solana-rpc-networking
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: solana-rpc
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
# Allow outbound HTTPS to external RPC provider
|
||||
- to: []
|
||||
ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
---
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
{{- if .Values.grafana.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}-grafana-dashboard
|
||||
namespace: {{ .Values.grafana.namespace | default .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "trading-platform.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
grafana_dashboard: "1"
|
||||
data:
|
||||
solana-quant-bot-dashboard.json: |
|
||||
{
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 1,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"title": "Bot Overview",
|
||||
"type": "row",
|
||||
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
|
||||
"collapsed": false
|
||||
},
|
||||
{
|
||||
"title": "Pods Running",
|
||||
"type": "stat",
|
||||
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 1},
|
||||
"targets": [
|
||||
{"expr": "up{job=\"solana-quant-bot\"}", "legendFormat": "{{instance}}"}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {"steps": [{"color": "red", "value": null}, {"color": "green", "value": 1}]},
|
||||
"unit": "short"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Total Trades (24h)",
|
||||
"type": "stat",
|
||||
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 1},
|
||||
"targets": [
|
||||
{"expr": "increase(solana_quant_bot_trades_total[24h])"}
|
||||
],
|
||||
"fieldConfig": {"defaults": {"unit": "short"}}
|
||||
},
|
||||
{
|
||||
"title": "Trade Error Rate (5m)",
|
||||
"type": "stat",
|
||||
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 1},
|
||||
"targets": [
|
||||
{"expr": "sum(rate(solana_quant_bot_trade_errors_total[5m])) / sum(rate(solana_quant_bot_trades_total[5m])) * 100"}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "percent",
|
||||
"thresholds": {"steps": [{"color": "green", "value": null}, {"color": "yellow", "value": 3}, {"color": "red", "value": 10}]}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "SOL Balance",
|
||||
"type": "stat",
|
||||
"gridPos": {"h": 4, "w": 6, "x": 18, "y": 1},
|
||||
"targets": [
|
||||
{"expr": "solana_quant_bot_sol_balance"}
|
||||
],
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "short",
|
||||
"thresholds": {"steps": [{"color": "red", "value": null}, {"color": "orange", "value": 10}, {"color": "green", "value": 25}]}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Trade Volume (USDC)",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 5},
|
||||
"targets": [
|
||||
{"expr": "sum(rate(solana_quant_bot_trade_volume_usdc[5m])) * 60", "legendFormat": "USDC/min"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Signal Processing Latency",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 13},
|
||||
"targets": [
|
||||
{"expr": "histogram_quantile(0.50, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p50"},
|
||||
{"expr": "histogram_quantile(0.95, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p95"},
|
||||
{"expr": "histogram_quantile(0.99, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p99"}
|
||||
],
|
||||
"fieldConfig": {"defaults": {"unit": "s"}}
|
||||
},
|
||||
{
|
||||
"title": "Trades per Minute",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 13},
|
||||
"targets": [
|
||||
{"expr": "sum(rate(solana_quant_bot_trades_total[5m])) * 60", "legendFormat": "trades/min"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Token Score Distribution",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 21},
|
||||
"targets": [
|
||||
{"expr": "avg(solana_quant_bot_token_score)", "legendFormat": "avg score"},
|
||||
{"expr": "max(solana_quant_bot_token_score)", "legendFormat": "max score"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "CPU / Memory Usage",
|
||||
"type": "timeseries",
|
||||
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 21},
|
||||
"targets": [
|
||||
{"expr": "sum(rate(container_cpu_usage_seconds_total{pod=~\"solana-quant-bot-.*\"}[5m])) * 1000", "legendFormat": "CPU (m)"},
|
||||
{"expr": "sum(container_memory_usage_bytes{pod=~\"solana-quant-bot-.*\"}) / 1024 / 1024", "legendFormat": "Memory (MB)"}
|
||||
],
|
||||
"fieldConfig": {"defaults": {"unit": "short"}}
|
||||
}
|
||||
],
|
||||
"refresh": "30s",
|
||||
"schemaVersion": 39,
|
||||
"tags": ["solana", "quant-bot", "trading"],
|
||||
"templating": {"list": []},
|
||||
"time": {"from": "now-3h", "to": "now"},
|
||||
"title": "Solana Quant Bot",
|
||||
"uid": "solana-quant-bot",
|
||||
"version": 1
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
{{- if .Values.alertmanager.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}-alerts
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "trading-platform.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
release: prometheus
|
||||
spec:
|
||||
groups:
|
||||
- name: solana-quant-bot.rules
|
||||
rules:
|
||||
# Alert if bot pod is down
|
||||
- alert: SolanaQuantBotDown
|
||||
expr: up{job="{{ .Values.solanaQuantBot.name }}"} == 0
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Solana Quant Bot instance {{ '{{ $instance }}' }} is down"
|
||||
description: "Bot has been unreachable for more than 5 minutes."
|
||||
|
||||
# High error rate on trades
|
||||
- alert: SolanaQuantBotHighErrorRate
|
||||
expr: |
|
||||
sum(rate(solana_quant_bot_trade_errors_total[5m]))
|
||||
/ sum(rate(solana_quant_bot_trades_total[5m])) > 0.05
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Solana Quant Bot trade error rate is >5%"
|
||||
description: "Error rate: {{ '{{ $value }}' }} over 5m window."
|
||||
|
||||
# No trades executed in a long time (stale bot)
|
||||
- alert: SolanaQuantBotNoTrades
|
||||
expr: |
|
||||
time() - increase(solana_quant_bot_last_trade_timestamp[1h]) > 3600
|
||||
for: 15m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Solana Quant Bot has not executed trades in 1 hour"
|
||||
description: "Bot may be stuck or signals pipeline is blocked."
|
||||
|
||||
# High signal processing latency
|
||||
- alert: SolanaQuantBotHighLatency
|
||||
expr: |
|
||||
histogram_quantile(0.95, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le)) > 2
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: "Solana Quant Bot p95 signal latency > 2s"
|
||||
description: "p95 latency: {{ '{{ $value }}' }}s."
|
||||
|
||||
# Wallet balance below threshold
|
||||
- alert: SolanaQuantBotLowBalance
|
||||
expr: solana_quant_bot_sol_balance < 10
|
||||
for: 5m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Solana Quant Bot SOL balance is below 10 SOL"
|
||||
description: "Current balance: {{ '{{ $value }}' }} SOL."
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
{{- if .Values.prometheus.enabled }}
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "trading-platform.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
release: prometheus
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
endpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: {{ .Values.prometheus.scrapeInterval | default "15s" }}
|
||||
scrapeTimeout: 10s
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ .Values.global.namespace }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}-db-migrate
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "trading-platform.labels" . | nindent 4 }}
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
annotations:
|
||||
# Prevent re-running on helm upgrade if already succeeded
|
||||
"helm.sh/hook": post-install,post-upgrade
|
||||
"helm.sh/hook-weight": "-5"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
spec:
|
||||
serviceAccountName: {{ .Values.solanaQuantBot.name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: alembic-migrate
|
||||
image: "{{ .Values.solanaQuantBot.image.repository }}:{{ .Values.solanaQuantBot.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.solanaQuantBot.image.pullPolicy }}
|
||||
command: ["alembic"]
|
||||
args: ["upgrade", "head"]
|
||||
env:
|
||||
- name: DB_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "trading-platform.fullname" . }}-config
|
||||
key: POSTGRES_HOST
|
||||
- name: DB_PORT
|
||||
value: "5432"
|
||||
- name: DB_NAME
|
||||
value: {{ .Values.solanaQuantBot.env.DB_NAME | quote }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: trading-secrets
|
||||
resources:
|
||||
{{- toYaml .Values.solanaQuantBot.migrationResources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
|
||||
restartPolicy: Never
|
||||
{{- with .Values.global.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 300
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "trading-platform.labels" . | nindent 4 }}
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
spec:
|
||||
replicas: {{ .Values.solanaQuantBot.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "trading-platform.selectorLabels" . | nindent 6 }}
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "trading-platform.selectorLabels" . | nindent 8 }}
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
annotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "{{ .Values.solanaQuantBot.metricsPort }}"
|
||||
prometheus.io/path: "/metrics"
|
||||
spec:
|
||||
serviceAccountName: {{ .Values.solanaQuantBot.name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
initContainers:
|
||||
# Wait for database migrations to complete
|
||||
- name: wait-for-migrations
|
||||
image: "{{ .Values.solanaQuantBot.image.repository }}:{{ .Values.solanaQuantBot.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.solanaQuantBot.image.pullPolicy }}
|
||||
command: ['sh', '-c', 'until python -c "import asyncio; from app.db import check_migrations; asyncio.run(check_migrations())"; do echo waiting for migrations; sleep 5; done']
|
||||
env:
|
||||
- name: DB_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "trading-platform.fullname" . }}-config
|
||||
key: POSTGRES_HOST
|
||||
- name: DB_PORT
|
||||
value: "5432"
|
||||
- name: DB_NAME
|
||||
value: {{ .Values.solanaQuantBot.env.DB_NAME | quote }}
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: trading-secrets
|
||||
resources:
|
||||
{{- toYaml .Values.solanaQuantBot.resources | nindent 12 }}
|
||||
containers:
|
||||
- name: {{ .Values.solanaQuantBot.name }}
|
||||
image: "{{ .Values.solanaQuantBot.image.repository }}:{{ .Values.solanaQuantBot.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.solanaQuantBot.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.solanaQuantBot.port }}
|
||||
protocol: TCP
|
||||
- name: metrics
|
||||
containerPort: {{ .Values.solanaQuantBot.metricsPort }}
|
||||
protocol: TCP
|
||||
env:
|
||||
{{- range $key, $value := .Values.solanaQuantBot.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
# Shared config from ConfigMap
|
||||
- name: DB_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "trading-platform.fullname" . }}-config
|
||||
key: POSTGRES_HOST
|
||||
- name: REDIS_HOST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "trading-platform.fullname" . }}-config
|
||||
key: REDIS_HOST
|
||||
- name: KAFKA_BOOTSTRAP_SERVERS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "trading-platform.fullname" . }}-config
|
||||
key: KAFKA_BOOTSTRAP
|
||||
- name: SOLANA_RPC_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: {{ include "trading-platform.fullname" . }}-config
|
||||
key: SOLANA_RPC_URL
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: trading-secrets
|
||||
- secretRef:
|
||||
name: solana-quant-bot-secrets
|
||||
resources:
|
||||
{{- toYaml .Values.solanaQuantBot.resources | nindent 12 }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
{{- with .Values.global.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
preferredDuringSchedulingIgnoredDuringExecution:
|
||||
- weight: 100
|
||||
podAffinityTerm:
|
||||
labelSelector:
|
||||
matchExpressions:
|
||||
- key: app
|
||||
operator: In
|
||||
values:
|
||||
- {{ .Values.solanaQuantBot.name }}
|
||||
topologyKey: kubernetes.io/hostname
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
{{- if .Values.solanaQuantBot.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: {{ .Values.solanaQuantBot.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
- port: {{ .Values.solanaQuantBot.metricsPort }}
|
||||
targetPort: metrics
|
||||
protocol: TCP
|
||||
name: metrics
|
||||
selector:
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ .Values.solanaQuantBot.name }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
app: {{ .Values.solanaQuantBot.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: solana-quant-bot@{{ .Values.global.clusterName }}.iam.gserviceaccount.com
|
||||
---
|
||||
# Dedicated secrets for Solana quant bot (wallet keys, RPC auth)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: solana-quant-bot-secrets
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
{{- include "trading-platform.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
annotations:
|
||||
secrets.yaml.sops.io/v2: encrypted
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Solana wallet private key (base58 encoded)
|
||||
SOLANA_BOT_PRIVATE_KEY_BASE58: {{ .Values.solanaQuantBot.env.SOLANA_BOT_PRIVATE_KEY_BASE58 | default "<CHANGE_ME_WALLET_KEY>" | quote }}
|
||||
# Helius/QuickNode API key for RPC
|
||||
SOLANA_RPC_API_KEY: {{ .Values.solanaQuantBot.env.SOLANA_RPC_API_KEY | default "<CHANGE_ME_RPC_KEY>" | quote }}
|
||||
# Jupiter API key (optional, for better rate limits)
|
||||
JUPITER_API_KEY: {{ .Values.solanaQuantBot.env.JUPITER_API_KEY | default "" | quote }}
|
||||
# Birdeye API key for price data
|
||||
BIRDEYE_API_KEY: {{ .Values.solanaQuantBot.env.BIRDEYE_API_KEY | default "<CHANGE_ME_BIRDEYE_KEY>" | quote }}
|
||||
# Dexscreener API key (if using authenticated endpoints)
|
||||
DEXSCREENER_API_KEY: {{ .Values.solanaQuantBot.env.DEXSCREENER_API_KEY | default "" | quote }}
|
||||
{{- end }}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{{- if .Values.solanaRpc.enabled }}
|
||||
# Solana RPC proxy service (Helius/QuickNode endpoint)
|
||||
# Exposes a stable internal DNS name for Solana RPC access
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .Values.solanaRpc.name }}
|
||||
namespace: {{ .Values.global.namespace }}
|
||||
labels:
|
||||
app: {{ .Values.solanaRpc.name }}
|
||||
app.kubernetes.io/part-of: solana-quant-bot
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: {{ .Values.solanaRpc.externalHost }}
|
||||
---
|
||||
{{- end }}
|
||||
|
|
@ -148,7 +148,7 @@ dashboard:
|
|||
name: dashboard
|
||||
image:
|
||||
repository: ""
|
||||
tag: "0.1.0"
|
||||
tag: "0.2.0-autonomous-bot"
|
||||
pullPolicy: IfNotPresent
|
||||
port: 3000
|
||||
resources:
|
||||
|
|
@ -166,6 +166,8 @@ dashboard:
|
|||
env:
|
||||
NODE_ENV: "production"
|
||||
NEXT_PUBLIC_API_BASE_URL: "/api"
|
||||
NEXT_PUBLIC_EXEC_SERVICE_URL: "http://execute-service:8000"
|
||||
NEXT_PUBLIC_NEWS_SERVICE_URL: "http://news-service:8001"
|
||||
|
||||
# =================================================================
|
||||
# Ingress
|
||||
|
|
@ -287,6 +289,89 @@ kafka:
|
|||
- name: news.analysis
|
||||
partitions: 6
|
||||
replicationFactor: 3
|
||||
# Solana quant bot topics
|
||||
- name: solana.bot.signals
|
||||
partitions: 6
|
||||
replicationFactor: 3
|
||||
- name: solana.bot.trades
|
||||
partitions: 6
|
||||
replicationFactor: 3
|
||||
- name: solana.bot.alerts
|
||||
partitions: 3
|
||||
replicationFactor: 3
|
||||
- name: solana.token.data
|
||||
partitions: 6
|
||||
replicationFactor: 3
|
||||
- name: solana.pool.data
|
||||
partitions: 6
|
||||
replicationFactor: 3
|
||||
|
||||
# =================================================================
|
||||
# Solana RPC (ExternalName service for external RPC providers)
|
||||
# =================================================================
|
||||
solanaRpc:
|
||||
enabled: true
|
||||
name: solana-rpc
|
||||
# ExternalName points to external RPC provider (Helius, QuickNode, etc.)
|
||||
externalName: "mainnet.helius-rpc.com"
|
||||
port: 443
|
||||
# env var for internal DNS: http://solana-rpc.<namespace>.svc.cluster.local:443
|
||||
|
||||
# =================================================================
|
||||
# Solana Quant Bot (autonomous trading agent)
|
||||
# =================================================================
|
||||
solanaQuantBot:
|
||||
enabled: true
|
||||
name: solana-quant-bot
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: ""
|
||||
tag: "0.1.0"
|
||||
pullPolicy: IfNotPresent
|
||||
port: 8000
|
||||
metricsPort: 9090
|
||||
resources:
|
||||
requests:
|
||||
cpu: 1
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 2Gi
|
||||
migrationResources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: 512Mi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 70
|
||||
env:
|
||||
LOG_LEVEL: "INFO"
|
||||
DB_NAME: "solana_quant_db"
|
||||
KAFKA_GROUP_ID: "solana-quant-bot"
|
||||
KAFKA_TOPIC_SIGNALS: "solana.bot.signals"
|
||||
KAFKA_TOPIC_TRADES: "solana.bot.trades"
|
||||
KAFKA_TOPIC_ALERTS: "solana.bot.alerts"
|
||||
KAFKA_TOPIC_TOKEN_DATA: "solana.token.data"
|
||||
KAFKA_TOPIC_POOL_DATA: "solana.pool.data"
|
||||
# Solana RPC via internal DNS (ExternalName service)
|
||||
SOLANA_RPC_ENDPOINT: "http://solana-rpc:443"
|
||||
# Trading config
|
||||
MIN_LIQUIDITY_USD: "50000"
|
||||
MAX_POSITION_SIZE_USD: "10000"
|
||||
SLIPPAGE_BPS: "100"
|
||||
SCORING_THRESHOLD: "60"
|
||||
HEALTHCHECK_INTERVAL: "300"
|
||||
# Secrets (override via secret, not here)
|
||||
SOLANA_BOT_PRIVATE_KEY_BASE58: "<CHANGE_ME>"
|
||||
SOLANA_RPC_API_KEY: "<CHANGE_ME>"
|
||||
BIRDEYE_API_KEY: "<CHANGE_ME>"
|
||||
JUPITER_API_KEY: ""
|
||||
DEXSCREENER_API_KEY: ""
|
||||
|
||||
# =================================================================
|
||||
# Network Policies (from security audit)
|
||||
|
|
@ -297,6 +382,27 @@ networkPolicies:
|
|||
defaultDenyEgress: true
|
||||
allowDNS: true
|
||||
|
||||
# =================================================================
|
||||
# Observability (Prometheus + Grafana + Alertmanager)
|
||||
# =================================================================
|
||||
prometheus:
|
||||
enabled: true
|
||||
scrapeInterval: "15s"
|
||||
retention: "15d"
|
||||
retentionSize: "10GB"
|
||||
|
||||
alertmanager:
|
||||
enabled: true
|
||||
# Alert routes configured separately
|
||||
|
||||
grafana:
|
||||
enabled: true
|
||||
namespace: "" # defaults to global.namespace
|
||||
adminUser: admin
|
||||
# adminPassword comes from secret
|
||||
dashboards:
|
||||
- solana-quant-bot-dashboard
|
||||
|
||||
# =================================================================
|
||||
# SOPS Encryption
|
||||
# =================================================================
|
||||
|
|
|
|||
34
trading-platform/scripts/solana-funding/README.md
Normal file
34
trading-platform/scripts/solana-funding/README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Solana Quant Bot Funding
|
||||
|
||||
Scripts for funding the Solana Quant Bot wallet and verifying balances.
|
||||
|
||||
## Initial Funding
|
||||
|
||||
```bash
|
||||
# Dry run first
|
||||
./fund-bot-wallet.sh --amount 50 --from-wallet <YOUR_WALLET> --namespace customer1 --dry-run
|
||||
|
||||
# Execute funding
|
||||
./fund-bot-wallet.sh --amount 50 --from-wallet <YOUR_WALLET> --namespace customer1
|
||||
|
||||
# Verify balance after
|
||||
./fund-bot-wallet.sh --bot-wallet <BOT_ADDRESS> --verify
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--amount SOL` | Amount in SOL to fund (default: 50) |
|
||||
| `--from-wallet ADDR` | Source wallet (required for transfers) |
|
||||
| `--bot-wallet ADDR` | Bot wallet (auto-read from K8s secret if omitted) |
|
||||
| `--namespace NS` | K8s namespace (default: customer1) |
|
||||
| `--dry-run` | Simulate without sending |
|
||||
| `--verify` | Check bot wallet balance |
|
||||
|
||||
## Security Notes
|
||||
|
||||
- The bot wallet private key is stored as a SOPS-encrypted K8s secret.
|
||||
- Initial funding should use a dedicated hot wallet, not a multi-sig for simplicity.
|
||||
- sec-ops may require multi-sig for larger seed amounts.
|
||||
- Monitor the bot wallet for anomalous transactions via the Prometheus alerts.
|
||||
177
trading-platform/scripts/solana-funding/fund-bot-wallet.sh
Executable file
177
trading-platform/scripts/solana-funding/fund-bot-wallet.sh
Executable file
|
|
@ -0,0 +1,177 @@
|
|||
#!/usr/bin/env bash
|
||||
# fund-bot-wallet.sh — Initial funding script for Solana Quant Bot
|
||||
#
|
||||
# Usage: ./fund-bot-wallet.sh [OPTIONS]
|
||||
#
|
||||
# Options:
|
||||
# --amount SOL Amount in SOL to fund (default: 50)
|
||||
# --from-wallet ADDR Source wallet address (required)
|
||||
# --bot-wallet ADDR Bot wallet address (default: reads from K8s secret)
|
||||
# --namespace NS K8s namespace (default: customer1)
|
||||
# --dry-run Simulate transfer without sending
|
||||
# --verify Verify current bot wallet balance (no transfer)
|
||||
# --help Show this help
|
||||
#
|
||||
# This script:
|
||||
# 1. Reads the bot's wallet address from the K8s secret
|
||||
# 2. Sends SOL from --from-wallet to the bot wallet
|
||||
# 3. Verifies the balance on-chain and in K8s
|
||||
#
|
||||
# Prerequisites:
|
||||
# - solana-cli installed and configured
|
||||
# - kubectl configured for the cluster
|
||||
# - --from-wallet must have enough SOL for the transfer + fees
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Defaults
|
||||
AMOUNT="50"
|
||||
FROM_WALLET=""
|
||||
BOT_WALLET=""
|
||||
NAMESPACE="customer1"
|
||||
DRY_RUN=false
|
||||
VERIFY_ONLY=false
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--amount) AMOUNT="$2"; shift 2 ;;
|
||||
--from-wallet) FROM_WALLET="$2"; shift 2 ;;
|
||||
--bot-wallet) BOT_WALLET="$2"; shift 2 ;;
|
||||
--namespace) NAMESPACE="$2"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--verify) VERIFY_ONLY=true; shift ;;
|
||||
--help)
|
||||
head -30 "$0" | tail -25
|
||||
exit 0
|
||||
;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Helper: get bot wallet from K8s secret if not explicitly provided
|
||||
get_bot_wallet_from_secret() {
|
||||
if [[ -n "$BOT_WALLET" ]]; then
|
||||
echo "$BOT_WALLET"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Reading bot wallet from K8s secret..."
|
||||
BOT_WALLET=$(kubectl -n "$NAMESPACE" get secret solana-quant-bot-secrets \
|
||||
-o jsonpath='{.data.SOLANA_BOT_WALLET_ADDRESS}' 2>/dev/null | base64 -d 2>/dev/null || true)
|
||||
|
||||
if [[ -z "$BOT_WALLET" ]]; then
|
||||
echo "ERROR: Could not read bot wallet from secret solana-quant-bot-secrets in namespace $NAMESPACE"
|
||||
echo "Either deploy the secret first or pass --bot-wallet explicitly."
|
||||
exit 1
|
||||
fi
|
||||
echo "Bot wallet: $BOT_WALLET"
|
||||
}
|
||||
|
||||
# Verify balance
|
||||
verify_balance() {
|
||||
local wallet="$1"
|
||||
echo "=== Verifying balance for $wallet ==="
|
||||
|
||||
# On-chain balance
|
||||
local on_chain_balance
|
||||
on_chain_balance=$(solana balance "$wallet" --output json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin))" 2>/dev/null || echo "0")
|
||||
echo "On-chain SOL balance: $on_chain_balance"
|
||||
|
||||
# Expected minimum
|
||||
local expected
|
||||
expected=$(echo "$AMOUNT * 0.99" | bc) # account for fees
|
||||
local is_ok
|
||||
is_ok=$(echo "$on_chain_balance >= $expected" | bc -l)
|
||||
|
||||
if [[ "$is_ok" -eq 1 ]]; then
|
||||
echo "OK: Balance >= $expected SOL (transferred amount minus fees)"
|
||||
else
|
||||
echo "WARNING: Balance ($on_chain_balance) < expected ($expected SOL)"
|
||||
echo " The transfer may still be pending confirmation."
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Main ---
|
||||
|
||||
echo "============================================"
|
||||
echo " Solana Quant Bot — Initial Funding"
|
||||
echo "============================================"
|
||||
echo " Amount: $AMOUNT SOL"
|
||||
echo " Namespace: $NAMESPACE"
|
||||
echo " Dry run: $DRY_RUN"
|
||||
echo "============================================"
|
||||
|
||||
# Validate from-wallet
|
||||
if [[ -z "$FROM_WALLET" ]]; then
|
||||
echo "ERROR: --from-wallet is required (source wallet address)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get bot wallet
|
||||
get_bot_wallet_from_secret
|
||||
|
||||
# Verify mode
|
||||
if [[ "$VERIFY_ONLY" == true ]]; then
|
||||
verify_balance "$BOT_WALLET"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Dry run
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo ""
|
||||
echo "DRY RUN — Would transfer $AMOUNT SOL from:"
|
||||
echo " $FROM_WALLET -> $BOT_WALLET"
|
||||
echo ""
|
||||
echo "Source wallet balance:"
|
||||
solana balance "$FROM_WALLET"
|
||||
echo ""
|
||||
echo "Bot wallet balance:"
|
||||
solana balance "$BOT_WALLET"
|
||||
echo ""
|
||||
echo "To execute, remove --dry-run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check source wallet has enough
|
||||
echo "Checking source wallet balance..."
|
||||
SOURCE_BALANCE=$(solana balance "$FROM_WALLET" --output json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin))" 2>/dev/null || echo "0")
|
||||
echo "Source wallet: $SOURCE_BALANCE SOL"
|
||||
|
||||
HAS_ENOUGH=$(echo "$SOURCE_BALANCE >= $AMOUNT" | bc -l)
|
||||
if [[ "$HAS_ENOUGH" -ne 1 ]]; then
|
||||
echo "ERROR: Source wallet ($SOURCE_BALANCE SOL) has less than $AMOUNT SOL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Confirm before sending (unless CI)
|
||||
if [[ "${CI:-false}" != "true" ]]; then
|
||||
echo ""
|
||||
read -p "Send $AMOUNT SOL from $FROM_WALLET to $BOT_WALLET? (yes/no): " CONFIRM
|
||||
if [[ "$CONFIRM" != "yes" ]]; then
|
||||
echo "Aborted."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Send transaction
|
||||
echo "Sending transaction..."
|
||||
TX_SIGNATURE=$(solana transfer "$FROM_WALLET" \
|
||||
"$BOT_WALLET" \
|
||||
"$AMOUNT" \
|
||||
--with-block-time \
|
||||
--output json 2>&1 | tee /tmp/solana-fund-tx.json)
|
||||
|
||||
echo "Transaction: $TX_SIGNATURE"
|
||||
|
||||
# Wait for confirmation
|
||||
echo "Waiting for confirmation..."
|
||||
solana confirm "$(echo "$TX_SIGNATURE" | python3 -c "import sys,json; print(json.load(sys.stdin)['signature'])")" --with-max-confirm-retries 20
|
||||
|
||||
# Verify balance
|
||||
echo ""
|
||||
verify_balance "$BOT_WALLET"
|
||||
|
||||
echo ""
|
||||
echo "Funding complete. Bot wallet: $BOT_WALLET"
|
||||
echo "Transferred: $AMOUNT SOL"
|
||||
Loading…
Add table
Reference in a new issue