@@ -12,53 +12,55 @@
|
|||||||
| **统计分析** | 总交易、胜率,按品种/类型/方向统计 |
|
| **统计分析** | 总交易、胜率,按品种/类型/方向统计 |
|
||||||
| **系统设置** | 修改密码、配置企业微信 Webhook |
|
| **系统设置** | 修改密码、配置企业微信 Webhook |
|
||||||
|
|
||||||
## 品种输入
|
## 品种与合约代码(同花顺格式)
|
||||||
|
|
||||||
在各表单中输入中文品种名(如「白银」「螺纹钢」),自动联想新浪行情代码及主力合约标识。
|
输入中文品种名(如「白银」「螺纹钢」)或同花顺合约代码(如 `ag2606`、`SR609`、`IF2606`),系统自动匹配**当前主力月份合约**。
|
||||||
|
|
||||||
|
| 交易所 | 同花顺示例 | 说明 |
|
||||||
|
|--------|-----------|------|
|
||||||
|
| 上期所 / 大商所 / 上期能源 | `ag2606`、`rb2605`、`m2609` | 小写品种 + 4 位年月 |
|
||||||
|
| 郑商所 | `SR609`、`MA606` | 大写品种 + 3 位年月 |
|
||||||
|
| 中金所 | `IF2606`、`IH2606` | 大写品种 + 4 位年月 |
|
||||||
|
|
||||||
|
界面展示同花顺代码;行情在后台通过新浪 API 拉取(内部自动转换,无需手动填写新浪代码)。
|
||||||
|
|
||||||
|
## 快速部署(Ubuntu root + /root/qihuo)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 以 root 登录后,在项目目录执行
|
||||||
|
bash deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
默认安装路径:`/root/qihuo`,服务端口:`6600`。
|
||||||
|
|
||||||
|
部署完成后访问:`http://服务器IP:6600`
|
||||||
|
|
||||||
## 环境要求
|
## 环境要求
|
||||||
|
|
||||||
- Ubuntu 20.04+(推荐)
|
- Ubuntu 20.04+(推荐)
|
||||||
|
- **root 用户**运行(部署目录 `/root/qihuo`)
|
||||||
- Python 3.10+
|
- Python 3.10+
|
||||||
- Node.js(仅 PM2 进程守护需要)
|
- Node.js + PM2(进程守护)
|
||||||
- 网络可访问 `hq.sinajs.cn`(行情)及企业微信 API
|
- 网络可访问 `hq.sinajs.cn`(行情)及企业微信 API
|
||||||
|
|
||||||
## 快速部署(Ubuntu + /opt)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 克隆仓库后,在项目目录执行一键部署
|
|
||||||
sudo bash deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
默认安装路径:`/opt/qihuo`,服务端口:`6600`。
|
|
||||||
|
|
||||||
部署完成后访问:`http://服务器IP:6600`
|
|
||||||
|
|
||||||
## 手动部署
|
## 手动部署
|
||||||
|
|
||||||
### 1. 安装系统依赖
|
### 1. 安装系统依赖
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt update
|
apt update
|
||||||
sudo apt install -y python3 python3-venv python3-pip git
|
apt install -y python3 python3-venv python3-pip git nodejs npm
|
||||||
# PM2(进程守护)
|
npm install -g pm2
|
||||||
curl -fsSL https://get.pnpm.io/install.sh | sh - # 或直接用 npm
|
|
||||||
sudo npm install -g pm2
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 克隆到 /opt
|
### 2. 克隆到 /root/qihuo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo mkdir -p /opt
|
git clone https://git.bz121.com/dekun/qihuo.git /root/qihuo
|
||||||
sudo git clone https://git.bz121.com/dekun/qihuo.git /opt/qihuo
|
cd /root/qihuo
|
||||||
sudo chown -R $USER:$USER /opt/qihuo
|
|
||||||
cd /opt/qihuo
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 虚拟环境与依赖
|
### 3. 虚拟环境与依赖
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m venv venv
|
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from flask import (
|
|||||||
)
|
)
|
||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
from symbols import search_symbols, get_by_code
|
from symbols import search_symbols, get_price, ths_to_sina_code
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@@ -82,6 +82,9 @@ def init_db():
|
|||||||
"ALTER TABLE key_monitors ADD COLUMN upper_triggered INTEGER DEFAULT 0",
|
"ALTER TABLE key_monitors ADD COLUMN upper_triggered INTEGER DEFAULT 0",
|
||||||
"ALTER TABLE key_monitors ADD COLUMN lower_triggered INTEGER DEFAULT 0",
|
"ALTER TABLE key_monitors ADD COLUMN lower_triggered INTEGER DEFAULT 0",
|
||||||
"ALTER TABLE trade_records ADD COLUMN symbol_name TEXT",
|
"ALTER TABLE trade_records ADD COLUMN symbol_name TEXT",
|
||||||
|
"ALTER TABLE order_plans ADD COLUMN sina_code TEXT",
|
||||||
|
"ALTER TABLE key_monitors ADD COLUMN sina_code TEXT",
|
||||||
|
"ALTER TABLE trade_records ADD COLUMN sina_code TEXT",
|
||||||
]
|
]
|
||||||
for sql in migrations:
|
for sql in migrations:
|
||||||
try:
|
try:
|
||||||
@@ -118,20 +121,20 @@ def send_wechat_msg(content: str):
|
|||||||
|
|
||||||
# —————————————— 行情 ——————————————
|
# —————————————— 行情 ——————————————
|
||||||
|
|
||||||
def get_price(symbol: str) -> Optional[float]:
|
def resolve_sina_code(ths_code: str, sina_code: str = "") -> Optional[str]:
|
||||||
try:
|
"""同花顺代码 -> 新浪行情代码;兼容旧数据中的新浪格式。"""
|
||||||
url = f"https://hq.sinajs.cn/list={symbol}"
|
if sina_code:
|
||||||
headers = {"Referer": "https://finance.sina.com.cn"}
|
return sina_code
|
||||||
resp = requests.get(url, headers=headers, timeout=5)
|
if ths_code.startswith("nf_") or ths_code.startswith("CFF_RE_"):
|
||||||
text = resp.text
|
return ths_code
|
||||||
if "=" not in text:
|
return ths_to_sina_code(ths_code)
|
||||||
return None
|
|
||||||
data = text.split("=")[1].strip().strip('"').split(",")
|
|
||||||
if len(data) < 9:
|
def fetch_price(ths_code: str, sina_code: str = "") -> Optional[float]:
|
||||||
return None
|
code = resolve_sina_code(ths_code, sina_code)
|
||||||
return float(data[8])
|
if not code:
|
||||||
except Exception:
|
|
||||||
return None
|
return None
|
||||||
|
return get_price(code)
|
||||||
|
|
||||||
# —————————————— 监控逻辑 ——————————————
|
# —————————————— 监控逻辑 ——————————————
|
||||||
|
|
||||||
@@ -143,7 +146,8 @@ def check_order_plans():
|
|||||||
|
|
||||||
for r in rows:
|
for r in rows:
|
||||||
sym = r["symbol"]
|
sym = r["symbol"]
|
||||||
p = get_price(sym)
|
sina = r["sina_code"] if "sina_code" in r.keys() else ""
|
||||||
|
p = fetch_price(sym, sina)
|
||||||
if not p:
|
if not p:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -224,8 +228,9 @@ def check_key_monitors():
|
|||||||
low_trig = r["lower_triggered"]
|
low_trig = r["lower_triggered"]
|
||||||
name = r["symbol_name"] or sym
|
name = r["symbol_name"] or sym
|
||||||
pid = r["id"]
|
pid = r["id"]
|
||||||
|
sina = r["sina_code"] if "sina_code" in r.keys() else ""
|
||||||
|
|
||||||
p = get_price(sym)
|
p = fetch_price(sym, sina)
|
||||||
if not p:
|
if not p:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -332,19 +337,20 @@ def add_plan():
|
|||||||
direction = d.get("direction")
|
direction = d.get("direction")
|
||||||
symbol = d.get("symbol", "").strip()
|
symbol = d.get("symbol", "").strip()
|
||||||
symbol_name = d.get("symbol_name", "").strip()
|
symbol_name = d.get("symbol_name", "").strip()
|
||||||
|
sina_code = d.get("sina_code", "").strip()
|
||||||
if not direction:
|
if not direction:
|
||||||
flash("请选择多空方向")
|
flash("请选择多空方向")
|
||||||
return redirect(url_for("plans"))
|
return redirect(url_for("plans"))
|
||||||
if not symbol:
|
if not symbol or not sina_code:
|
||||||
flash("请选择有效品种")
|
flash("请从下拉列表选择品种(同花顺合约代码)")
|
||||||
return redirect(url_for("plans"))
|
return redirect(url_for("plans"))
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO order_plans
|
"""INSERT INTO order_plans
|
||||||
(symbol, symbol_name, direction, zone_upper, zone_lower, stop_loss, take_profit)
|
(symbol, symbol_name, sina_code, direction, zone_upper, zone_lower, stop_loss, take_profit)
|
||||||
VALUES (?,?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?,?)""",
|
||||||
(
|
(
|
||||||
symbol, symbol_name, direction,
|
symbol, symbol_name, sina_code, direction,
|
||||||
float(d["zone_upper"]), float(d["zone_lower"]),
|
float(d["zone_upper"]), float(d["zone_lower"]),
|
||||||
float(d["stop_loss"]), float(d["take_profit"]),
|
float(d["stop_loss"]), float(d["take_profit"]),
|
||||||
),
|
),
|
||||||
@@ -382,18 +388,19 @@ def add_key():
|
|||||||
direction = d.get("direction")
|
direction = d.get("direction")
|
||||||
symbol = d.get("symbol", "").strip()
|
symbol = d.get("symbol", "").strip()
|
||||||
symbol_name = d.get("symbol_name", "").strip()
|
symbol_name = d.get("symbol_name", "").strip()
|
||||||
|
sina_code = d.get("sina_code", "").strip()
|
||||||
if not direction:
|
if not direction:
|
||||||
flash("请选择多空方向")
|
flash("请选择多空方向")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("keys"))
|
||||||
if not symbol:
|
if not symbol or not sina_code:
|
||||||
flash("请选择有效品种")
|
flash("请从下拉列表选择品种(同花顺合约代码)")
|
||||||
return redirect(url_for("keys"))
|
return redirect(url_for("keys"))
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO key_monitors
|
"""INSERT INTO key_monitors
|
||||||
(symbol, symbol_name, monitor_type, direction, upper, lower)
|
(symbol, symbol_name, sina_code, monitor_type, direction, upper, lower)
|
||||||
VALUES (?,?,?,?,?,?)""",
|
VALUES (?,?,?,?,?,?,?)""",
|
||||||
(symbol, symbol_name, d["type"], direction, float(d["upper"]), float(d["lower"])),
|
(symbol, symbol_name, sina_code, d["type"], direction, float(d["upper"]), float(d["lower"])),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -1,40 +1,42 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# 国内期货监控系统 - Ubuntu 一键部署脚本
|
# 国内期货监控系统 - Ubuntu 一键部署(root 用户,/root/qihuo)
|
||||||
# 安装路径: /opt/qihuo 端口: 6600 进程守护: PM2
|
# 端口: 6600 进程守护: PM2
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
APP_DIR="/opt/qihuo"
|
APP_DIR="/root/qihuo"
|
||||||
REPO_URL="https://git.bz121.com/dekun/qihuo.git"
|
REPO_URL="https://git.bz121.com/dekun/qihuo.git"
|
||||||
SERVICE_NAME="qihuo"
|
SERVICE_NAME="qihuo"
|
||||||
|
|
||||||
echo "==> 检查系统依赖..."
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "请使用 root 用户运行: sudo bash deploy.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
install_pkg() {
|
echo "==> 检查系统依赖..."
|
||||||
|
apt-get update -qq
|
||||||
|
|
||||||
|
need_install() {
|
||||||
if ! command -v "$1" &>/dev/null; then
|
if ! command -v "$1" &>/dev/null; then
|
||||||
echo "安装 $1..."
|
apt-get install -y "$2"
|
||||||
sudo apt-get install -y "$2"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
sudo apt-get update -qq
|
need_install python3 python3
|
||||||
install_pkg python3 python3
|
need_install python3-venv python3-venv
|
||||||
install_pkg python3-venv python3-venv
|
need_install git git
|
||||||
install_pkg git git
|
|
||||||
|
|
||||||
if ! command -v pm2 &>/dev/null; then
|
if ! command -v pm2 &>/dev/null; then
|
||||||
echo "==> 安装 PM2..."
|
echo "==> 安装 PM2..."
|
||||||
if command -v npm &>/dev/null; then
|
if ! command -v npm &>/dev/null; then
|
||||||
sudo npm install -g pm2
|
need_install nodejs nodejs
|
||||||
else
|
need_install npm npm
|
||||||
install_pkg nodejs nodejs
|
|
||||||
install_pkg npm npm
|
|
||||||
sudo npm install -g pm2
|
|
||||||
fi
|
fi
|
||||||
|
npm install -g pm2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> 准备应用目录 ${APP_DIR}..."
|
echo "==> 准备应用目录 ${APP_DIR}..."
|
||||||
sudo mkdir -p "$(dirname "$APP_DIR")"
|
mkdir -p "$(dirname "$APP_DIR")"
|
||||||
|
|
||||||
if [ -d "$APP_DIR/.git" ]; then
|
if [ -d "$APP_DIR/.git" ]; then
|
||||||
echo "==> 更新已有仓库..."
|
echo "==> 更新已有仓库..."
|
||||||
@@ -45,16 +47,10 @@ else
|
|||||||
echo "目录 ${APP_DIR} 已存在且非 git 仓库,请手动处理后重试"
|
echo "目录 ${APP_DIR} 已存在且非 git 仓库,请手动处理后重试"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "==> 克隆仓库..."
|
git clone "$REPO_URL" "$APP_DIR"
|
||||||
sudo git clone "$REPO_URL" "$APP_DIR"
|
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 确保当前用户可写(若以 root 克隆则 chown)
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
|
||||||
sudo chown -R "$(whoami):$(whoami)" "$APP_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "==> 创建 Python 虚拟环境..."
|
echo "==> 创建 Python 虚拟环境..."
|
||||||
python3 -m venv "$APP_DIR/venv"
|
python3 -m venv "$APP_DIR/venv"
|
||||||
source "$APP_DIR/venv/bin/activate"
|
source "$APP_DIR/venv/bin/activate"
|
||||||
@@ -62,17 +58,16 @@ pip install --upgrade pip -q
|
|||||||
pip install -r "$APP_DIR/requirements.txt" -q
|
pip install -r "$APP_DIR/requirements.txt" -q
|
||||||
|
|
||||||
if [ ! -f "$APP_DIR/.env" ]; then
|
if [ ! -f "$APP_DIR/.env" ]; then
|
||||||
echo "==> 生成 .env(请稍后编辑 SECRET_KEY 和密码)..."
|
echo "==> 生成 .env(请编辑 ADMIN_PASSWORD 后重启)..."
|
||||||
cp "$APP_DIR/.env.example" "$APP_DIR/.env"
|
cp "$APP_DIR/.env.example" "$APP_DIR/.env"
|
||||||
# 生成随机 SECRET_KEY
|
|
||||||
RAND_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
RAND_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
|
||||||
sed -i "s/change-this-to-a-random-secret-key/${RAND_KEY}/" "$APP_DIR/.env"
|
sed -i "s/change-this-to-a-random-secret-key/${RAND_KEY}/" "$APP_DIR/.env"
|
||||||
echo "已创建 $APP_DIR/.env ,请编辑 ADMIN_PASSWORD 后重启服务"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$APP_DIR/logs"
|
||||||
|
|
||||||
echo "==> PM2 启动/重启服务..."
|
echo "==> PM2 启动/重启服务..."
|
||||||
cd "$APP_DIR"
|
cd "$APP_DIR"
|
||||||
mkdir -p "$APP_DIR/logs"
|
|
||||||
pm2 delete "$SERVICE_NAME" 2>/dev/null || true
|
pm2 delete "$SERVICE_NAME" 2>/dev/null || true
|
||||||
pm2 start ecosystem.config.cjs
|
pm2 start ecosystem.config.cjs
|
||||||
pm2 save
|
pm2 save
|
||||||
@@ -81,6 +76,7 @@ echo ""
|
|||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " 部署完成"
|
echo " 部署完成"
|
||||||
echo " 目录: ${APP_DIR}"
|
echo " 目录: ${APP_DIR}"
|
||||||
|
echo " 用户: root"
|
||||||
echo " 端口: 6600"
|
echo " 端口: 6600"
|
||||||
echo " 访问: http://<服务器IP>:6600"
|
echo " 访问: http://<服务器IP>:6600"
|
||||||
echo " 日志: pm2 logs ${SERVICE_NAME}"
|
echo " 日志: pm2 logs ${SERVICE_NAME}"
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
name: "qihuo",
|
name: "qihuo",
|
||||||
script: "app.py",
|
script: "app.py",
|
||||||
cwd: "/opt/qihuo",
|
cwd: "/root/qihuo",
|
||||||
interpreter: "/opt/qihuo/venv/bin/python",
|
interpreter: "/root/qihuo/venv/bin/python",
|
||||||
instances: 1,
|
instances: 1,
|
||||||
autorestart: true,
|
autorestart: true,
|
||||||
watch: false,
|
watch: false,
|
||||||
@@ -12,8 +12,8 @@ module.exports = {
|
|||||||
env: {
|
env: {
|
||||||
NODE_ENV: "production",
|
NODE_ENV: "production",
|
||||||
},
|
},
|
||||||
error_file: "/opt/qihuo/logs/pm2-error.log",
|
error_file: "/root/qihuo/logs/pm2-error.log",
|
||||||
out_file: "/opt/qihuo/logs/pm2-out.log",
|
out_file: "/root/qihuo/logs/pm2-out.log",
|
||||||
time: true,
|
time: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+21
-11
@@ -1,8 +1,9 @@
|
|||||||
(function () {
|
(function () {
|
||||||
function initSymbolInput(wrapper) {
|
function initSymbolInput(wrapper) {
|
||||||
const input = wrapper.querySelector('.symbol-input');
|
const input = wrapper.querySelector('.symbol-input');
|
||||||
const hiddenCode = wrapper.querySelector('input[name="symbol"]');
|
const hiddenThs = wrapper.querySelector('input[name="symbol"]');
|
||||||
const hiddenName = wrapper.querySelector('input[name="symbol_name"]');
|
const hiddenName = wrapper.querySelector('input[name="symbol_name"]');
|
||||||
|
const hiddenSina = wrapper.querySelector('input[name="sina_code"]');
|
||||||
const dropdown = wrapper.querySelector('.symbol-dropdown');
|
const dropdown = wrapper.querySelector('.symbol-dropdown');
|
||||||
const selectedEl = wrapper.querySelector('.symbol-selected');
|
const selectedEl = wrapper.querySelector('.symbol-selected');
|
||||||
let timer = null;
|
let timer = null;
|
||||||
@@ -13,21 +14,23 @@
|
|||||||
|
|
||||||
function selectItem(item) {
|
function selectItem(item) {
|
||||||
input.value = item.name;
|
input.value = item.name;
|
||||||
hiddenCode.value = item.code;
|
hiddenThs.value = item.ths_code;
|
||||||
hiddenName.value = item.name;
|
hiddenName.value = item.name;
|
||||||
selectedEl.textContent = item.display + ' | ' + item.code;
|
if (hiddenSina) hiddenSina.value = item.sina_code;
|
||||||
|
selectedEl.textContent = '同花顺: ' + item.ths_code + ' | 主力 ' + (item.contract || item.ths_code);
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderItems(items) {
|
function renderItems(items) {
|
||||||
dropdown.innerHTML = '';
|
dropdown.innerHTML = '';
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
dropdown.innerHTML = '<div class="symbol-option">无匹配品种</div>';
|
dropdown.innerHTML = '<div class="symbol-option">无匹配品种,可输入同花顺合约如 ag2606</div>';
|
||||||
} else {
|
} else {
|
||||||
items.forEach(function (item) {
|
items.forEach(function (item) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'symbol-option';
|
div.className = 'symbol-option';
|
||||||
div.innerHTML = item.display + '<div class="sub">' + item.code + ' · ' + item.exchange + '</div>';
|
div.innerHTML = item.display +
|
||||||
|
'<div class="sub">同花顺 ' + item.ths_code + ' · ' + item.exchange + '</div>';
|
||||||
div.addEventListener('mousedown', function (e) {
|
div.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selectItem(item);
|
selectItem(item);
|
||||||
@@ -39,8 +42,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
input.addEventListener('input', function () {
|
input.addEventListener('input', function () {
|
||||||
hiddenCode.value = '';
|
hiddenThs.value = '';
|
||||||
hiddenName.value = '';
|
hiddenName.value = '';
|
||||||
|
if (hiddenSina) hiddenSina.value = '';
|
||||||
selectedEl.textContent = '';
|
selectedEl.textContent = '';
|
||||||
const q = input.value.trim();
|
const q = input.value.trim();
|
||||||
if (!q) {
|
if (!q) {
|
||||||
@@ -53,7 +57,7 @@
|
|||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(renderItems)
|
.then(renderItems)
|
||||||
.catch(function () { hideDropdown(); });
|
.catch(function () { hideDropdown(); });
|
||||||
}, 200);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('blur', function () {
|
input.addEventListener('blur', function () {
|
||||||
@@ -62,7 +66,7 @@
|
|||||||
|
|
||||||
input.addEventListener('focus', function () {
|
input.addEventListener('focus', function () {
|
||||||
const q = input.value.trim();
|
const q = input.value.trim();
|
||||||
if (q && !hiddenCode.value) {
|
if (q && !hiddenThs.value) {
|
||||||
fetch('/api/symbols/search?q=' + encodeURIComponent(q))
|
fetch('/api/symbols/search?q=' + encodeURIComponent(q))
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(renderItems);
|
.then(renderItems);
|
||||||
@@ -76,10 +80,16 @@
|
|||||||
document.querySelectorAll('form').forEach(function (form) {
|
document.querySelectorAll('form').forEach(function (form) {
|
||||||
if (!form.querySelector('.symbol-wrap')) return;
|
if (!form.querySelector('.symbol-wrap')) return;
|
||||||
form.addEventListener('submit', function (e) {
|
form.addEventListener('submit', function (e) {
|
||||||
const hidden = form.querySelector('input[name="symbol"]');
|
const ths = form.querySelector('input[name="symbol"]');
|
||||||
if (hidden && !hidden.value.trim()) {
|
const sina = form.querySelector('input[name="sina_code"]');
|
||||||
|
if (ths && !ths.value.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('请从下拉列表中选择品种');
|
alert('请从下拉列表选择品种');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sina && !sina.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('请从下拉列表选择品种(需含同花顺合约代码)');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+250
-75
@@ -1,95 +1,270 @@
|
|||||||
# 国内期货品种映射:中文名 -> 新浪行情代码(0 表示主力连续)
|
"""
|
||||||
SYMBOLS = [
|
期货品种与同花顺代码映射。
|
||||||
{"name": "白银", "code": "nf_AG0", "exchange": "上期所"},
|
界面展示同花顺格式(如 ag2606、SR609、IF2606),行情通过新浪 API(内部 sina_code)获取。
|
||||||
{"name": "黄金", "code": "nf_AU0", "exchange": "上期所"},
|
"""
|
||||||
{"name": "铜", "code": "nf_CU0", "exchange": "上期所"},
|
import re
|
||||||
{"name": "铝", "code": "nf_AL0", "exchange": "上期所"},
|
import time
|
||||||
{"name": "锌", "code": "nf_ZN0", "exchange": "上期所"},
|
from datetime import date
|
||||||
{"name": "铅", "code": "nf_PB0", "exchange": "上期所"},
|
from typing import Optional
|
||||||
{"name": "镍", "code": "nf_NI0", "exchange": "上期所"},
|
|
||||||
{"name": "锡", "code": "nf_SN0", "exchange": "上期所"},
|
import requests
|
||||||
{"name": "螺纹钢", "code": "nf_RB0", "exchange": "上期所"},
|
|
||||||
{"name": "热卷", "code": "nf_HC0", "exchange": "上期所"},
|
# 品种字母:ths=同花顺展示用,sina=新浪 nf_ 前缀后字母(通常大写)
|
||||||
{"name": "不锈钢", "code": "nf_SS0", "exchange": "上期所"},
|
PRODUCTS = [
|
||||||
{"name": "原油", "code": "nf_SC0", "exchange": "上期所"},
|
{"name": "白银", "ths": "ag", "sina": "AG", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "燃油", "code": "nf_FU0", "exchange": "上期所"},
|
{"name": "黄金", "ths": "au", "sina": "AU", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "沥青", "code": "nf_BU0", "exchange": "上期所"},
|
{"name": "铜", "ths": "cu", "sina": "CU", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "橡胶", "code": "nf_RU0", "exchange": "上期所"},
|
{"name": "铝", "ths": "al", "sina": "AL", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "纸浆", "code": "nf_SP0", "exchange": "上期所"},
|
{"name": "锌", "ths": "zn", "sina": "ZN", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "铁矿石", "code": "nf_I0", "exchange": "大商所"},
|
{"name": "铅", "ths": "pb", "sina": "PB", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "焦炭", "code": "nf_J0", "exchange": "大商所"},
|
{"name": "镍", "ths": "ni", "sina": "NI", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "焦煤", "code": "nf_JM0", "exchange": "大商所"},
|
{"name": "锡", "ths": "sn", "sina": "SN", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "豆粕", "code": "nf_M0", "exchange": "大商所"},
|
{"name": "螺纹钢", "ths": "rb", "sina": "RB", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "豆油", "code": "nf_Y0", "exchange": "大商所"},
|
{"name": "热卷", "ths": "hc", "sina": "HC", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "棕榈油", "code": "nf_P0", "exchange": "大商所"},
|
{"name": "不锈钢", "ths": "ss", "sina": "SS", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "玉米", "code": "nf_C0", "exchange": "大商所"},
|
{"name": "原油", "ths": "sc", "sina": "SC", "exchange": "上期能源", "ex": "INE"},
|
||||||
{"name": "淀粉", "code": "nf_CS0", "exchange": "大商所"},
|
{"name": "燃油", "ths": "fu", "sina": "FU", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "鸡蛋", "code": "nf_JD0", "exchange": "大商所"},
|
{"name": "沥青", "ths": "bu", "sina": "BU", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "生猪", "code": "nf_LH0", "exchange": "大商所"},
|
{"name": "橡胶", "ths": "ru", "sina": "RU", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "聚乙烯", "code": "nf_L0", "exchange": "大商所"},
|
{"name": "纸浆", "ths": "sp", "sina": "SP", "exchange": "上期所", "ex": "SHFE"},
|
||||||
{"name": "聚丙烯", "code": "nf_PP0", "exchange": "大商所"},
|
{"name": "铁矿石", "ths": "i", "sina": "I", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "PVC", "code": "nf_V0", "exchange": "大商所"},
|
{"name": "焦炭", "ths": "j", "sina": "J", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "乙二醇", "code": "nf_EG0", "exchange": "大商所"},
|
{"name": "焦煤", "ths": "jm", "sina": "JM", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "苯乙烯", "code": "nf_EB0", "exchange": "大商所"},
|
{"name": "豆粕", "ths": "m", "sina": "M", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "液化气", "code": "nf_PG0", "exchange": "大商所"},
|
{"name": "豆油", "ths": "y", "sina": "Y", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "菜粕", "code": "nf_RM0", "exchange": "郑商所"},
|
{"name": "棕榈油", "ths": "p", "sina": "P", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "菜油", "code": "nf_OI0", "exchange": "郑商所"},
|
{"name": "玉米", "ths": "c", "sina": "C", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "白糖", "code": "nf_SR0", "exchange": "郑商所"},
|
{"name": "淀粉", "ths": "cs", "sina": "CS", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "棉花", "code": "nf_CF0", "exchange": "郑商所"},
|
{"name": "鸡蛋", "ths": "jd", "sina": "JD", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "甲醇", "code": "nf_MA0", "exchange": "郑商所"},
|
{"name": "生猪", "ths": "lh", "sina": "LH", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "PTA", "code": "nf_TA0", "exchange": "郑商所"},
|
{"name": "聚乙烯", "ths": "l", "sina": "L", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "玻璃", "code": "nf_FG0", "exchange": "郑商所"},
|
{"name": "聚丙烯", "ths": "pp", "sina": "PP", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "纯碱", "code": "nf_SA0", "exchange": "郑商所"},
|
{"name": "PVC", "ths": "v", "sina": "V", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "尿素", "code": "nf_UR0", "exchange": "郑商所"},
|
{"name": "乙二醇", "ths": "eg", "sina": "EG", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "硅铁", "code": "nf_SF0", "exchange": "郑商所"},
|
{"name": "苯乙烯", "ths": "eb", "sina": "EB", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "锰硅", "code": "nf_SM0", "exchange": "郑商所"},
|
{"name": "液化气", "ths": "pg", "sina": "PG", "exchange": "大商所", "ex": "DCE"},
|
||||||
{"name": "苹果", "code": "nf_AP0", "exchange": "郑商所"},
|
{"name": "菜粕", "ths": "RM", "sina": "RM", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
{"name": "红枣", "code": "nf_CJ0", "exchange": "郑商所"},
|
{"name": "菜油", "ths": "OI", "sina": "OI", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
{"name": "花生", "code": "nf_PK0", "exchange": "郑商所"},
|
{"name": "白糖", "ths": "SR", "sina": "SR", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
{"name": "沪深300", "code": "CFF_RE_IF0", "exchange": "中金所"},
|
{"name": "棉花", "ths": "CF", "sina": "CF", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
{"name": "上证50", "code": "CFF_RE_IH0", "exchange": "中金所"},
|
{"name": "甲醇", "ths": "MA", "sina": "MA", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
{"name": "中证500", "code": "CFF_RE_IC0", "exchange": "中金所"},
|
{"name": "PTA", "ths": "TA", "sina": "TA", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
{"name": "中证1000", "code": "CFF_RE_IM0", "exchange": "中金所"},
|
{"name": "玻璃", "ths": "FG", "sina": "FG", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "纯碱", "ths": "SA", "sina": "SA", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "尿素", "ths": "UR", "sina": "UR", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "硅铁", "ths": "SF", "sina": "SF", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "锰硅", "ths": "SM", "sina": "SM", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "苹果", "ths": "AP", "sina": "AP", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "红枣", "ths": "CJ", "sina": "CJ", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "花生", "ths": "PK", "sina": "PK", "exchange": "郑商所", "ex": "CZCE"},
|
||||||
|
{"name": "沪深300", "ths": "IF", "sina": "IF", "exchange": "中金所", "ex": "CFFEX"},
|
||||||
|
{"name": "上证50", "ths": "IH", "sina": "IH", "exchange": "中金所", "ex": "CFFEX"},
|
||||||
|
{"name": "中证500", "ths": "IC", "sina": "IC", "exchange": "中金所", "ex": "CFFEX"},
|
||||||
|
{"name": "中证1000", "ths": "IM", "sina": "IM", "exchange": "中金所", "ex": "CFFEX"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_MAIN_CACHE: dict[str, tuple[float, dict]] = {}
|
||||||
|
_CACHE_TTL = 300 # 5 分钟
|
||||||
|
|
||||||
def _contract_label(code: str) -> str:
|
|
||||||
"""从新浪代码提取品种字母部分,用于展示主力合约标识。"""
|
def _sina_headers() -> dict:
|
||||||
raw = code.replace("nf_", "").replace("CFF_RE_", "")
|
return {"Referer": "https://finance.sina.com.cn"}
|
||||||
letters = "".join(c for c in raw if c.isalpha())
|
|
||||||
return f"{letters}主力"
|
|
||||||
|
def _fetch_sina_raw(sina_code: str) -> Optional[dict]:
|
||||||
|
try:
|
||||||
|
url = f"https://hq.sinajs.cn/list={sina_code}"
|
||||||
|
resp = requests.get(url, headers=_sina_headers(), timeout=5)
|
||||||
|
resp.encoding = "gbk"
|
||||||
|
if '"' not in resp.text:
|
||||||
|
return None
|
||||||
|
body = resp.text.split('"')[1]
|
||||||
|
if not body:
|
||||||
|
return None
|
||||||
|
parts = body.split(",")
|
||||||
|
if len(parts) < 9:
|
||||||
|
return None
|
||||||
|
price = float(parts[8])
|
||||||
|
volume = float(parts[14]) if len(parts) > 14 and parts[14] else 0
|
||||||
|
return {"name": parts[0], "price": price, "volume": volume, "parts": parts}
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def build_ths_code(product: dict, year: int, month: int) -> str:
|
||||||
|
"""同花顺合约代码。"""
|
||||||
|
ex = product["ex"]
|
||||||
|
letters = product["ths"]
|
||||||
|
if ex == "CZCE":
|
||||||
|
return f"{letters}{year % 10}{month:02d}"
|
||||||
|
return f"{letters}{year % 100:02d}{month:02d}"
|
||||||
|
|
||||||
|
|
||||||
|
def build_sina_code(product: dict, year: int, month: int) -> str:
|
||||||
|
"""新浪行情代码(用于拉价)。"""
|
||||||
|
ex = product["ex"]
|
||||||
|
letters = product["sina"]
|
||||||
|
suffix = f"{year % 100:02d}{month:02d}"
|
||||||
|
if ex == "CFFEX":
|
||||||
|
return f"CFF_RE_{letters}{suffix}"
|
||||||
|
return f"nf_{letters}{suffix}"
|
||||||
|
|
||||||
|
|
||||||
|
def build_sina_main_code(product: dict) -> str:
|
||||||
|
"""新浪主力连续代码。"""
|
||||||
|
ex = product["ex"]
|
||||||
|
letters = product["sina"]
|
||||||
|
if ex == "CFFEX":
|
||||||
|
return f"CFF_RE_{letters}0"
|
||||||
|
return f"nf_{letters}0"
|
||||||
|
|
||||||
|
|
||||||
|
def ths_to_sina_code(ths_code: str) -> Optional[str]:
|
||||||
|
"""将同花顺代码转为新浪代码(用户直接输入合约时使用)。"""
|
||||||
|
code = ths_code.strip()
|
||||||
|
if not code:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# CFFEX / 四位月份: IF2606, ag2606
|
||||||
|
m = re.match(r"^([A-Za-z]+)(\d{4})$", code)
|
||||||
|
if m:
|
||||||
|
letters, digits = m.group(1), m.group(2)
|
||||||
|
letters_up = letters.upper()
|
||||||
|
for p in PRODUCTS:
|
||||||
|
if p["ths"].upper() == letters_up or p["sina"] == letters_up:
|
||||||
|
year = 2000 + int(digits[:2])
|
||||||
|
month = int(digits[2:])
|
||||||
|
if 1 <= month <= 12:
|
||||||
|
return build_sina_code(p, year, month)
|
||||||
|
if letters_up in ("IF", "IH", "IC", "IM", "T", "TF", "TS"):
|
||||||
|
return f"CFF_RE_{letters_up}{digits}"
|
||||||
|
|
||||||
|
# CZCE 3-digit: SR609
|
||||||
|
m3 = re.match(r"^([A-Za-z]+)(\d{3})$", code)
|
||||||
|
if m3:
|
||||||
|
letters, digits = m3.group(1), m3.group(2)
|
||||||
|
letters_up = letters.upper()
|
||||||
|
y_digit = int(digits[0])
|
||||||
|
month = int(digits[1:])
|
||||||
|
if 1 <= month <= 12:
|
||||||
|
year = date.today().year
|
||||||
|
decade = year // 10 * 10
|
||||||
|
candidate = decade + y_digit
|
||||||
|
if candidate < year - 1:
|
||||||
|
candidate += 10
|
||||||
|
for p in PRODUCTS:
|
||||||
|
if p["ths"].upper() == letters_up or p["sina"] == letters_up:
|
||||||
|
return build_sina_code(p, candidate, month)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_main_contract(product: dict) -> Optional[dict]:
|
||||||
|
"""按成交量选取当前主力月份合约。"""
|
||||||
|
cache_key = product["sina"]
|
||||||
|
now = time.time()
|
||||||
|
cached = _MAIN_CACHE.get(cache_key)
|
||||||
|
if cached and now - cached[0] < _CACHE_TTL:
|
||||||
|
return cached[1]
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
y, m = today.year, today.month
|
||||||
|
best = None
|
||||||
|
|
||||||
|
for i in range(14):
|
||||||
|
cy, cm = y, m + i
|
||||||
|
while cm > 12:
|
||||||
|
cm -= 12
|
||||||
|
cy += 1
|
||||||
|
sina = build_sina_code(product, cy, cm)
|
||||||
|
raw = _fetch_sina_raw(sina)
|
||||||
|
if raw and raw["volume"] > 0:
|
||||||
|
ths = build_ths_code(product, cy, cm)
|
||||||
|
item = {
|
||||||
|
"name": product["name"],
|
||||||
|
"ths_code": ths,
|
||||||
|
"sina_code": sina,
|
||||||
|
"exchange": product["exchange"],
|
||||||
|
"contract": f"主力 {ths}",
|
||||||
|
"display": f"{product['name']} 主力 {ths}",
|
||||||
|
"volume": raw["volume"],
|
||||||
|
}
|
||||||
|
if best is None or raw["volume"] > best["volume"]:
|
||||||
|
best = item
|
||||||
|
|
||||||
|
if best is None:
|
||||||
|
sina_main = build_sina_main_code(product)
|
||||||
|
raw = _fetch_sina_raw(sina_main)
|
||||||
|
if raw:
|
||||||
|
ths_letters = product["ths"]
|
||||||
|
ths_main = f"{ths_letters}888" if product["ex"] != "CFFEX" else f"{ths_letters.upper()}888"
|
||||||
|
best = {
|
||||||
|
"name": product["name"],
|
||||||
|
"ths_code": ths_main,
|
||||||
|
"sina_code": sina_main,
|
||||||
|
"exchange": product["exchange"],
|
||||||
|
"contract": f"主力连续 {ths_main}",
|
||||||
|
"display": f"{product['name']} 主力连续 {ths_main}",
|
||||||
|
"volume": raw.get("volume", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
if best:
|
||||||
|
_MAIN_CACHE[cache_key] = (now, best)
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
def search_symbols(query: str) -> list:
|
def search_symbols(query: str) -> list:
|
||||||
q = query.strip().lower()
|
q = query.strip().lower()
|
||||||
if not q:
|
if not q:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for s in SYMBOLS:
|
for p in PRODUCTS:
|
||||||
name = s["name"]
|
name = p["name"]
|
||||||
code = s["code"]
|
if q not in name.lower() and q not in p["ths"].lower() and q not in p["sina"].lower():
|
||||||
contract = _contract_label(code)
|
continue
|
||||||
if q in name.lower() or q in code.lower() or q in contract.lower():
|
main = resolve_main_contract(p)
|
||||||
|
if main:
|
||||||
|
results.append(main)
|
||||||
|
|
||||||
|
# 用户直接输入同花顺合约代码
|
||||||
|
if not results and len(q) >= 3:
|
||||||
|
sina = ths_to_sina_code(query.strip())
|
||||||
|
if sina:
|
||||||
|
raw = _fetch_sina_raw(sina)
|
||||||
|
if raw:
|
||||||
results.append({
|
results.append({
|
||||||
"name": name,
|
"name": raw["name"],
|
||||||
"code": code,
|
"ths_code": query.strip(),
|
||||||
"exchange": s["exchange"],
|
"sina_code": sina,
|
||||||
"contract": contract,
|
"exchange": "",
|
||||||
"display": f"{name} ({contract})",
|
"contract": query.strip(),
|
||||||
|
"display": f"{raw['name']} ({query.strip()})",
|
||||||
|
"volume": raw.get("volume", 0),
|
||||||
})
|
})
|
||||||
|
|
||||||
return results[:12]
|
return results[:12]
|
||||||
|
|
||||||
|
|
||||||
from typing import Optional
|
def get_price(sina_code: str) -> Optional[float]:
|
||||||
|
raw = _fetch_sina_raw(sina_code)
|
||||||
|
return raw["price"] if raw else None
|
||||||
|
|
||||||
|
|
||||||
def get_by_code(code: str) -> Optional[dict]:
|
def get_by_ths_code(ths_code: str) -> Optional[dict]:
|
||||||
for s in SYMBOLS:
|
for p in PRODUCTS:
|
||||||
if s["code"] == code:
|
main = resolve_main_contract(p)
|
||||||
|
if main and main["ths_code"].lower() == ths_code.lower():
|
||||||
|
return main
|
||||||
|
sina = ths_to_sina_code(ths_code)
|
||||||
|
if sina:
|
||||||
|
raw = _fetch_sina_raw(sina)
|
||||||
|
if raw:
|
||||||
return {
|
return {
|
||||||
"name": s["name"],
|
"name": raw["name"],
|
||||||
"code": s["code"],
|
"ths_code": ths_code,
|
||||||
"exchange": s["exchange"],
|
"sina_code": sina,
|
||||||
"contract": _contract_label(s["code"]),
|
"exchange": "",
|
||||||
|
"contract": ths_code,
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|||||||
+3
-2
@@ -7,9 +7,10 @@
|
|||||||
<h2>新增监控</h2>
|
<h2>新增监控</h2>
|
||||||
<form action="{{ url_for('add_key') }}" method="post" class="form-row">
|
<form action="{{ url_for('add_key') }}" method="post" class="form-row">
|
||||||
<div class="symbol-wrap">
|
<div class="symbol-wrap">
|
||||||
<input type="text" class="symbol-input" placeholder="输入中文品种名" autocomplete="off" required>
|
<input type="text" class="symbol-input" placeholder="输入中文名或同花顺代码" autocomplete="off" required>
|
||||||
<input type="hidden" name="symbol" required>
|
<input type="hidden" name="symbol" required>
|
||||||
<input type="hidden" name="symbol_name">
|
<input type="hidden" name="symbol_name">
|
||||||
|
<input type="hidden" name="sina_code" required>
|
||||||
<div class="symbol-dropdown"></div>
|
<div class="symbol-dropdown"></div>
|
||||||
<div class="symbol-selected"></div>
|
<div class="symbol-selected"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
<span class="badge dir">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
<span class="badge dir">{{ '做多' if k.direction == 'long' else '做空' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>上: {{ k.upper }} | 下: {{ k.lower }}</div>
|
<div>上: {{ k.upper }} | 下: {{ k.lower }}</div>
|
||||||
<div style="font-size:.8rem;color:#888">{{ k.symbol }}</div>
|
<div style="font-size:.8rem;color:#888">同花顺: {{ k.symbol }}</div>
|
||||||
<a href="{{ url_for('del_key', pid=k.id) }}" class="btn-del" onclick="return confirm('删除?')">删除</a>
|
<a href="{{ url_for('del_key', pid=k.id) }}" class="btn-del" onclick="return confirm('删除?')">删除</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -7,9 +7,10 @@
|
|||||||
<h2>新增计划</h2>
|
<h2>新增计划</h2>
|
||||||
<form action="{{ url_for('add_plan') }}" method="post" class="form-row">
|
<form action="{{ url_for('add_plan') }}" method="post" class="form-row">
|
||||||
<div class="symbol-wrap">
|
<div class="symbol-wrap">
|
||||||
<input type="text" class="symbol-input" placeholder="输入中文品种名" autocomplete="off" required>
|
<input type="text" class="symbol-input" placeholder="输入中文名或同花顺代码" autocomplete="off" required>
|
||||||
<input type="hidden" name="symbol" required>
|
<input type="hidden" name="symbol" required>
|
||||||
<input type="hidden" name="symbol_name">
|
<input type="hidden" name="symbol_name">
|
||||||
|
<input type="hidden" name="sina_code" required>
|
||||||
<div class="symbol-dropdown"></div>
|
<div class="symbol-dropdown"></div>
|
||||||
<div class="symbol-selected"></div>
|
<div class="symbol-selected"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>区间: {{ p.zone_lower }} ~ {{ p.zone_upper }}</div>
|
<div>区间: {{ p.zone_lower }} ~ {{ p.zone_upper }}</div>
|
||||||
<div>止损: {{ p.stop_loss }} | 止盈: {{ p.take_profit }}</div>
|
<div>止损: {{ p.stop_loss }} | 止盈: {{ p.take_profit }}</div>
|
||||||
<div style="font-size:.8rem;color:#888">{{ p.symbol }}</div>
|
<div style="font-size:.8rem;color:#888">同花顺: {{ p.symbol }}</div>
|
||||||
<a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除此计划?')">删除</a>
|
<a href="{{ url_for('del_plan', pid=p.id) }}" class="btn-del" onclick="return confirm('删除此计划?')">删除</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
Reference in New Issue
Block a user