Use 比特骆驼 logo for PWA/brand marks, remove install button, docs Ubuntu 22.04 only.
Co-authored-by: Cursor <cursoragent@cursor.com>
@@ -8,6 +8,15 @@
|
||||
|
||||
仓库:<https://git.bz121.com/dekun/eth_hedge_sim.git>
|
||||
|
||||
## 运行环境
|
||||
|
||||
| 项 | 约定 |
|
||||
|----|------|
|
||||
| 操作系统 | **Ubuntu 22.04** LTS |
|
||||
| 进程管理 | PM2(`eth-hedge-api`) |
|
||||
| 安装目录 | `/opt/eth_hedge_sim` |
|
||||
| 端口 | **5155** |
|
||||
|
||||
## 文档
|
||||
|
||||
- [开发方案](docs/开发方案.md)
|
||||
@@ -21,11 +30,10 @@
|
||||
- 反代:[`https://dc.hyf2.cc`](https://dc.hyf2.cc) → 本机 `5155`(PM2: `eth-hedge-api`)
|
||||
- 登录账号:见服务器 `/opt/eth_hedge_sim/.env` 的 `AUTH_USERNAME` / `AUTH_PASSWORD`
|
||||
- 可在「系统设置」修改用户名/密码;前端固定同源 API,无 API 地址配置项
|
||||
- 支持电脑 / 平板安装为独立应用(PWA)
|
||||
|
||||
## 一键部署 / 更新(禁止 scp 传代码)
|
||||
|
||||
服务器目录:`/opt/eth_hedge_sim`
|
||||
目标机:**Ubuntu 22.04**,目录 `/opt/eth_hedge_sim`。
|
||||
更新方式:**只允许 `git pull`**,然后构建并 `pm2 startOrReload` 本项目进程。
|
||||
|
||||
### 推荐:curl 一键管理器(菜单)
|
||||
@@ -47,30 +55,33 @@ curl -fsSL https://git.bz121.com/dekun/eth_hedge_sim/raw/branch/main/deploy/mana
|
||||
bash /opt/eth_hedge_sim/deploy/manage.sh
|
||||
```
|
||||
|
||||
### 本机触发远程更新
|
||||
### 从开发机触发远程更新(可选)
|
||||
|
||||
在能 SSH 到 Ubuntu 服务器的机器上:
|
||||
|
||||
```bash
|
||||
pip install paramiko
|
||||
set DEPLOY_PASS=*** # Windows PowerShell: $env:DEPLOY_PASS='***'
|
||||
export DEPLOY_PASS='***'
|
||||
python scripts/deploy_remote.py
|
||||
```
|
||||
|
||||
PM2 进程名:`eth-hedge-api`(端口 **5155**)。禁止 `pm2 restart all`。
|
||||
|
||||
## 本地开发
|
||||
## 服务器上本地开发 / 调试(Ubuntu 22.04)
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
cd /opt/eth_hedge_sim
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
copy .env.example .env
|
||||
cp .env.example .env
|
||||
|
||||
python scripts/smoke_market.py
|
||||
cd backend
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 5155 --reload
|
||||
|
||||
# 另开终端
|
||||
cd frontend
|
||||
cd /opt/eth_hedge_sim/frontend
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
@@ -81,3 +92,4 @@ npm run dev
|
||||
- 永续市价;期权只吃买卖一;滑点 = 1×手续费
|
||||
- 仓位:永续 1 ETH,期权 2 ETH 名义
|
||||
- 部署更新只用 git pull,不用 scp
|
||||
- 部署环境固定为 Ubuntu 22.04
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy/lib/common.sh — 比特骆驼自动化对冲系统 部署公共函数
|
||||
# 目标系统: Ubuntu 22.04 LTS
|
||||
set -e
|
||||
set -u
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 比特骆驼自动化对冲系统 部署管理器(工程 eth_hedge_sim)
|
||||
# 部署环境: Ubuntu 22.04 LTS
|
||||
#
|
||||
# 新服务器(免克隆):
|
||||
# curl -fsSL https://git.bz121.com/dekun/eth_hedge_sim/raw/branch/main/deploy/manage.sh | bash
|
||||
|
||||
@@ -191,6 +191,8 @@ frontend/src/components/
|
||||
|
||||
## 4. 部署相关文件
|
||||
|
||||
目标环境:**Ubuntu 22.04** + PM2。目录默认 `/opt/eth_hedge_sim`。
|
||||
|
||||
### 4.1 `deploy/ecosystem.config.cjs`(示意)
|
||||
|
||||
```js
|
||||
@@ -252,7 +254,7 @@ module.exports = {
|
||||
|
||||
| 允许 | 禁止 |
|
||||
|------|------|
|
||||
| 复制 OKX 行情订阅、签名、深度解析到本仓库 | 修改 `C:\Users\dekun\Desktop\crypto_monitor` 下任何文件 |
|
||||
| 复制 OKX 行情订阅、签名、深度解析到本仓库 | 修改现网 `crypto_monitor` 仓库内任何文件 |
|
||||
| 复制「买一流动性检查」思路后重写 | 把本项目塞进现网 monorepo 一起 PM2 |
|
||||
| 新建本仓库的 `.env` | 默认使用现网交易 Key 做模拟(模拟不需要交易权限) |
|
||||
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
| 阶段 | 形态 | 说明 |
|
||||
|------|------|------|
|
||||
| 现在(内部) | Git + `deploy/manage.sh` + PM2 | 已具备,继续打磨成「可复制安装」 |
|
||||
| 商用 v1 | **一键部署脚本** + 环境检查 + 健康检查 | 客户 Ubuntu 机器上跑通 |
|
||||
| 商用 v1 | **一键部署脚本** + 环境检查 + 健康检查 | 客户 **Ubuntu 22.04** 机器上跑通 |
|
||||
| 商用 v2 | **Docker Compose**(可选) | 降低环境差异;与脚本二选一或并存 |
|
||||
| 商用 v3 | 离线包 / 镜像导出 | 内网客户、无法拉 Git 的场景 |
|
||||
|
||||
### 2.2 客户侧必备条件
|
||||
|
||||
- 一台独立 Linux 主机(建议独立 IP)
|
||||
- 一台独立 **Ubuntu 22.04** 主机(建议独立 IP)
|
||||
- 出网访问目标交易所 API(OKX 等)
|
||||
- 域名或 IP + HTTPS(可用反代)
|
||||
- 客户自备交易所 API Key(只读行情 / 实盘交易按档位要求)
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|----|------|
|
||||
| 产品名 | **比特骆驼自动化对冲系统** |
|
||||
| 工程名 / 目录名 | `eth_hedge_sim` |
|
||||
| 本机路径 | `C:\Users\dekun\Desktop\新建文件夹\eth_hedge_sim` |
|
||||
| 云服务器 | **单独一台 Ubuntu**(或同机不同目录/不同 PM2 进程名,且与现网隔离) |
|
||||
| 部署操作系统 | **Ubuntu 22.04** LTS |
|
||||
| 云服务器 | **单独一台 Ubuntu 22.04**(或同机不同目录/不同 PM2 进程名,且与现网隔离) |
|
||||
| 部署 | PM2 |
|
||||
| 行情 | OKX **实盘只读** API(REST + WebSocket) |
|
||||
| 成交(默认) | **本地模拟撮合 + 本地虚拟资金**(非 OKX 模拟盘) |
|
||||
@@ -150,7 +150,7 @@ OKX 实盘只读行情 (WS/REST)
|
||||
|
||||
---
|
||||
|
||||
## 6. 部署(Ubuntu + PM2)
|
||||
## 6. 部署(Ubuntu 22.04 + PM2)
|
||||
|
||||
### 6.1 建议目录
|
||||
|
||||
@@ -221,4 +221,4 @@ TZ=Asia/Shanghai
|
||||
|
||||
## 9. 一句话
|
||||
|
||||
**独立仓 `eth_hedge_sim`(产品:比特骆驼自动化对冲系统):OKX/币安真行情只读 + 本地虚拟资金撮合(永续市价、期权只买吃买卖一、滑点=1×手续费)+ 四页前端 + Ubuntu/PM2 单独部署;可抄现网思路,但不改现网代码、不共用现网进程。**
|
||||
**独立仓 `eth_hedge_sim`(产品:比特骆驼自动化对冲系统):OKX/币安真行情只读 + 本地虚拟资金撮合(永续市价、期权只买吃买卖一、滑点=1×手续费)+ 四页前端 + Ubuntu 22.04 / PM2 单独部署;可抄现网思路,但不改现网代码、不共用现网进程。**
|
||||
|
||||
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 210 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 571 KiB |
@@ -1,7 +1,6 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { NavLink, Navigate, Route, Routes } from "react-router-dom";
|
||||
import { clearSession, getToken, getUsername } from "./api/client";
|
||||
import InstallBadge from "./components/InstallBadge";
|
||||
import LoginPage from "./pages/Login";
|
||||
import PlanPage from "./pages/Plan";
|
||||
import TradesPage from "./pages/Trades";
|
||||
@@ -14,8 +13,16 @@ function Shell({ children }: { children: ReactNode }) {
|
||||
<div className="app-shell">
|
||||
<header className="topnav">
|
||||
<div className="topnav-side left">
|
||||
<div className="brand">比特骆驼自动化对冲系统</div>
|
||||
<InstallBadge />
|
||||
<div className="brand">
|
||||
<img
|
||||
className="brand-mark"
|
||||
src="/icons/brand.png"
|
||||
alt=""
|
||||
width={28}
|
||||
height={28}
|
||||
/>
|
||||
<span>比特骆驼自动化对冲系统</span>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="topnav-center">
|
||||
<NavLink to="/plan" className={({ isActive }) => (isActive ? "active" : "")}>
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
BeforeInstallPromptEvent,
|
||||
isAppleTouchDevice,
|
||||
isStandaloneDisplay,
|
||||
} from "../pwa/install";
|
||||
|
||||
/**
|
||||
* Desktop/tablet install affordance:
|
||||
* - Chrome/Edge/Android: native install via beforeinstallprompt
|
||||
* - iPad/iOS: tip to Add to Home Screen
|
||||
* - Already installed: show 已安装标识
|
||||
*/
|
||||
export default function InstallBadge() {
|
||||
const [standalone, setStandalone] = useState(isStandaloneDisplay);
|
||||
const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(
|
||||
null,
|
||||
);
|
||||
const [tipOpen, setTipOpen] = useState(false);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const apple = isAppleTouchDevice();
|
||||
|
||||
useEffect(() => {
|
||||
const mq = window.matchMedia("(display-mode: standalone)");
|
||||
const sync = () => setStandalone(isStandaloneDisplay());
|
||||
mq.addEventListener?.("change", sync);
|
||||
sync();
|
||||
|
||||
const onBip = (e: Event) => {
|
||||
e.preventDefault();
|
||||
setDeferred(e as BeforeInstallPromptEvent);
|
||||
};
|
||||
const onInstalled = () => {
|
||||
setDeferred(null);
|
||||
setStandalone(true);
|
||||
setTipOpen(false);
|
||||
};
|
||||
window.addEventListener("beforeinstallprompt", onBip);
|
||||
window.addEventListener("appinstalled", onInstalled);
|
||||
return () => {
|
||||
mq.removeEventListener?.("change", sync);
|
||||
window.removeEventListener("beforeinstallprompt", onBip);
|
||||
window.removeEventListener("appinstalled", onInstalled);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (standalone) {
|
||||
return (
|
||||
<span className="install-badge installed" title="已安装为独立应用">
|
||||
已安装
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const canNativeInstall = Boolean(deferred);
|
||||
|
||||
async function onInstall() {
|
||||
if (!deferred) {
|
||||
setTipOpen((v) => !v);
|
||||
return;
|
||||
}
|
||||
setBusy(true);
|
||||
try {
|
||||
await deferred.prompt();
|
||||
await deferred.userChoice;
|
||||
setDeferred(null);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="install-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className="btn ghost install-btn"
|
||||
disabled={busy}
|
||||
onClick={() => void onInstall()}
|
||||
title={
|
||||
canNativeInstall
|
||||
? "安装到电脑或平板"
|
||||
: apple
|
||||
? "添加到主屏幕(平板/手机)"
|
||||
: "安装说明"
|
||||
}
|
||||
>
|
||||
{canNativeInstall ? (busy ? "安装中…" : "安装应用") : "安装应用"}
|
||||
</button>
|
||||
{tipOpen && !canNativeInstall ? (
|
||||
<div className="install-tip" role="dialog" aria-label="安装说明">
|
||||
{apple ? (
|
||||
<>
|
||||
<p>iPad / iPhone:用 Safari 打开本站,点分享 →「添加到主屏幕」。</p>
|
||||
<p className="muted">安装后从主屏幕打开即为独立应用窗口。</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>
|
||||
电脑:Chrome / Edge 地址栏右侧「安装」图标,或菜单 → 安装应用。
|
||||
</p>
|
||||
<p>
|
||||
安卓平板:浏览器菜单 →「安装应用」/「添加到主屏幕」。
|
||||
</p>
|
||||
<p className="muted">需 HTTPS(或本机 localhost)且已加载完成。</p>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="btn ghost"
|
||||
onClick={() => setTipOpen(false)}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { FormEvent, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { login, setSession } from "../api/client";
|
||||
import InstallBadge from "../components/InstallBadge";
|
||||
|
||||
export default function LoginPage() {
|
||||
const nav = useNavigate();
|
||||
@@ -29,8 +28,14 @@ export default function LoginPage() {
|
||||
<div className="login-wrap">
|
||||
<form className="login-box" onSubmit={onSubmit}>
|
||||
<div className="login-title-row">
|
||||
<img
|
||||
className="login-brand-mark"
|
||||
src="/icons/brand.png"
|
||||
alt="比特骆驼"
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
<h1>比特骆驼自动化对冲系统</h1>
|
||||
<InstallBadge />
|
||||
</div>
|
||||
{err ? <div className="err">{err}</div> : null}
|
||||
<div className="field">
|
||||
|
||||
@@ -81,70 +81,35 @@ input {
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--accent);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.install-wrap {
|
||||
position: relative;
|
||||
.brand-mark,
|
||||
.login-brand-mark {
|
||||
flex-shrink: 0;
|
||||
border-radius: 6px;
|
||||
object-fit: cover;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.install-btn {
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border: 1px solid var(--line);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.install-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.install-badge.installed {
|
||||
color: #0b0e11;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.install-tip {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
left: 0;
|
||||
z-index: 30;
|
||||
width: min(320px, calc(100vw - 24px));
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-elev);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.install-tip p {
|
||||
margin: 0 0 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.install-tip p.muted,
|
||||
.install-tip .muted {
|
||||
color: var(--muted);
|
||||
.login-brand-mark {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.topnav-side.left .brand {
|
||||
max-width: 11em;
|
||||
.topnav-side.left .brand span {
|
||||
max-width: 9em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,9 +371,8 @@ input {
|
||||
|
||||
.login-title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||