Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions zhishi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__/
*.pyc
.venv/
.env
.DS_Store
*.log
3 changes: 3 additions & 0 deletions zhishi/PARENT_NOTE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 姊妹投教产品(独立)

仓库内 [`zhishi/`](./zhishi/) 为面向大陆 A股/港股通的投教产品 **知势**(独立品牌与契约,不互通账号计费)。详见 `zhishi/README.md`。
37 changes: 37 additions & 0 deletions zhishi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 知势 Zhishi — A股 / 港股通投教产品

独立于 QuantRadar 的大陆投教产品:**复盘教练 + 课程框架自检**,不荐股。

## 快速开始

```bash
cd zhishi
python3 scripts/generate_fixtures.py # 生成 20 个教学案例日线
python3 -m app # http://0.0.0.0:8787
python3 -m unittest discover -s tests -v
```

环境变量见 [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md)。默认 `ZHISHI_MODE=artifact`。

## 文档

| 文档 | 说明 |
|------|------|
| [docs/BRAND.md](docs/BRAND.md) | 品牌锁定 |
| [docs/COMPLIANCE.md](docs/COMPLIANCE.md) | 禁荐股边界 |
| [docs/TRUST_GATE.md](docs/TRUST_GATE.md) | 信任门控 |
| [docs/WIREFRAMES.md](docs/WIREFRAMES.md) | 主路径线框 |
| [docs/ENGINE_CONTRACT.md](docs/ENGINE_CONTRACT.md) | API 契约 |
| [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md) | 数据源授权 |
| [docs/LICENSED_PARTNER.md](docs/LICENSED_PARTNER.md) | 持牌合作路径 |

## 功能地图

- H5:`/` 案例 → 自检 → 复盘本 → 错题本 → 港股通差异 → 会员定价
- 小程序壳:[`miniprogram/`](miniprogram/)
- SKU:免费 / ¥68 月 / ¥488 年 / ¥129 课包(mock 支付)
- 法务:`/terms` `/privacy` `/disclaimer` `/methodology`

## 拆仓说明

本目录设计为**独立产品根**;可整体迁到单独 GitHub 仓库。勿与美股 QuantRadar 账号或订阅互通。
3 changes: 3 additions & 0 deletions zhishi/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from __future__ import annotations

# 知势应用包
4 changes: 4 additions & 0 deletions zhishi/app/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from app.server import main

if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions zhishi/app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""知势 — 配置。默认 artifact,live 需显式开关 + token。"""
from __future__ import annotations

import os
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "fixtures"
CONTENT = ROOT / "content"

HOST = os.environ.get("HOST", "0.0.0.0")
PORT = int(os.environ.get("PORT", os.environ.get("ZHISHI_PORT", "8787")))
MODE = os.environ.get("ZHISHI_MODE", "artifact").strip().lower()
ALLOW_LIVE = os.environ.get("ZHISHI_ALLOW_LIVE", "0").strip() in {"1", "true", "yes"}
TUSHARE_TOKEN = os.environ.get("TUSHARE_TOKEN", "").strip()
SESSION_SECRET = os.environ.get("SESSION_SECRET", "zhishi-dev-secret-change-me")

DISCLAIMER = "本产品为投资教育工具,不构成任何投资建议。市场有风险,决策须自负。"
CONTRACT_VERSION = "1.0.0"

# 知识付费 SKU(微信支付商品名须与此类目一致)
SKUS = {
"free": {"name": "免费导读", "price_fen": 0, "period": None},
"monthly": {"name": "月度研习会员", "price_fen": 6800, "period": "month"},
"yearly": {"name": "年度研习会员", "price_fen": 48800, "period": "year"},
"course_traps": {"name": "课包·A股制度与行为陷阱", "price_fen": 12900, "period": None},
}


def live_eligible() -> bool:
return ALLOW_LIVE and bool(TUSHARE_TOKEN) and MODE == "live"
10 changes: 10 additions & 0 deletions zhishi/app/engine/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from app.engine.data import load_bars, load_fixture
from app.engine.score import analyze, fail_closed, posture_for

__all__ = [
"analyze",
"fail_closed",
"load_bars",
"load_fixture",
"posture_for",
]
80 changes: 80 additions & 0 deletions zhishi/app/engine/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""行情加载:默认 fixture;live 仅在授权后走 Tushare。"""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from app import config


def _symbol_path(symbol: str) -> Path:
return config.FIXTURES / "ohlcv" / f"{symbol}.json"


def load_fixture(symbol: str) -> dict[str, Any] | None:
path = _symbol_path(symbol)
if not path.is_file():
return None
with path.open(encoding="utf-8") as f:
return json.load(f)


def load_bars(symbol: str, mode: str | None = None) -> dict[str, Any] | None:
mode = (mode or config.MODE).lower()
if mode == "live" and config.live_eligible():
live = _load_tushare(symbol)
if live is not None:
return live
# fail-closed:live 失败不静默编造
return None
return load_fixture(symbol)


def _load_tushare(symbol: str) -> dict[str, Any] | None:
"""可选依赖:未安装或失败则返回 None。"""
try:
import tushare as ts # type: ignore
except Exception:
return None
try:
pro = ts.pro_api(config.TUSHARE_TOKEN)
ts_code = _to_ts_code(symbol)
df = pro.daily(ts_code=ts_code, limit=120)
if df is None or df.empty:
return None
basic = pro.stock_basic(ts_code=ts_code, fields="ts_code,name,market,list_status")
name = str(basic.iloc[0]["name"]) if basic is not None and not basic.empty else symbol
bars = []
for _, row in df.sort_values("trade_date").iterrows():
bars.append(
{
"date": str(row["trade_date"]),
"open": float(row["open"]),
"high": float(row["high"]),
"low": float(row["low"]),
"close": float(row["close"]),
"volume": float(row["vol"]),
}
)
return {
"symbol": symbol,
"name": name,
"market": "A",
"is_st": "ST" in name.upper(),
"is_star_or_chinext": ts_code.startswith("688") or ts_code.startswith("300"),
"bars": bars,
"source": "tushare",
}
except Exception:
return None


def _to_ts_code(symbol: str) -> str:
if symbol.startswith("6"):
return f"{symbol}.SH"
if symbol.startswith(("0", "3")):
return f"{symbol}.SZ"
if symbol.startswith(("4", "8")):
return f"{symbol}.BJ"
return f"{symbol}.SH"
Loading
Loading