feat: 开源准备和功能优化

- 添加 MIT 许可证
- 创建完整的项目 README.md
- 前端添加 GitHub 和 Twitter 链接
- 重构表名:copy_trading_accounts -> wallet_accounts
  - 更新 Account 实体类
  - 添加数据库迁移脚本 V3
  - 更新初始化脚本和文档
- 优化跟单订单类型:GTC -> FAK
  - 买入和卖出订单均使用 FAK (Fill-And-Kill)
  - 允许部分成交,未成交部分立即取消
  - 提高跟单响应速度和价格匹配准确性
This commit is contained in:
WrBug
2025-12-03 02:27:05 +08:00
parent 719b01508a
commit 7eb310b35e
8 changed files with 353 additions and 19 deletions
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2024 WrBug
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+231
View File
@@ -0,0 +1,231 @@
# PolyHermes
[![GitHub](https://img.shields.io/badge/GitHub-WrBug%2FPolyHermes-blue?logo=github)](https://github.com/WrBug/PolyHermes)
[![Twitter](https://img.shields.io/badge/Twitter-@quant__tr-blue?logo=twitter)](https://x.com/quant_tr)
一个功能强大的 Polymarket 预测市场跟单交易系统,支持自动化跟单、多账户管理、实时订单推送和统计分析。
## ✨ 功能特性
### 核心功能
- 🔐 **多账户管理**:支持通过私钥导入多个钱包账户,统一管理
- 👥 **Leader 管理**:添加和管理被跟单者(Leader),支持分类筛选(sports/crypto
- 📊 **跟单配置**:灵活的跟单模板配置,支持比例跟单和固定金额跟单
- 🔄 **自动跟单**:实时监控 Leader 交易,自动复制订单(支持买入和卖出)
- 📈 **订单跟踪**:完整的订单生命周期跟踪,包括买入、卖出和匹配记录
- 📊 **统计分析**:全局统计、Leader 统计、分类统计,支持时间范围筛选
- 💼 **仓位管理**:实时查看和管理持仓,支持仓位推送
- ⚙️ **系统管理**:代理配置、API 健康检查、实时状态监控
### 技术特性
- 🌐 **WebSocket 实时推送**:订单和仓位数据实时推送
- 🔒 **安全存储**:私钥和 API 凭证加密存储
- 📱 **响应式设计**:完美支持移动端和桌面端
- 🚀 **高性能**:异步处理、并发优化
- 🛡️ **风险控制**:每日亏损限制、订单数限制、价格容忍度等
## 🏗️ 技术栈
### 后端
- **框架**: Spring Boot 3.2.0
- **语言**: Kotlin 1.9.20
- **数据库**: MySQL 8.2.0
- **ORM**: Spring Data JPA
- **数据库迁移**: Flyway
- **HTTP 客户端**: Retrofit 2.9.0 + OkHttp 4.12.0
- **WebSocket**: Spring WebSocket
### 前端
- **框架**: React 18 + TypeScript
- **构建工具**: Vite
- **UI 库**: Ant Design 5.12.0
- **HTTP 客户端**: axios
- **状态管理**: Zustand
- **路由**: React Router 6
- **以太坊库**: ethers.js 6.9.0
## 📦 项目结构
```
polymarket-bot/
├── backend/ # 后端服务
│ ├── src/main/kotlin/ # Kotlin 源代码
│ │ ├── api/ # Polymarket API 接口定义
│ │ ├── controller/ # REST 控制器
│ │ ├── service/ # 业务逻辑服务
│ │ ├── entity/ # 数据库实体
│ │ ├── repository/ # 数据访问层
│ │ ├── dto/ # 数据传输对象
│ │ ├── websocket/ # WebSocket 处理
│ │ └── util/ # 工具类
│ └── src/main/resources/
│ ├── application.properties
│ └── db/migration/ # 数据库迁移脚本
├── frontend/ # 前端应用
│ ├── src/
│ │ ├── components/ # 公共组件
│ │ ├── pages/ # 页面组件
│ │ ├── services/ # API 服务
│ │ ├── types/ # TypeScript 类型
│ │ └── utils/ # 工具函数
│ └── package.json
├── docs/ # 文档
└── README.md
```
## 🚀 快速开始
### 前置要求
- JDK 17+
- Node.js 18+
- MySQL 8.0+
- Gradle 7.5+
### 安装步骤
1. **克隆仓库**
```bash
git clone https://github.com/WrBug/PolyHermes.git
cd PolyHermes
```
2. **配置数据库**
创建 MySQL 数据库:
```sql
CREATE DATABASE polymarket_bot CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
3. **配置后端**
编辑 `backend/src/main/resources/application.properties`
```properties
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/polymarket_bot?useSSL=false&serverTimezone=UTC&characterEncoding=utf8mb4
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD:your_password}
# 服务器端口
server.port=${SERVER_PORT:8000}
# Polygon RPC(用于查询链上余额)
polygon.rpc.url=${POLYGON_RPC_URL:https://polygon-rpc.com}
```
4. **启动后端**
```bash
cd backend
./gradlew bootRun
```
后端服务将在 `http://localhost:8000` 启动。
5. **启动前端**
```bash
cd frontend
npm install
npm run dev
```
前端应用将在 `http://localhost:3000` 启动。
## 📖 使用指南
### 账户管理
1. 进入"账户管理"页面
2. 点击"导入账户"
3. 输入私钥(支持私钥字符串、助记词或 Keystore 文件)
4. 系统自动推导钱包地址并验证
5. 配置账户名称和是否默认账户
### 跟单配置
1. 进入"Leader 管理",添加被跟单者(Leader)地址
2. 进入"跟单模板",创建跟单模板(配置跟单比例、风险控制等)
3. 进入"跟单配置",将账户、模板和 Leader 关联
4. 启用跟单关系,系统将自动开始跟单
### 查看统计
- **全局统计**:查看所有跟单关系的汇总统计
- **Leader 统计**:查看特定 Leader 的统计信息
- **分类统计**:按分类(sports/crypto)查看统计
- **跟单关系统计**:查看单个跟单关系的详细统计
## 🔧 配置说明
### 环境变量
| 变量名 | 说明 | 默认值 |
|--------|------|--------|
| `DB_USERNAME` | 数据库用户名 | `root` |
| `DB_PASSWORD` | 数据库密码 | - |
| `SERVER_PORT` | 后端服务端口 | `8000` |
| `POLYGON_RPC_URL` | Polygon RPC 地址 | `https://polygon-rpc.com` |
| `JWT_SECRET` | JWT 密钥 | - |
### 代理配置
系统支持通过 Web UI 配置 HTTP 代理,无需修改环境变量:
1. 进入"系统管理"页面
2. 配置代理主机、端口、用户名和密码
3. 启用代理并测试连接
4. 配置实时生效,无需重启服务
## 📚 API 文档
详细的 API 文档请参考:
- [跟单系统需求文档](docs/copy-trading-requirements.md)
- [前端需求文档](docs/copy-trading-frontend-requirements.md)
## 🤝 贡献
欢迎贡献代码!请遵循以下步骤:
1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
## 📝 开发规范
- **后端**: 遵循 Kotlin 编码规范,使用 Spring Boot 最佳实践
- **前端**: 遵循 TypeScript 和 React 最佳实践
- **提交信息**: 使用清晰的提交信息,遵循 [Conventional Commits](https://www.conventionalcommits.org/)
详细开发规范请参考:
- [后端开发规范](.cursor/rules/backend.mdc)
- [前端开发规范](.cursor/rules/frontend.mdc)
## ⚠️ 免责声明
本软件仅供学习和研究使用。使用本软件进行交易的风险由用户自行承担。作者不对任何交易损失负责。
## 📄 许可证
本项目采用 MIT 许可证。详情请参阅 [LICENSE](LICENSE) 文件。
## 🔗 相关链接
- [GitHub 仓库](https://github.com/WrBug/PolyHermes)
- [Twitter](https://x.com/quant_tr)
- [Polymarket 官网](https://polymarket.com)
- [Polymarket API 文档](https://docs.polymarket.com)
## 🙏 致谢
感谢所有为本项目做出贡献的开发者和用户!
---
**⭐ 如果这个项目对你有帮助,请给个 Star!**
@@ -4,10 +4,10 @@ import jakarta.persistence.*
/**
* 账户信息实体
* 用于存储跟单者的账户信息(支持多账户)
* 用于存储钱包账户信息(支持多账户)
*/
@Entity
@Table(name = "copy_trading_accounts")
@Table(name = "wallet_accounts")
data class Account(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -461,10 +461,12 @@ class CopyOrderTrackingService(
}
// 8. 构建订单请求
// 跟单订单使用 FAK (Fill-And-Kill),允许部分成交,未成交部分立即取消
// 这样可以快速响应 Leader 的交易,避免订单长期挂单导致价格不匹配
val orderRequest = NewOrderRequest(
order = signedOrder,
owner = account.apiKey!!,
orderType = "GTC", // Good-Til-Cancelled
orderType = "FAK", // Fill-And-Kill
deferExec = false
)
@@ -584,10 +586,12 @@ class CopyOrderTrackingService(
)
// 构建订单请求(每次重试都使用新签名的订单)
// 跟单订单使用 FAK (Fill-And-Kill),允许部分成交,未成交部分立即取消
// 这样可以快速响应 Leader 的交易,避免订单长期挂单导致价格不匹配
val orderRequest = NewOrderRequest(
order = signedOrder,
owner = owner, // API Key
orderType = "GTC", // Good-Til-Cancelled
orderType = "FAK", // Fill-And-Kill
deferExec = false
)
@@ -5,7 +5,7 @@
-- ============================================
-- 1. 创建账户表
-- ============================================
CREATE TABLE IF NOT EXISTS copy_trading_accounts (
CREATE TABLE IF NOT EXISTS wallet_accounts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
private_key VARCHAR(500) NOT NULL COMMENT '私钥(加密存储)',
wallet_address VARCHAR(42) NOT NULL UNIQUE COMMENT '钱包地址(从私钥推导)',
@@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS copy_trading_accounts (
updated_at BIGINT NOT NULL COMMENT '更新时间(毫秒时间戳)',
INDEX idx_wallet_address (wallet_address),
INDEX idx_is_default (is_default)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跟单系统账户表';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='钱包账户表';
-- ============================================
-- 2. 创建被跟单者(Leader)表
@@ -77,7 +77,7 @@ CREATE TABLE IF NOT EXISTS copy_trading (
INDEX idx_template_id (template_id),
INDEX idx_leader_id (leader_id),
INDEX idx_enabled (enabled),
FOREIGN KEY (account_id) REFERENCES copy_trading_accounts(id) ON DELETE CASCADE,
FOREIGN KEY (account_id) REFERENCES wallet_accounts(id) ON DELETE CASCADE,
FOREIGN KEY (template_id) REFERENCES copy_trading_templates(id) ON DELETE RESTRICT,
FOREIGN KEY (leader_id) REFERENCES copy_trading_leaders(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='跟单关系表(钱包-模板关联)';
@@ -0,0 +1,36 @@
-- ============================================
-- 重命名账户表:copy_trading_accounts -> wallet_accounts
-- ============================================
-- 检查表是否存在,如果存在则重命名
-- 注意:此迁移脚本假设表已经存在(从 V1 迁移创建)
-- 1. 查找外键约束名称并删除
SET @fk_name = NULL;
SELECT CONSTRAINT_NAME INTO @fk_name
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'copy_trading'
AND COLUMN_NAME = 'account_id'
AND REFERENCED_TABLE_NAME = 'copy_trading_accounts'
LIMIT 1;
-- 2. 如果找到外键,删除它
SET @drop_fk_sql = IF(@fk_name IS NOT NULL,
CONCAT('ALTER TABLE copy_trading DROP FOREIGN KEY `', @fk_name, '`'),
'SELECT 1');
PREPARE stmt FROM @drop_fk_sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 3. 重命名表
RENAME TABLE copy_trading_accounts TO wallet_accounts;
-- 4. 重新添加外键约束
ALTER TABLE copy_trading
ADD CONSTRAINT fk_copy_trading_account_id
FOREIGN KEY (account_id) REFERENCES wallet_accounts(id) ON DELETE CASCADE;
-- 5. 更新表注释
ALTER TABLE wallet_accounts COMMENT = '钱包账户表';
+3 -3
View File
@@ -580,7 +580,7 @@
### 3.1 Account(账户信息)
```kotlin
@Entity
@Table(name = "copy_trading_accounts")
@Table(name = "wallet_accounts")
data class Account(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -1623,7 +1623,7 @@ const address = account.address;
## 7. 数据库设计
### 7.1 表结构
- `copy_trading_accounts`: 账户信息
- `wallet_accounts`: 钱包账户表
- `copy_trading_leaders`: 被跟单者表
- `copy_trading_templates`: 跟单模板表
- `copy_trading`: 跟单关系表(钱包-模板关联,多对多)
@@ -1632,7 +1632,7 @@ const address = account.address;
- `copy_trading_processed_trades`: 已处理交易表(用于去重)
### 7.2 索引设计
- `copy_trading_accounts.wallet_address`: UNIQUE 索引
- `wallet_accounts.wallet_address`: UNIQUE 索引
- `copy_trading_leaders.leader_address`: UNIQUE 索引
- `copy_trading_templates.template_name`: UNIQUE 索引
- `copy_trading.account_id + template_id + leader_id`: 联合唯一索引
+50 -9
View File
@@ -13,7 +13,9 @@ import {
AppstoreOutlined,
TeamOutlined,
LogoutOutlined,
SettingOutlined
SettingOutlined,
GithubOutlined,
TwitterOutlined
} from '@ant-design/icons'
import type { MenuProps } from 'antd'
import type { ReactNode } from 'react'
@@ -172,12 +174,30 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
<div style={{ color: '#fff', fontSize: '18px', fontWeight: 'bold' }}>
PolyHermes
</div>
<Button
type="text"
icon={<MenuOutlined />}
style={{ color: '#fff' }}
onClick={() => setMobileMenuOpen(true)}
/>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<a
href="https://github.com/WrBug/PolyHermes"
target="_blank"
rel="noopener noreferrer"
style={{ color: '#fff', fontSize: '18px' }}
>
<GithubOutlined />
</a>
<a
href="https://x.com/quant_tr"
target="_blank"
rel="noopener noreferrer"
style={{ color: '#fff', fontSize: '18px' }}
>
<TwitterOutlined />
</a>
<Button
type="text"
icon={<MenuOutlined />}
style={{ color: '#fff' }}
onClick={() => setMobileMenuOpen(true)}
/>
</div>
</Header>
<Content style={{
padding: '12px 8px',
@@ -225,13 +245,34 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
height: '64px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
justifyContent: 'space-between',
padding: '0 16px',
color: '#fff',
fontSize: '18px',
fontWeight: 'bold',
flexShrink: 0
}}>
PolyHermes
<span>PolyHermes</span>
<div style={{ display: 'flex', gap: '12px' }}>
<a
href="https://github.com/WrBug/PolyHermes"
target="_blank"
rel="noopener noreferrer"
style={{ color: '#fff', fontSize: '16px' }}
title="GitHub"
>
<GithubOutlined />
</a>
<a
href="https://x.com/quant_tr"
target="_blank"
rel="noopener noreferrer"
style={{ color: '#fff', fontSize: '16px' }}
title="Twitter"
>
<TwitterOutlined />
</a>
</div>
</div>
<Menu
mode="inline"