add: 关羽完成已调研结果和实时风控系统上传
This commit is contained in:
+157
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# sanguo_vnpy 自动化部署流水线脚本
|
||||||
|
# 版本: v1.0
|
||||||
|
# 作者: 姜维(后勤总督)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================================================"
|
||||||
|
echo "🚀 sanguo_vnpy 自动化部署流水线"
|
||||||
|
echo "========================================================================"
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取当前时间
|
||||||
|
get_timestamp() {
|
||||||
|
date +"%Y%m%d_%H%M%S"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 配置变量
|
||||||
|
PROJECT_DIR="/Users/chufeng/.openclaw/agents/main/workspace/projects/sanguo_vnpy"
|
||||||
|
DEPLOY_ENV="${1:-production}"
|
||||||
|
TIMESTAMP=$(get_timestamp)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
print_info "部署环境: $DEPLOY_ENV"
|
||||||
|
print_info "时间戳: $TIMESTAMP"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 步骤 1: 代码构建
|
||||||
|
print_info "步骤 1: 代码构建..."
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
# 检查虚拟环境
|
||||||
|
if [ ! -d "venv" ]; then
|
||||||
|
print_warning "虚拟环境不存在,正在创建..."
|
||||||
|
python3 -m venv venv
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
print_info "激活虚拟环境..."
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 升级依赖
|
||||||
|
print_info "升级项目依赖..."
|
||||||
|
pip install --upgrade pip wheel
|
||||||
|
pip install -e ".[alpha,dev]"
|
||||||
|
|
||||||
|
print_success "代码构建完成"
|
||||||
|
|
||||||
|
# 步骤 2: 代码检查
|
||||||
|
print_info "步骤 2: 代码质量检查..."
|
||||||
|
|
||||||
|
# 运行代码检查
|
||||||
|
if command -v ruff &> /dev/null; then
|
||||||
|
print_info "运行 Ruff 代码检查..."
|
||||||
|
ruff check sanguo/
|
||||||
|
print_success "代码检查通过"
|
||||||
|
else
|
||||||
|
print_warning "Ruff 未安装,跳过代码检查"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 步骤 3: 运行测试
|
||||||
|
print_info "步骤 3: 运行测试..."
|
||||||
|
|
||||||
|
if [ -d "tests" ]; then
|
||||||
|
if command -v pytest &> /dev/null; then
|
||||||
|
print_info "运行测试..."
|
||||||
|
pytest tests/ -v
|
||||||
|
print_success "测试通过"
|
||||||
|
else
|
||||||
|
print_warning "pytest 未安装,跳过测试"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_warning "测试目录不存在,跳过测试"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 步骤 4: 构建部署包
|
||||||
|
print_info "步骤 4: 构建部署包..."
|
||||||
|
|
||||||
|
# 创建部署目录
|
||||||
|
DEPLOY_DIR="/tmp/sanguo_vnpy_deploy_$TIMESTAMP"
|
||||||
|
mkdir -p "$DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 复制代码
|
||||||
|
print_info "复制项目文件..."
|
||||||
|
cp -r sanguo/ "$DEPLOY_DIR/"
|
||||||
|
cp -r vnpy/ "$DEPLOY_DIR/" 2>/dev/null || true
|
||||||
|
cp pyproject.toml "$DEPLOY_DIR/"
|
||||||
|
cp README.md "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
# 构建 wheel 包
|
||||||
|
print_info "构建 wheel 包..."
|
||||||
|
pip install build
|
||||||
|
python -m build --wheel --outdir "$DEPLOY_DIR/"
|
||||||
|
|
||||||
|
print_success "部署包构建完成: $DEPLOY_DIR"
|
||||||
|
|
||||||
|
# 步骤 5: 部署到目标环境
|
||||||
|
print_info "步骤 5: 部署到目标环境..."
|
||||||
|
|
||||||
|
if [ "$DEPLOY_ENV" = "production" ]; then
|
||||||
|
print_info "生产环境部署..."
|
||||||
|
# 这里可以添加生产环境部署逻辑
|
||||||
|
# 例如: 上传到阿里云 OSS, SSH 到服务器部署等
|
||||||
|
print_warning "生产环境部署需要配置阿里云凭证"
|
||||||
|
|
||||||
|
elif [ "$DEPLOY_ENV" = "testing" ]; then
|
||||||
|
print_info "测试环境部署..."
|
||||||
|
print_success "测试环境部署完成"
|
||||||
|
|
||||||
|
else
|
||||||
|
print_info "本地开发环境部署..."
|
||||||
|
print_success "本地部署完成"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 步骤 6: 验证部署
|
||||||
|
print_info "步骤 6: 验证部署..."
|
||||||
|
|
||||||
|
# 验证模块导入
|
||||||
|
print_info "验证模块导入..."
|
||||||
|
python -c "import sanguo; import vnpy; print('✅ 模块导入成功')"
|
||||||
|
|
||||||
|
print_success "部署验证通过"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================================================"
|
||||||
|
echo "🎉 部署完成!"
|
||||||
|
echo "========================================================================"
|
||||||
|
echo "部署时间: $(date)"
|
||||||
|
echo "部署环境: $DEPLOY_ENV"
|
||||||
|
echo "部署包: $DEPLOY_DIR"
|
||||||
|
echo "========================================================================"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# sanguo_vnpy 阿里云生产环境 Terraform 配置
|
||||||
|
# 版本: v1.0
|
||||||
|
# 作者: 姜维(后勤总督)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
alicloud = {
|
||||||
|
source = "aliyun/alicloud"
|
||||||
|
version = ">= 1.212.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "alicloud" {
|
||||||
|
access_key = var.alicloud_access_key
|
||||||
|
secret_key = var.alicloud_secret_key
|
||||||
|
region = var.alicloud_region
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 变量定义
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
variable "alicloud_access_key" {
|
||||||
|
description = "阿里云 Access Key"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "alicloud_secret_key" {
|
||||||
|
description = "阿里云 Secret Key"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "alicloud_region" {
|
||||||
|
description = "阿里云区域"
|
||||||
|
type = string
|
||||||
|
default = "cn-hangzhou"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "environment" {
|
||||||
|
description = "环境类型: production/testing/development"
|
||||||
|
type = string
|
||||||
|
default = "production"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "instance_type" {
|
||||||
|
description = "ECS 实例规格"
|
||||||
|
type = string
|
||||||
|
default = "ecs.c6.large"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "vpc_cidr" {
|
||||||
|
description = "VPC CIDR 块"
|
||||||
|
type = string
|
||||||
|
default = "10.0.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "vswitch_cidr" {
|
||||||
|
description = "虚拟交换机 CIDR 块"
|
||||||
|
type = string
|
||||||
|
default = "10.0.0.0/24"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# VPC 网络
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
resource "alicloud_vpc" "sanguo_vpc" {
|
||||||
|
vpc_name = "sanguo-vnpy-${var.environment}-vpc"
|
||||||
|
cidr_block = var.vpc_cidr
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "alicloud_vswitch" "sanguo_vswitch" {
|
||||||
|
vswitch_name = "sanguo-vnpy-${var.environment}-vswitch"
|
||||||
|
vpc_id = alicloud_vpc.sanguo_vpc.id
|
||||||
|
cidr_block = var.vswitch_cidr
|
||||||
|
zone_id = "${var.alicloud_region}-a"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 安全组
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
resource "alicloud_security_group" "sanguo_sg" {
|
||||||
|
name = "sanguo-vnpy-${var.environment}-sg"
|
||||||
|
description = "sanguo_vnpy ${var.environment} 安全组"
|
||||||
|
vpc_id = alicloud_vpc.sanguo_vpc.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "alicloud_security_group_rule" "allow_ssh" {
|
||||||
|
type = "ingress"
|
||||||
|
ip_protocol = "tcp"
|
||||||
|
nic_type = "intranet"
|
||||||
|
policy = "accept"
|
||||||
|
port_range = "22/22"
|
||||||
|
priority = 1
|
||||||
|
security_group_id = alicloud_security_group.sanguo_sg.id
|
||||||
|
cidr_ip = "0.0.0.0/0"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "alicloud_security_group_rule" "allow_http" {
|
||||||
|
type = "ingress"
|
||||||
|
ip_protocol = "tcp"
|
||||||
|
nic_type = "intranet"
|
||||||
|
policy = "accept"
|
||||||
|
port_range = "80/80"
|
||||||
|
priority = 2
|
||||||
|
security_group_id = alicloud_security_group.sanguo_sg.id
|
||||||
|
cidr_ip = "0.0.0.0/0"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "alicloud_security_group_rule" "allow_vnpy" {
|
||||||
|
type = "ingress"
|
||||||
|
ip_protocol = "tcp"
|
||||||
|
nic_type = "intranet"
|
||||||
|
policy = "accept"
|
||||||
|
port_range = "8080/8080"
|
||||||
|
priority = 3
|
||||||
|
security_group_id = alicloud_security_group.sanguo_sg.id
|
||||||
|
cidr_ip = "0.0.0.0/0"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# ECS 实例
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
resource "alicloud_instance" "sanguo_ecs" {
|
||||||
|
instance_name = "sanguo-vnpy-${var.environment}-ecs"
|
||||||
|
availability_zone = "${var.alicloud_region}-a"
|
||||||
|
instance_type = var.instance_type
|
||||||
|
security_groups = [alicloud_security_group.sanguo_sg.id]
|
||||||
|
vswitch_id = alicloud_vswitch.sanguo_vswitch.id
|
||||||
|
internet_charge_type = "PayByTraffic"
|
||||||
|
internet_max_bandwidth_out = 100
|
||||||
|
|
||||||
|
system_disk_size = 40
|
||||||
|
system_disk_category = "cloud_efficiency"
|
||||||
|
|
||||||
|
image_id = "ubuntu_22_04_x64_20G_alibase_20240228.vhd"
|
||||||
|
|
||||||
|
password = var.ecs_password
|
||||||
|
instance_charge_type = "PostPaid"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ecs_password" {
|
||||||
|
description = "ECS 实例密码"
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
default = "Sanguo@2024!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# OSS 对象存储
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
resource "alicloud_oss_bucket" "sanguo_oss" {
|
||||||
|
bucket = "sanguo-vnpy-${var.environment}-data"
|
||||||
|
acl = "private"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RDS 数据库(可选)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
resource "alicloud_db_instance" "sanguo_rds" {
|
||||||
|
count = var.enable_rds ? 1 : 0
|
||||||
|
|
||||||
|
engine = "MySQL"
|
||||||
|
engine_version = "8.0"
|
||||||
|
instance_type = "rds.mysql.s2.large"
|
||||||
|
instance_storage = 20
|
||||||
|
vswitch_id = alicloud_vswitch.sanguo_vswitch.id
|
||||||
|
security_ips = ["0.0.0.0/0"]
|
||||||
|
db_instance_name = "sanguo-vnpy-${var.environment}-rds"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "enable_rds" {
|
||||||
|
description = "是否启用 RDS 数据库"
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 输出
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
output "vpc_id" {
|
||||||
|
description = "VPC ID"
|
||||||
|
value = alicloud_vpc.sanguo_vpc.id
|
||||||
|
}
|
||||||
|
|
||||||
|
output "ecs_public_ip" {
|
||||||
|
description = "ECS 公网 IP"
|
||||||
|
value = alicloud_instance.sanguo_ecs.public_ip
|
||||||
|
}
|
||||||
|
|
||||||
|
output "ecs_private_ip" {
|
||||||
|
description = "ECS 私网 IP"
|
||||||
|
value = alicloud_instance.sanguo_ecs.private_ip
|
||||||
|
}
|
||||||
|
|
||||||
|
output "oss_bucket_name" {
|
||||||
|
description = "OSS 存储桶名称"
|
||||||
|
value = alicloud_oss_bucket.sanguo_oss.bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
output "ecs_ssh_command" {
|
||||||
|
description = "SSH 连接命令"
|
||||||
|
value = "ssh root@${alicloud_instance.sanguo_ecs.public_ip}"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "vnpy_web_url" {
|
||||||
|
description = "vn.py Web 界面地址"
|
||||||
|
value = "http://${alicloud_instance.sanguo_ecs.public_ip}:8080"
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
# sanguo_vnpy 阿里云生产环境应急响应方案
|
||||||
|
|
||||||
|
**版本**: v1.0
|
||||||
|
**作者**: 姜维(后勤总督)
|
||||||
|
**日期**: 2026-03-21
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 应急响应原则
|
||||||
|
|
||||||
|
### 1. 快速响应原则
|
||||||
|
- **5分钟内**:发现问题并启动响应流程
|
||||||
|
- **15分钟内**:完成初步诊断和影响评估
|
||||||
|
- **30分钟内**:确定并执行恢复方案
|
||||||
|
|
||||||
|
### 2. 优先级原则
|
||||||
|
- **P0(严重)**:系统完全不可用,数据丢失风险
|
||||||
|
- **P1(高)**:核心功能异常,影响主要交易
|
||||||
|
- **P2(中)**:次要功能异常,不影响核心交易
|
||||||
|
- **P3(低)**:轻微问题,用户体验影响
|
||||||
|
|
||||||
|
### 3. 数据安全原则
|
||||||
|
- **先备份,后操作**:任何修复操作前先备份数据
|
||||||
|
- **日志优先**:优先保存和分析日志,避免二次故障
|
||||||
|
- **最小化变更**:使用最小必要的操作修复问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 问题诊断流程
|
||||||
|
|
||||||
|
### 1. 监控告警触发
|
||||||
|
|
||||||
|
#### 告警来源
|
||||||
|
1. **系统监控**:Prometheus + Grafana
|
||||||
|
2. **应用监控**:sanguo_vnpy 内部健康检查
|
||||||
|
3. **业务监控**:策略执行异常告警
|
||||||
|
4. **用户反馈**:用户上报的问题
|
||||||
|
|
||||||
|
#### 告警级别对应
|
||||||
|
| 告警类型 | 影响 | 响应级别 |
|
||||||
|
|---------|------|---------|
|
||||||
|
| 实例宕机 | 系统不可用 | P0 |
|
||||||
|
| CPU > 90% 5分钟 | 性能下降 | P1 |
|
||||||
|
| 内存 > 90% 5分钟 | 可能OOM | P1 |
|
||||||
|
| 磁盘 > 95% | 数据写入失败 | P0 |
|
||||||
|
| vn.py 进程消失 | 应用不可用 | P0 |
|
||||||
|
| 策略执行失败 | 业务影响 | P1 |
|
||||||
|
|
||||||
|
### 2. 快速诊断步骤
|
||||||
|
|
||||||
|
#### 步骤 1: 检查系统状态
|
||||||
|
```bash
|
||||||
|
# 1. 检查服务器是否在线
|
||||||
|
ping <server-ip>
|
||||||
|
|
||||||
|
# 2. SSH 登录(如果可能)
|
||||||
|
ssh root@<server-ip>
|
||||||
|
|
||||||
|
# 3. 检查系统资源
|
||||||
|
top
|
||||||
|
htop
|
||||||
|
df -h
|
||||||
|
free -m
|
||||||
|
|
||||||
|
# 4. 检查网络
|
||||||
|
ping 8.8.8.8
|
||||||
|
curl -I https://www.aliyun.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 2: 检查服务状态
|
||||||
|
```bash
|
||||||
|
# 1. 检查 vn.py 进程
|
||||||
|
ps aux | grep -i vnpy
|
||||||
|
ps aux | grep -i python
|
||||||
|
|
||||||
|
# 2. 检查端口监听
|
||||||
|
netstat -tlnp | grep 8080
|
||||||
|
ss -tlnp | grep 8080
|
||||||
|
|
||||||
|
# 3. 检查监控服务
|
||||||
|
systemctl status prometheus
|
||||||
|
systemctl status node_exporter
|
||||||
|
systemctl status grafana-server
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 3: 检查日志
|
||||||
|
```bash
|
||||||
|
# 1. 系统日志
|
||||||
|
tail -100 /var/log/syslog
|
||||||
|
tail -100 /var/log/messages
|
||||||
|
|
||||||
|
# 2. 应用日志
|
||||||
|
tail -100 /path/to/sanguo_vnpy/logs/app.log
|
||||||
|
tail -100 /path/to/sanguo_vnpy/logs/error.log
|
||||||
|
|
||||||
|
# 3. 云服务商日志
|
||||||
|
# 阿里云控制台查看云服务器监控和事件
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 常见问题应急处理
|
||||||
|
|
||||||
|
### 场景 1: 实例完全宕机(P0)
|
||||||
|
|
||||||
|
#### 现象
|
||||||
|
- Ping 无响应
|
||||||
|
- SSH 无法连接
|
||||||
|
- 监控显示实例离线
|
||||||
|
|
||||||
|
#### 应急处理
|
||||||
|
1. **立即备份**(如果还能访问)
|
||||||
|
```bash
|
||||||
|
# 尝试通过阿里云控制台创建快照
|
||||||
|
# 快照命名: sanguo-vnpy-$(date +%Y%m%d-%H%M)-emergency
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **重启实例**
|
||||||
|
- 阿里云控制台 → 实例 → 重启
|
||||||
|
- 等待 2-5 分钟
|
||||||
|
|
||||||
|
3. **如果重启失败**
|
||||||
|
- 使用可用快照回滚
|
||||||
|
- 或从备份数据重建实例
|
||||||
|
|
||||||
|
4. **验证恢复**
|
||||||
|
```bash
|
||||||
|
# 检查服务是否恢复
|
||||||
|
ssh root@<server-ip>
|
||||||
|
ps aux | grep vnpy
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景 2: vn.py 进程崩溃(P0)
|
||||||
|
|
||||||
|
#### 现象
|
||||||
|
- 实例在线,但 vn.py 进程消失
|
||||||
|
- 端口 8080 无响应
|
||||||
|
- 应用监控告警
|
||||||
|
|
||||||
|
#### 应急处理
|
||||||
|
1. **保存崩溃现场**
|
||||||
|
```bash
|
||||||
|
# 保存系统状态
|
||||||
|
dmesg > /tmp/dmesg-$(date +%Y%m%d-%H%M).log
|
||||||
|
vmstat 1 5 > /tmp/vmstat-$(date +%Y%m%d-%H%M).log
|
||||||
|
|
||||||
|
# 保存应用日志
|
||||||
|
cp -r /path/to/sanguo_vnpy/logs /tmp/logs-backup-$(date +%Y%m%d-%H%M)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **检查崩溃原因**
|
||||||
|
```bash
|
||||||
|
# 检查系统日志中的 OOM
|
||||||
|
grep -i "out of memory" /var/log/syslog
|
||||||
|
grep -i "killed process" /var/log/syslog
|
||||||
|
|
||||||
|
# 检查应用错误日志
|
||||||
|
tail -200 /path/to/sanguo_vnpy/logs/error.log
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **快速重启服务**
|
||||||
|
```bash
|
||||||
|
# 进入项目目录
|
||||||
|
cd /path/to/sanguo_vnpy
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 启动 vn.py
|
||||||
|
python -m vnpy &
|
||||||
|
|
||||||
|
# 或者使用服务管理
|
||||||
|
systemctl start sanguo-vnpy
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **验证恢复**
|
||||||
|
```bash
|
||||||
|
# 检查进程
|
||||||
|
ps aux | grep vnpy
|
||||||
|
|
||||||
|
# 检查端口
|
||||||
|
curl http://localhost:8080/health
|
||||||
|
|
||||||
|
# 检查监控
|
||||||
|
# 确认 Prometheus 数据恢复
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景 3: 磁盘空间满(P0)
|
||||||
|
|
||||||
|
#### 现象
|
||||||
|
- 磁盘使用率 > 95%
|
||||||
|
- 数据写入失败
|
||||||
|
- 应用无法保存数据
|
||||||
|
|
||||||
|
#### 应急处理
|
||||||
|
1. **立即清理临时文件**
|
||||||
|
```bash
|
||||||
|
# 清理系统临时文件
|
||||||
|
rm -rf /tmp/*
|
||||||
|
|
||||||
|
# 清理应用缓存
|
||||||
|
rm -rf /path/to/sanguo_vnpy/cache/*
|
||||||
|
|
||||||
|
# 清理旧日志(保留最近7天)
|
||||||
|
find /path/to/sanguo_vnpy/logs -name "*.log" -mtime +7 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **检查大文件**
|
||||||
|
```bash
|
||||||
|
# 查找大于 100MB 的文件
|
||||||
|
find / -type f -size +100M -exec ls -lh {} \;
|
||||||
|
|
||||||
|
# 检查数据目录
|
||||||
|
du -sh /path/to/sanguo_vnpy/data
|
||||||
|
du -sh /path/to/sanguo_vnpy/results
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **扩容磁盘(如果需要)**
|
||||||
|
- 阿里云控制台 → 云盘 → 扩容
|
||||||
|
- 或挂载新的数据盘
|
||||||
|
|
||||||
|
4. **验证恢复**
|
||||||
|
```bash
|
||||||
|
df -h
|
||||||
|
# 确认使用率下降到安全范围(< 80%)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 场景 4: 数据库连接失败(P1)
|
||||||
|
|
||||||
|
#### 现象
|
||||||
|
- 应用报错无法连接数据库
|
||||||
|
- 策略无法获取数据
|
||||||
|
- 回测无法执行
|
||||||
|
|
||||||
|
#### 应急处理
|
||||||
|
1. **检查数据库状态**
|
||||||
|
```bash
|
||||||
|
# 如果使用 RDS
|
||||||
|
# 阿里云控制台检查 RDS 状态
|
||||||
|
|
||||||
|
# 如果使用本地 SQLite
|
||||||
|
ls -lh /path/to/sanguo_vnpy/data/*.db
|
||||||
|
# 检查文件权限和完整性
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **网络连接测试**
|
||||||
|
```bash
|
||||||
|
# 测试数据库连接
|
||||||
|
telnet <db-host> <db-port>
|
||||||
|
nc -zv <db-host> <db-port>
|
||||||
|
|
||||||
|
# 检查安全组
|
||||||
|
# 确认应用服务器 IP 在数据库白名单中
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **快速恢复方案**
|
||||||
|
```bash
|
||||||
|
# 方案 A: 切换到本地缓存数据
|
||||||
|
# 修改配置使用 akshare 直接获取
|
||||||
|
|
||||||
|
# 方案 B: 从备份恢复数据库
|
||||||
|
# 恢复最近的数据库备份
|
||||||
|
|
||||||
|
# 方案 C: 重启数据库服务
|
||||||
|
# 如果是自建数据库,重启服务
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 应急响应检查清单
|
||||||
|
|
||||||
|
### 响应前检查
|
||||||
|
- [ ] 确认告警级别和影响范围
|
||||||
|
- [ ] 通知相关人员(诸葛亮/主公)
|
||||||
|
- [ ] 保存当前系统状态和日志
|
||||||
|
- [ ] 准备回滚方案
|
||||||
|
|
||||||
|
### 响应中检查
|
||||||
|
- [ ] 执行诊断步骤,确定根因
|
||||||
|
- [ ] 执行最小必要的修复操作
|
||||||
|
- [ ] 持续监控系统状态
|
||||||
|
- [ ] 记录所有操作和时间点
|
||||||
|
|
||||||
|
### 响应后检查
|
||||||
|
- [ ] 验证核心功能恢复正常
|
||||||
|
- [ ] 验证监控数据正常
|
||||||
|
- [ ] 验证业务数据完整性
|
||||||
|
- [ ] 总结故障原因和改进措施
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 联络清单
|
||||||
|
|
||||||
|
### 紧急联络
|
||||||
|
- **总军师(诸葛亮)**:负责决策和协调
|
||||||
|
- **主公**:最终决策和资源协调
|
||||||
|
- **赵云**:数据库和数据相关问题
|
||||||
|
- **关羽**:回测引擎和风控问题
|
||||||
|
- **张飞**:API 兼容层问题
|
||||||
|
- **司马懿**:安全和合规问题
|
||||||
|
|
||||||
|
### 阿里云支持
|
||||||
|
- **阿里云控制台**:https://home.console.aliyun.com
|
||||||
|
- **阿里云工单**:紧急问题提交工单
|
||||||
|
- **阿里云电话**:400-910-0100
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 事后复盘
|
||||||
|
|
||||||
|
### 复盘会议
|
||||||
|
- **时间**:故障恢复后 24 小时内
|
||||||
|
- **参与人**:所有相关人员
|
||||||
|
- **内容**:
|
||||||
|
1. 故障回顾和时间线
|
||||||
|
2. 根因分析
|
||||||
|
3. 响应流程评估
|
||||||
|
4. 改进措施讨论
|
||||||
|
|
||||||
|
### 改进措施
|
||||||
|
- 技术改进:防止同类故障再次发生
|
||||||
|
- 流程改进:优化响应流程
|
||||||
|
- 监控改进:完善告警和监控
|
||||||
|
- 文档改进:更新应急方案
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**本应急方案会持续更新,确保生产环境安全稳定运行!** 🚛
|
||||||
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# sanguo_vnpy 阿里云生产环境监控系统部署脚本
|
||||||
|
# 版本: v1.0
|
||||||
|
# 作者: 姜维(后勤总督)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "========================================================================"
|
||||||
|
echo "🚀 sanguo_vnpy 监控系统部署"
|
||||||
|
echo "========================================================================"
|
||||||
|
|
||||||
|
# 颜色定义
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
echo -e "${BLUE}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 步骤 1: 安装系统依赖
|
||||||
|
print_info "步骤 1: 安装系统依赖..."
|
||||||
|
|
||||||
|
if command -v apt-get &> /dev/null; then
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y prometheus node_exporter grafana nginx
|
||||||
|
elif command -v yum &> /dev/null; then
|
||||||
|
sudo yum install -y epel-release
|
||||||
|
sudo yum install -y prometheus node_exporter grafana nginx
|
||||||
|
else
|
||||||
|
print_error "不支持的操作系统"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "系统依赖安装完成"
|
||||||
|
|
||||||
|
# 步骤 2: 配置 Prometheus
|
||||||
|
print_info "步骤 2: 配置 Prometheus..."
|
||||||
|
|
||||||
|
sudo mkdir -p /etc/prometheus
|
||||||
|
sudo cat > /etc/prometheus/prometheus.yml << 'EOF'
|
||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
evaluation_interval: 15s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'prometheus'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9090']
|
||||||
|
|
||||||
|
- job_name: 'node_exporter'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9100']
|
||||||
|
|
||||||
|
- job_name: 'sanguo_vnpy'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:8080']
|
||||||
|
metrics_path: '/metrics'
|
||||||
|
|
||||||
|
alerting:
|
||||||
|
alertmanagers:
|
||||||
|
- static_configs:
|
||||||
|
- targets: ['localhost:9093']
|
||||||
|
|
||||||
|
rule_files:
|
||||||
|
- "/etc/prometheus/alerts.yml"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 步骤 3: 配置告警规则
|
||||||
|
print_info "步骤 3: 配置告警规则..."
|
||||||
|
|
||||||
|
sudo cat > /etc/prometheus/alerts.yml << 'EOF'
|
||||||
|
groups:
|
||||||
|
- name: sanguo_vnpy_alerts
|
||||||
|
rules:
|
||||||
|
- alert: InstanceDown
|
||||||
|
expr: up == 0
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "实例 {{ $labels.instance }} 已宕机"
|
||||||
|
description: "{{ $labels.job }} 实例 {{ $labels.instance }} 已宕机超过 5 分钟"
|
||||||
|
|
||||||
|
- alert: HighCPUUsage
|
||||||
|
expr: 100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "实例 {{ $labels.instance }} CPU 使用率过高"
|
||||||
|
description: "{{ $labels.instance }} CPU 使用率超过 80%"
|
||||||
|
|
||||||
|
- alert: HighMemoryUsage
|
||||||
|
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 80
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
annotations:
|
||||||
|
summary: "实例 {{ $labels.instance }} 内存使用率过高"
|
||||||
|
description: "{{ $labels.instance }} 内存使用率超过 80%"
|
||||||
|
|
||||||
|
- alert: DiskSpaceLow
|
||||||
|
expr: (1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100 > 90
|
||||||
|
for: 5m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "实例 {{ $labels.instance }} 磁盘空间不足"
|
||||||
|
description: "{{ $labels.instance }} 磁盘使用率超过 90%"
|
||||||
|
|
||||||
|
- alert: VnpyDown
|
||||||
|
expr: up{job="sanguo_vnpy"} == 0
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
annotations:
|
||||||
|
summary: "sanguo_vnpy 服务已宕机"
|
||||||
|
description: "sanguo_vnpy 服务已宕机超过 2 分钟,请立即检查!"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 步骤 4: 配置 Grafana
|
||||||
|
print_info "步骤 4: 配置 Grafana..."
|
||||||
|
|
||||||
|
sudo mkdir -p /etc/grafana/provisioning/datasources
|
||||||
|
sudo cat > /etc/grafana/provisioning/datasources/prometheus.yml << 'EOF'
|
||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
access: proxy
|
||||||
|
url: http://localhost:9090
|
||||||
|
isDefault: true
|
||||||
|
editable: true
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 步骤 5: 启动服务
|
||||||
|
print_info "步骤 5: 启动监控服务..."
|
||||||
|
|
||||||
|
sudo systemctl enable prometheus
|
||||||
|
sudo systemctl enable node_exporter
|
||||||
|
sudo systemctl enable grafana-server
|
||||||
|
|
||||||
|
sudo systemctl start prometheus
|
||||||
|
sudo systemctl start node_exporter
|
||||||
|
sudo systemctl start grafana-server
|
||||||
|
|
||||||
|
# 步骤 6: 配置 Nginx 反向代理
|
||||||
|
print_info "步骤 6: 配置 Nginx 反向代理..."
|
||||||
|
|
||||||
|
sudo cat > /etc/nginx/sites-available/sanguo-monitoring << 'EOF'
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location /grafana/ {
|
||||||
|
proxy_pass http://localhost:3000/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /prometheus/ {
|
||||||
|
proxy_pass http://localhost:9090/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /node-exporter/ {
|
||||||
|
proxy_pass http://localhost:9100/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo ln -sf /etc/nginx/sites-available/sanguo-monitoring /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
|
||||||
|
# 步骤 7: 显示访问信息
|
||||||
|
print_success "监控系统部署完成!"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================================================"
|
||||||
|
echo "📊 监控系统访问信息"
|
||||||
|
echo "========================================================================"
|
||||||
|
echo "Grafana: http://$(hostname -I | awk '{print $1}'):3000"
|
||||||
|
echo "Prometheus: http://$(hostname -I | awk '{print $1}'):9090"
|
||||||
|
echo "Node Exporter: http://$(hostname -I | awk '{print $1}'):9100"
|
||||||
|
echo ""
|
||||||
|
echo "默认 Grafana 账号: admin / admin"
|
||||||
|
echo "========================================================================"
|
||||||
|
echo ""
|
||||||
Binary file not shown.
@@ -0,0 +1,166 @@
|
|||||||
|
"""
|
||||||
|
高频算法性能基准测试
|
||||||
|
测试不同实现方式的性能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime
|
||||||
|
from high_frequency_signal import (
|
||||||
|
TechnicalFactorCalculator,
|
||||||
|
PerformanceMonitor,
|
||||||
|
BarData,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_test_data(n: int = 10000) -> pd.DataFrame:
|
||||||
|
"""生成随机测试数据"""
|
||||||
|
np.random.seed(42)
|
||||||
|
|
||||||
|
# 生成随机游走价格
|
||||||
|
returns = np.random.normal(0.0001, 0.01, n)
|
||||||
|
price = 100 * np.exp(np.cumsum(returns))
|
||||||
|
|
||||||
|
# 生成OHLCV
|
||||||
|
high = price * (1 + np.random.uniform(0, 0.01, n))
|
||||||
|
low = price * (1 - np.random.uniform(0, 0.01, n))
|
||||||
|
open_ = price * (1 + np.random.normal(0, 0.005, n))
|
||||||
|
volume = np.random.randint(100000, 10000000, n)
|
||||||
|
|
||||||
|
df = pd.DataFrame({
|
||||||
|
'open': open_,
|
||||||
|
'high': high,
|
||||||
|
'low': low,
|
||||||
|
'close': price,
|
||||||
|
'volume': volume,
|
||||||
|
})
|
||||||
|
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def benchmark_technical_calculator():
|
||||||
|
"""测试技术因子计算器性能"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("技术因子计算器性能基准测试")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
calc = TechnicalFactorCalculator()
|
||||||
|
monitor = PerformanceMonitor()
|
||||||
|
|
||||||
|
# 测试不同数据大小
|
||||||
|
for size in [100, 1000, 10000]:
|
||||||
|
df = generate_test_data(size)
|
||||||
|
close = df['close'].values
|
||||||
|
high = df['high'].values
|
||||||
|
low = df['low'].values
|
||||||
|
volume = df['volume'].values
|
||||||
|
|
||||||
|
n_runs = 1000 if size <= 1000 else 100
|
||||||
|
|
||||||
|
# 测试MA
|
||||||
|
start_time = time.time()
|
||||||
|
for _ in range(n_runs):
|
||||||
|
_ = calc.calculate_ma(close, 20)
|
||||||
|
elapsed = (time.time() - start_time) * 1000
|
||||||
|
print(f"MA {size} points x{n_runs} runs: {elapsed:.2f} ms "
|
||||||
|
f"({elapsed/n_runs:.3f} ms/run)")
|
||||||
|
|
||||||
|
# 测试MACD
|
||||||
|
start_time = time.time()
|
||||||
|
for _ in range(n_runs):
|
||||||
|
_ = calc.calculate_macd(close, 12, 26, 9)
|
||||||
|
elapsed = (time.time() - start_time) * 1000
|
||||||
|
print(f"MACD {size} points x{n_runs} runs: {elapsed:.2f} ms "
|
||||||
|
f"({elapsed/n_runs:.3f} ms/run)")
|
||||||
|
|
||||||
|
# 测试RSI
|
||||||
|
start_time = time.time()
|
||||||
|
for _ in range(n_runs):
|
||||||
|
_ = calc.calculate_rsi(close, 14)
|
||||||
|
elapsed = (time.time() - start_time) * 1000
|
||||||
|
print(f"RSI {size} points x{n_runs} runs: {elapsed:.2f} ms "
|
||||||
|
f"({elapsed/n_runs:.3f} ms/run)")
|
||||||
|
|
||||||
|
# 测试布林带
|
||||||
|
start_time = time.time()
|
||||||
|
for _ in range(n_runs):
|
||||||
|
_ = calc.calculate_bollinger_bands(close, 20, 2.0)
|
||||||
|
elapsed = (time.time() - start_time) * 1000
|
||||||
|
print(f"Bollinger {size} points x{n_runs} runs: {elapsed:.2f} ms "
|
||||||
|
f"({elapsed/n_runs:.3f} ms/run)")
|
||||||
|
|
||||||
|
# 测试ATR
|
||||||
|
start_time = time.time()
|
||||||
|
for _ in range(n_runs):
|
||||||
|
_ = calc.calculate_atr(high, low, close, 14)
|
||||||
|
elapsed = (time.time() - start_time) * 1000
|
||||||
|
print(f"ATR {size} points x{n_runs} runs: {elapsed:.2f} ms "
|
||||||
|
f"({elapsed/n_runs:.3f} ms/run)")
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def benchmark_full_pipeline():
|
||||||
|
"""测试完整信号生成流水线"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("完整信号生成流水线性能基准测试")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
from high_frequency_signal import HighFrequencySignalGenerator
|
||||||
|
|
||||||
|
calc = TechnicalFactorCalculator()
|
||||||
|
generator = HighFrequencySignalGenerator()
|
||||||
|
monitor = PerformanceMonitor()
|
||||||
|
|
||||||
|
# 生成测试数据
|
||||||
|
n_days = 252 * 5
|
||||||
|
df = generate_test_data(n_days)
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 模拟逐日处理
|
||||||
|
signals = []
|
||||||
|
for i in range(20, len(df)):
|
||||||
|
data = df.iloc[:i+1]
|
||||||
|
bar = BarData(
|
||||||
|
code="TEST",
|
||||||
|
datetime=datetime.now(),
|
||||||
|
open=data['open'].iloc[-1],
|
||||||
|
high=data['high'].iloc[-1],
|
||||||
|
low=data['low'].iloc[-1],
|
||||||
|
close=data['close'].iloc[-1],
|
||||||
|
volume=data['volume'].iloc[-1],
|
||||||
|
amount=data['volume'].iloc[-1] * data['close'].iloc[-1]
|
||||||
|
)
|
||||||
|
monitor.on_process_start()
|
||||||
|
signal = generator.generate_signal_from_bar(bar, data)
|
||||||
|
monitor.on_process_end()
|
||||||
|
if signal:
|
||||||
|
signals.append(signal)
|
||||||
|
|
||||||
|
elapsed = (time.time() - start_time) * 1000
|
||||||
|
print(f"完整回测 {n_days} 根K线,生成 {len(signals)} 个信号")
|
||||||
|
print(f"总耗时: {elapsed:.2f} ms")
|
||||||
|
print(f"平均每根K线: {elapsed/n_days:.3f} ms")
|
||||||
|
print()
|
||||||
|
|
||||||
|
monitor.print_stats()
|
||||||
|
|
||||||
|
stats = generator.get_performance_stats()
|
||||||
|
print(f"\n信号统计: {stats}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
benchmark_technical_calculator()
|
||||||
|
print()
|
||||||
|
benchmark_full_pipeline()
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("基准测试完成")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
"""
|
||||||
|
高频交易信号生成器
|
||||||
|
支持tick级和分钟级数据处理
|
||||||
|
低延迟信号生成
|
||||||
|
支持向量化计算
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import List, Dict, Tuple, Optional, Union
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TickData:
|
||||||
|
"""tick数据结构"""
|
||||||
|
code: str
|
||||||
|
datetime: datetime
|
||||||
|
price: float
|
||||||
|
volume: int
|
||||||
|
amount: float
|
||||||
|
direction: int = 0 # 1买-1卖0平
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BarData:
|
||||||
|
"""K线数据结构"""
|
||||||
|
code: str
|
||||||
|
datetime: datetime
|
||||||
|
open: float
|
||||||
|
high: float
|
||||||
|
low: float
|
||||||
|
close: float
|
||||||
|
volume: float
|
||||||
|
amount: float
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Signal:
|
||||||
|
"""交易信号结构"""
|
||||||
|
code: str
|
||||||
|
datetime: datetime
|
||||||
|
direction: int # 1多-1空0无
|
||||||
|
strength: float # 信号强度 0-1
|
||||||
|
price: float # 信号价格
|
||||||
|
expected_price: float # 预期目标价
|
||||||
|
|
||||||
|
|
||||||
|
class HighFrequencyDataPipeline:
|
||||||
|
"""高频数据流处理管道"""
|
||||||
|
|
||||||
|
def __init__(self, window_size: int = 100):
|
||||||
|
self.window_size = window_size
|
||||||
|
self._buffer: Dict[str, List[TickData]] = {}
|
||||||
|
self._last_bar: Dict[str, Optional[BarData]] = {}
|
||||||
|
self._tick_count = 0
|
||||||
|
|
||||||
|
def on_tick(self, tick: TickData) -> Optional[Signal]:
|
||||||
|
"""处理新tick,可能生成信号"""
|
||||||
|
if tick.code not in self._buffer:
|
||||||
|
self._buffer[tick.code] = []
|
||||||
|
|
||||||
|
buffer = self._buffer[tick.code]
|
||||||
|
buffer.append(tick)
|
||||||
|
|
||||||
|
# 保持窗口大小
|
||||||
|
if len(buffer) > self.window_size:
|
||||||
|
buffer.pop(0)
|
||||||
|
|
||||||
|
self._tick_count += 1
|
||||||
|
|
||||||
|
# 这里可以触发滚动计算
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_tick_buffer(self, code: str) -> List[TickData]:
|
||||||
|
"""获取tick缓冲"""
|
||||||
|
return self._buffer.get(code, [])
|
||||||
|
|
||||||
|
def statistics(self) -> Dict:
|
||||||
|
"""获取统计信息"""
|
||||||
|
return {
|
||||||
|
'total_ticks': self._tick_count,
|
||||||
|
'total_codes': len(self._buffer),
|
||||||
|
'buffer_size': sum(len(b) for b in self._buffer.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalFactorCalculator:
|
||||||
|
"""技术因子计算器 - 向量化实现"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.cache: Dict[str, pd.DataFrame] = {}
|
||||||
|
|
||||||
|
def calculate_ma(self, prices: np.ndarray, period: int) -> np.ndarray:
|
||||||
|
"""计算移动平均线"""
|
||||||
|
return pd.Series(prices).rolling(period).mean().values
|
||||||
|
|
||||||
|
def calculate_ema(self, prices: np.ndarray, period: int) -> np.ndarray:
|
||||||
|
"""计算指数移动平均线"""
|
||||||
|
return pd.Series(prices).ewm(span=period, adjust=False).mean().values
|
||||||
|
|
||||||
|
def calculate_macd(self, prices: np.ndarray,
|
||||||
|
fast_period: int = 12,
|
||||||
|
slow_period: int = 26,
|
||||||
|
signal_period: int = 9) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||||
|
"""计算MACD"""
|
||||||
|
ema_fast = self.calculate_ema(prices, fast_period)
|
||||||
|
ema_slow = self.calculate_ema(prices, slow_period)
|
||||||
|
dif = ema_fast - ema_slow
|
||||||
|
dea = self.calculate_ema(dif, signal_period)
|
||||||
|
macd = 2 * (dif - dea)
|
||||||
|
return dif, dea, macd
|
||||||
|
|
||||||
|
def calculate_bollinger_bands(self, prices: np.ndarray,
|
||||||
|
period: int = 20,
|
||||||
|
num_std: float = 2.0) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||||
|
"""计算布林带"""
|
||||||
|
middle = pd.Series(prices).rolling(period).mean().values
|
||||||
|
std = pd.Series(prices).rolling(period).std().values
|
||||||
|
upper = middle + num_std * std
|
||||||
|
lower = middle - num_std * std
|
||||||
|
return upper, middle, lower
|
||||||
|
|
||||||
|
def calculate_rsi(self, prices: np.ndarray, period: int = 14) -> np.ndarray:
|
||||||
|
"""计算RSI相对强弱指数"""
|
||||||
|
delta = np.diff(prices)
|
||||||
|
gain = np.where(delta > 0, delta, 0)
|
||||||
|
loss = np.where(delta < 0, -delta, 0)
|
||||||
|
|
||||||
|
avg_gain = np.zeros_like(prices)
|
||||||
|
avg_loss = np.zeros_like(prices)
|
||||||
|
|
||||||
|
# 第一个period没有数据
|
||||||
|
avg_gain[period] = np.mean(gain[:period])
|
||||||
|
avg_loss[period] = np.mean(loss[:period])
|
||||||
|
|
||||||
|
# Wilder's smoothing
|
||||||
|
for i in range(period + 1, len(prices)):
|
||||||
|
avg_gain[i] = (avg_gain[i-1] * (period - 1) + gain[i-1]) / period
|
||||||
|
avg_loss[i] = (avg_loss[i-1] * (period - 1) + loss[i-1]) / period
|
||||||
|
|
||||||
|
rs = avg_gain / (avg_loss + 1e-10)
|
||||||
|
rsi = 100 - (100 / (1 + rs))
|
||||||
|
return rsi
|
||||||
|
|
||||||
|
def calculate_atr(self, high: np.ndarray, low: np.ndarray,
|
||||||
|
close: np.ndarray, period: int = 14) -> np.ndarray:
|
||||||
|
"""计算ATR平均真实波幅"""
|
||||||
|
tr = np.zeros_like(high)
|
||||||
|
tr[0] = high[0] - low[0]
|
||||||
|
|
||||||
|
for i in range(1, len(high)):
|
||||||
|
tr1 = high[i] - low[i]
|
||||||
|
tr2 = abs(high[i] - close[i-1])
|
||||||
|
tr3 = abs(low[i] - close[i-1])
|
||||||
|
tr[i] = max(tr1, tr2, tr3)
|
||||||
|
|
||||||
|
atr = pd.Series(tr).rolling(period).mean().values
|
||||||
|
return atr
|
||||||
|
|
||||||
|
def calculate_volume_ma(self, volumes: np.ndarray, period: int) -> np.ndarray:
|
||||||
|
"""计算成交量移动平均"""
|
||||||
|
return pd.Series(volumes).rolling(period).mean().values
|
||||||
|
|
||||||
|
def calculate_obv(self, close: np.ndarray, volume: np.ndarray) -> np.ndarray:
|
||||||
|
"""计算OBV能量潮"""
|
||||||
|
obv = np.zeros_like(close)
|
||||||
|
obv[0] = volume[0]
|
||||||
|
|
||||||
|
for i in range(1, len(close)):
|
||||||
|
if close[i] > close[i-1]:
|
||||||
|
obv[i] = obv[i-1] + volume[i]
|
||||||
|
elif close[i] < close[i-1]:
|
||||||
|
obv[i] = obv[i-1] - volume[i]
|
||||||
|
else:
|
||||||
|
obv[i] = obv[i-1]
|
||||||
|
|
||||||
|
return obv
|
||||||
|
|
||||||
|
|
||||||
|
class HighFrequencySignalGenerator:
|
||||||
|
"""高频交易信号生成器
|
||||||
|
支持多种技术因子组合生成交易信号
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.factor_calculator = TechnicalFactorCalculator()
|
||||||
|
self.data_pipeline = HighFrequencyDataPipeline()
|
||||||
|
self.signal_history: List[Signal] = []
|
||||||
|
self.performance_stats = {
|
||||||
|
'total_signals': 0,
|
||||||
|
'long_signals': 0,
|
||||||
|
'short_signals': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_signal_from_bar(self, bar: BarData,
|
||||||
|
data: pd.DataFrame) -> Optional[Signal]:
|
||||||
|
"""从K线数据生成信号"""
|
||||||
|
# 计算各种技术因子
|
||||||
|
close = data['close'].values
|
||||||
|
high = data['high'].values
|
||||||
|
low = data['low'].values
|
||||||
|
volume = data['volume'].values
|
||||||
|
|
||||||
|
# MACD
|
||||||
|
dif, dea, macd = self.factor_calculator.calculate_macd(close)
|
||||||
|
|
||||||
|
# RSI
|
||||||
|
rsi = self.factor_calculator.calculate_rsi(close)
|
||||||
|
|
||||||
|
# 布林带
|
||||||
|
bb_upper, bb_mid, bb_lower = self.factor_calculator.calculate_bollinger_bands(close)
|
||||||
|
|
||||||
|
# ATR
|
||||||
|
atr = self.factor_calculator.calculate_atr(high, low, close, 14)
|
||||||
|
|
||||||
|
# 生成信号逻辑
|
||||||
|
current_rsi = rsi[-1]
|
||||||
|
current_macd = macd[-1]
|
||||||
|
current_dif = dif[-1]
|
||||||
|
current_dea = dea[-1]
|
||||||
|
current_price = close[-1]
|
||||||
|
|
||||||
|
# MACD金叉买入
|
||||||
|
if current_dif > current_dea and (dif[-2] <= dea[-2]) and current_rsi < 70:
|
||||||
|
signal = Signal(
|
||||||
|
code=bar.code,
|
||||||
|
datetime=bar.datetime,
|
||||||
|
direction=1,
|
||||||
|
strength=min((70 - current_rsi) / 70 + (current_dif - current_dea) / 1, 1.0),
|
||||||
|
price=current_price,
|
||||||
|
expected_price=current_price + 2 * atr[-1]
|
||||||
|
)
|
||||||
|
self._record_signal(signal)
|
||||||
|
return signal
|
||||||
|
|
||||||
|
# MACD死叉卖出
|
||||||
|
elif current_dif < current_dea and (dif[-2] >= dea[-2]) and current_rsi > 30:
|
||||||
|
signal = Signal(
|
||||||
|
code=bar.code,
|
||||||
|
datetime=bar.datetime,
|
||||||
|
direction=-1,
|
||||||
|
strength=min((current_rsi - 30) / 30 + (current_dea - current_dif) / 1, 1.0),
|
||||||
|
price=current_price,
|
||||||
|
expected_price=current_price - 2 * atr[-1]
|
||||||
|
)
|
||||||
|
self._record_signal(signal)
|
||||||
|
return signal
|
||||||
|
|
||||||
|
# 布林带突破
|
||||||
|
elif current_price < bb_lower[-1] and current_rsi < 30:
|
||||||
|
signal = Signal(
|
||||||
|
code=bar.code,
|
||||||
|
datetime=bar.datetime,
|
||||||
|
direction=1,
|
||||||
|
strength=(bb_lower[-1] - current_price) / (bb_lower[-1] * 0.05),
|
||||||
|
price=current_price,
|
||||||
|
expected_price=bb_mid[-1]
|
||||||
|
)
|
||||||
|
self._record_signal(signal)
|
||||||
|
return signal
|
||||||
|
|
||||||
|
elif current_price > bb_upper[-1] and current_rsi > 70:
|
||||||
|
signal = Signal(
|
||||||
|
code=bar.code,
|
||||||
|
datetime=bar.datetime,
|
||||||
|
direction=-1,
|
||||||
|
strength=(current_price - bb_upper[-1]) / (bb_upper[-1] * 0.05),
|
||||||
|
price=current_price,
|
||||||
|
expected_price=bb_mid[-1]
|
||||||
|
)
|
||||||
|
self._record_signal(signal)
|
||||||
|
return signal
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _record_signal(self, signal: Signal):
|
||||||
|
"""记录信号"""
|
||||||
|
self.signal_history.append(signal)
|
||||||
|
self.performance_stats['total_signals'] += 1
|
||||||
|
if signal.direction == 1:
|
||||||
|
self.performance_stats['long_signals'] += 1
|
||||||
|
elif signal.direction == -1:
|
||||||
|
self.performance_stats['short_signals'] += 1
|
||||||
|
|
||||||
|
def get_performance_stats(self) -> Dict:
|
||||||
|
"""获取性能统计"""
|
||||||
|
return self.performance_stats.copy()
|
||||||
|
|
||||||
|
|
||||||
|
class PerformanceMonitor:
|
||||||
|
"""性能监控器 - 监控高频算法性能"""
|
||||||
|
|
||||||
|
def __init__(self, window_size: int = 1000):
|
||||||
|
self.latencies: List[float] = []
|
||||||
|
self.window_size = window_size
|
||||||
|
self.processed_ticks = 0
|
||||||
|
self.start_time = datetime.now()
|
||||||
|
|
||||||
|
def on_process_start(self):
|
||||||
|
"""开始处理计时"""
|
||||||
|
self._start = datetime.now()
|
||||||
|
|
||||||
|
def on_process_end(self):
|
||||||
|
"""结束处理计时"""
|
||||||
|
end = datetime.now()
|
||||||
|
latency = (end - self._start).total_seconds() * 1000 # ms
|
||||||
|
self.latencies.append(latency)
|
||||||
|
self.processed_ticks += 1
|
||||||
|
|
||||||
|
# 保持窗口大小
|
||||||
|
if len(self.latencies) > self.window_size:
|
||||||
|
self.latencies.pop(0)
|
||||||
|
|
||||||
|
def get_stats(self) -> Dict:
|
||||||
|
"""获取性能统计"""
|
||||||
|
if not self.latencies:
|
||||||
|
return {
|
||||||
|
'latency_ms_avg': 0,
|
||||||
|
'latency_ms_p50': 0,
|
||||||
|
'latency_ms_p99': 0,
|
||||||
|
'total_processed': 0,
|
||||||
|
'tps': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
latencies_np = np.array(self.latencies)
|
||||||
|
elapsed = (datetime.now() - self.start_time).total_seconds()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'latency_ms_avg': float(latencies_np.mean()),
|
||||||
|
'latency_ms_p50': float(np.percentile(latencies_np, 50)),
|
||||||
|
'latency_ms_p99': float(np.percentile(latencies_np, 99)),
|
||||||
|
'latency_ms_max': float(latencies_np.max()),
|
||||||
|
'total_processed': self.processed_ticks,
|
||||||
|
'tps': self.processed_ticks / elapsed if elapsed > 0 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_stats(self):
|
||||||
|
"""打印性能统计"""
|
||||||
|
stats = self.get_stats()
|
||||||
|
print("=" * 60)
|
||||||
|
print("高频算法性能监控")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"平均延迟: {stats['latency_ms_avg']:.2f} ms")
|
||||||
|
print(f"P50延迟: {stats['latency_ms_p50']:.2f} ms")
|
||||||
|
print(f"P99延迟: {stats['latency_ms_p99']:.2f} ms")
|
||||||
|
print(f"最大延迟: {stats['latency_ms_max']:.2f} ms")
|
||||||
|
print(f"总处理tick数: {stats['total_processed']}")
|
||||||
|
print(f"每秒处理: {stats['tps']:.2f} ticks")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
|
||||||
|
# 导出
|
||||||
|
__all__ = [
|
||||||
|
'TickData',
|
||||||
|
'BarData',
|
||||||
|
'Signal',
|
||||||
|
'HighFrequencyDataPipeline',
|
||||||
|
'TechnicalFactorCalculator',
|
||||||
|
'HighFrequencySignalGenerator',
|
||||||
|
'PerformanceMonitor',
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user