Initial commit
Made-with: Cursor
This commit is contained in:
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Python
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
54
srde/README.md
Normal file
54
srde/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# SRDE - Structured Risk Decision Engine
|
||||||
|
|
||||||
|
Deterministic 风控投资系统 Demo
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
srde/
|
||||||
|
├── backend/ # FastAPI 后端(Python)
|
||||||
|
├── frontend/ # React Web 前端
|
||||||
|
├── miniprogram/ # 微信小程序前端
|
||||||
|
└── docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速启动
|
||||||
|
|
||||||
|
### 方式一:Docker(需 Docker Desktop)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd srde
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
- Web 前端: http://localhost
|
||||||
|
- API: http://localhost:8000
|
||||||
|
|
||||||
|
### 方式二:本地开发
|
||||||
|
|
||||||
|
**后端**
|
||||||
|
```bash
|
||||||
|
cd srde/backend
|
||||||
|
python -m venv .venv && source .venv/bin/activate
|
||||||
|
pip install -r requirements.txt
|
||||||
|
# 启动 PostgreSQL 后:
|
||||||
|
export DATABASE_URL=postgresql://user:pass@localhost:5432/srde
|
||||||
|
alembic upgrade head
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
**Web 前端**
|
||||||
|
```bash
|
||||||
|
cd srde/frontend && npm install && npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**微信小程序**
|
||||||
|
1. 用微信开发者工具打开 `srde/miniprogram`
|
||||||
|
2. 默认使用模拟数据,可离线浏览
|
||||||
|
3. 对接后端:修改 `miniprogram/services/api.ts` 中 `USE_MOCK=false` 和 `BASE_URL`
|
||||||
|
|
||||||
|
## Demo 说明
|
||||||
|
|
||||||
|
- **Web**:登录、账户、创建交易、交易列表、复盘
|
||||||
|
- **小程序**:风控首页、分步创建交易、交易详情、复盘、统计、个人中心
|
||||||
|
- **后端**:JWT 认证、风控引擎、仓位计算、回撤/锁仓规则
|
||||||
33
srde/miniprogram/README.md
Normal file
33
srde/miniprogram/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# SRDE Risk Control Mini Program
|
||||||
|
|
||||||
|
微信小程序风控前端,对接 SRDE 后端 API。
|
||||||
|
|
||||||
|
## 运行方式
|
||||||
|
|
||||||
|
1. 安装 [微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)
|
||||||
|
2. 打开项目:选择 `srde/miniprogram` 目录
|
||||||
|
3. 使用「测试号」或填写 AppID 后预览
|
||||||
|
|
||||||
|
## 对接真实后端
|
||||||
|
|
||||||
|
1. 修改 `services/api.ts`:
|
||||||
|
- 设置 `USE_MOCK = false`
|
||||||
|
- 设置 `BASE_URL = 'https://your-api.com'`(你的后端地址)
|
||||||
|
|
||||||
|
2. 登录流程:
|
||||||
|
- 后端需提供 `POST /auth/login`,返回 `{ access_token }`
|
||||||
|
- 小程序将 token 存入 `wx.setStorageSync('srde_token', token)`
|
||||||
|
- 后续请求自动携带 `Authorization: Bearer {token}`
|
||||||
|
|
||||||
|
3. API 映射(后端需实现):
|
||||||
|
- `GET /account/status` → 账户状态
|
||||||
|
- `POST /account/capital` → 设置资金
|
||||||
|
- `POST /trade/create` → 创建交易
|
||||||
|
- `POST /trade/{id}/close` → 关闭交易
|
||||||
|
- `GET /trade/` → 交易列表
|
||||||
|
- `POST /review/{id}`、`GET /review/{id}` → 复盘
|
||||||
|
- `GET /statistics` → 统计数据(胜率、盈亏比等)
|
||||||
|
|
||||||
|
## 模拟模式
|
||||||
|
|
||||||
|
默认 `USE_MOCK = true`,使用本地模拟数据,不依赖后端即可完整浏览流程。
|
||||||
27
srde/miniprogram/app.json
Normal file
27
srde/miniprogram/app.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
"pages/dashboard/dashboard",
|
||||||
|
"pages/create/create",
|
||||||
|
"pages/trade-detail/trade-detail",
|
||||||
|
"pages/review/review",
|
||||||
|
"pages/statistics/statistics",
|
||||||
|
"pages/profile/profile"
|
||||||
|
],
|
||||||
|
"window": {
|
||||||
|
"backgroundTextStyle": "dark",
|
||||||
|
"navigationBarBackgroundColor": "#2a2a2a",
|
||||||
|
"navigationBarTitleText": "SRDE 风控",
|
||||||
|
"navigationBarTextStyle": "white"
|
||||||
|
},
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#999",
|
||||||
|
"selectedColor": "#5a9",
|
||||||
|
"backgroundColor": "#2a2a2a",
|
||||||
|
"list": [
|
||||||
|
{ "pagePath": "pages/dashboard/dashboard", "text": "首页" },
|
||||||
|
{ "pagePath": "pages/create/create", "text": "创建" },
|
||||||
|
{ "pagePath": "pages/statistics/statistics", "text": "统计" },
|
||||||
|
{ "pagePath": "pages/profile/profile", "text": "我的" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
32
srde/miniprogram/app.ts
Normal file
32
srde/miniprogram/app.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// SRDE Risk Control Mini Program
|
||||||
|
interface IAccountState {
|
||||||
|
total_capital: number;
|
||||||
|
current_capital: number;
|
||||||
|
current_drawdown: number;
|
||||||
|
max_drawdown: number;
|
||||||
|
consecutive_losses: number;
|
||||||
|
trading_locked_until: string | null;
|
||||||
|
status: 'tradable' | 'compressed' | 'locked';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppOption {
|
||||||
|
globalData: {
|
||||||
|
token: string | null;
|
||||||
|
account: IAccountState | null;
|
||||||
|
accountLoadedAt: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
App<IAppOption>({
|
||||||
|
globalData: {
|
||||||
|
token: wx.getStorageSync('srde_token') || null,
|
||||||
|
account: null,
|
||||||
|
accountLoadedAt: 0,
|
||||||
|
},
|
||||||
|
onLaunch() {
|
||||||
|
const token = wx.getStorageSync('srde_token');
|
||||||
|
if (token) {
|
||||||
|
this.globalData.token = token;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
27
srde/miniprogram/app.wxss
Normal file
27
srde/miniprogram/app.wxss
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
page {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: #333;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tradable { color: #5a9; }
|
||||||
|
.status-compressed { color: #b95; }
|
||||||
|
.status-locked { color: #a44; }
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
text: { type: String, value: '确认' },
|
||||||
|
duration: { type: Number, value: 3 },
|
||||||
|
},
|
||||||
|
data: { countdown: 0, loading: false },
|
||||||
|
methods: {
|
||||||
|
onTap() {
|
||||||
|
if (this.data.countdown > 0 || this.data.loading) return;
|
||||||
|
this.setData({ countdown: this.properties.duration });
|
||||||
|
const t = setInterval(() => {
|
||||||
|
const n = this.data.countdown - 1;
|
||||||
|
this.setData({ countdown: n });
|
||||||
|
if (n <= 0) clearInterval(t);
|
||||||
|
}, 1000);
|
||||||
|
this.triggerEvent('confirm');
|
||||||
|
},
|
||||||
|
setLoading(v: boolean) {
|
||||||
|
this.setData({ loading: v });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<button
|
||||||
|
class="cooldown-btn"
|
||||||
|
disabled="{{countdown > 0 || loading}}"
|
||||||
|
loading="{{loading}}"
|
||||||
|
bindtap="onTap"
|
||||||
|
>
|
||||||
|
{{countdown > 0 ? countdown + '秒' : (loading ? '提交中' : text)}}
|
||||||
|
</button>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.cooldown-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 28rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
background: #5a9;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.cooldown-btn[disabled] {
|
||||||
|
background: #888;
|
||||||
|
}
|
||||||
4
srde/miniprogram/components/risk-card/risk-card.json
Normal file
4
srde/miniprogram/components/risk-card/risk-card.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
7
srde/miniprogram/components/risk-card/risk-card.ts
Normal file
7
srde/miniprogram/components/risk-card/risk-card.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
title: { type: String, value: '' },
|
||||||
|
value: { type: String, value: '' },
|
||||||
|
sub: { type: String, value: '' },
|
||||||
|
},
|
||||||
|
});
|
||||||
5
srde/miniprogram/components/risk-card/risk-card.wxml
Normal file
5
srde/miniprogram/components/risk-card/risk-card.wxml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<view class="risk-card card">
|
||||||
|
<view class="card-title">{{title}}</view>
|
||||||
|
<view class="value">{{value}}</view>
|
||||||
|
<view class="sub" wx:if="{{sub}}">{{sub}}</view>
|
||||||
|
</view>
|
||||||
14
srde/miniprogram/components/risk-card/risk-card.wxss
Normal file
14
srde/miniprogram/components/risk-card/risk-card.wxss
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.risk-card {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.risk-card .value {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.risk-card .sub {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
19
srde/miniprogram/components/status-badge/status-badge.ts
Normal file
19
srde/miniprogram/components/status-badge/status-badge.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const STATUS_TEXT: Record<string, string> = {
|
||||||
|
tradable: '可交易',
|
||||||
|
compressed: '风险压缩',
|
||||||
|
locked: '锁仓',
|
||||||
|
};
|
||||||
|
|
||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
status: { type: String, value: 'tradable' },
|
||||||
|
text: { type: String, value: '' },
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {
|
||||||
|
const s = this.properties.status;
|
||||||
|
const t = this.properties.text || STATUS_TEXT[s] || s;
|
||||||
|
this.setData({ text: t });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<view class="badge status-{{status}}">{{text || (status === 'tradable' ? '可交易' : (status === 'compressed' ? '风险压缩' : '锁仓'))}}</view>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
.status-tradable { background: rgba(85,170,153,0.2); color: #5a9; }
|
||||||
|
.status-compressed { background: rgba(187,153,85,0.2); color: #b95; }
|
||||||
|
.status-locked { background: rgba(170,68,68,0.2); color: #a44; }
|
||||||
4
srde/miniprogram/components/trade-item/trade-item.json
Normal file
4
srde/miniprogram/components/trade-item/trade-item.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
18
srde/miniprogram/components/trade-item/trade-item.ts
Normal file
18
srde/miniprogram/components/trade-item/trade-item.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Component({
|
||||||
|
properties: {
|
||||||
|
id: String,
|
||||||
|
symbol: String,
|
||||||
|
direction: String,
|
||||||
|
entry_price: [String, Number],
|
||||||
|
status: String,
|
||||||
|
position_size: [String, Number],
|
||||||
|
pnl: Number,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onTap() {
|
||||||
|
if (this.data.id) {
|
||||||
|
wx.navigateTo({ url: `/pages/trade-detail/trade-detail?id=${this.data.id}` });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
14
srde/miniprogram/components/trade-item/trade-item.wxml
Normal file
14
srde/miniprogram/components/trade-item/trade-item.wxml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<view class="trade-item card" bindtap="onTap">
|
||||||
|
<view class="row">
|
||||||
|
<text class="symbol">{{symbol}}</text>
|
||||||
|
<text class="dir {{direction}}">{{direction === 'long' ? '多' : '空'}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="row sub">
|
||||||
|
<text>入场 {{entry_price}}</text>
|
||||||
|
<text class="status-{{status}}">{{status === 'open' ? '持仓' : '已平'}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="row sub" wx:if="{{pnl !== undefined}}">
|
||||||
|
<text>盈亏</text>
|
||||||
|
<text class="{{pnl >= 0 ? 'profit' : 'loss'}}">{{pnl >= 0 ? '+' : ''}}{{pnl}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
9
srde/miniprogram/components/trade-item/trade-item.wxss
Normal file
9
srde/miniprogram/components/trade-item/trade-item.wxss
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.trade-item { cursor: pointer; }
|
||||||
|
.trade-item .row { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
|
||||||
|
.trade-item .symbol { font-weight: 600; color: #333; }
|
||||||
|
.trade-item .dir { font-size: 24rpx; }
|
||||||
|
.trade-item .dir.long { color: #5a9; }
|
||||||
|
.trade-item .dir.short { color: #95a; }
|
||||||
|
.trade-item .sub { font-size: 24rpx; color: #888; }
|
||||||
|
.trade-item .profit { color: #5a9; }
|
||||||
|
.trade-item .loss { color: #a44; }
|
||||||
6
srde/miniprogram/pages/create/create.json
Normal file
6
srde/miniprogram/pages/create/create.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {
|
||||||
|
"cooldown-button": "/components/cooldown-button/cooldown-button"
|
||||||
|
},
|
||||||
|
"navigationBarTitleText": "创建交易"
|
||||||
|
}
|
||||||
65
srde/miniprogram/pages/create/create.ts
Normal file
65
srde/miniprogram/pages/create/create.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
step: 1,
|
||||||
|
logic: '',
|
||||||
|
symbol: '',
|
||||||
|
direction: 'long' as 'long' | 'short',
|
||||||
|
entry_price: '',
|
||||||
|
stop_loss: '',
|
||||||
|
take_profit: '',
|
||||||
|
upPct: 0,
|
||||||
|
downPct: 0,
|
||||||
|
odds: '',
|
||||||
|
position_size: '',
|
||||||
|
},
|
||||||
|
onLogicInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ logic: (e.detail.value as string).trim() });
|
||||||
|
},
|
||||||
|
onSymbolInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ symbol: (e.detail.value as string).trim() });
|
||||||
|
},
|
||||||
|
setDir(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ direction: e.currentTarget.dataset.dir as 'long' | 'short' });
|
||||||
|
},
|
||||||
|
onEntryInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ entry_price: e.detail.value as string }, () => this.calc());
|
||||||
|
},
|
||||||
|
onStopInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ stop_loss: e.detail.value as string }, () => this.calc());
|
||||||
|
},
|
||||||
|
onTpInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ take_profit: e.detail.value as string }, () => this.calc());
|
||||||
|
},
|
||||||
|
calc() {
|
||||||
|
const { entry_price, stop_loss, take_profit, direction } = this.data;
|
||||||
|
const e = parseFloat(entry_price);
|
||||||
|
const s = parseFloat(stop_loss);
|
||||||
|
const t = parseFloat(take_profit);
|
||||||
|
if (!e || !s || !t) return;
|
||||||
|
const upPct = direction === 'long' ? ((t - e) / e * 100) : ((e - t) / e * 100);
|
||||||
|
const downPct = direction === 'long' ? ((e - s) / e * 100) : ((s - e) / e * 100);
|
||||||
|
const risk = direction === 'long' ? e - s : s - e;
|
||||||
|
const reward = direction === 'long' ? t - e : e - t;
|
||||||
|
const odds = risk > 0 && reward > 0 ? (reward / risk).toFixed(2) : '';
|
||||||
|
this.setData({ upPct: upPct.toFixed(1), downPct: downPct.toFixed(1), odds });
|
||||||
|
},
|
||||||
|
nextStep() {
|
||||||
|
const { step, logic } = this.data;
|
||||||
|
if (step === 1 && logic.length < 30) return;
|
||||||
|
if (step === 2) {
|
||||||
|
this.setData({ position_size: '0.5', odds: this.data.odds || '-' });
|
||||||
|
}
|
||||||
|
this.setData({ step: step + 1 });
|
||||||
|
},
|
||||||
|
prevStep() {
|
||||||
|
this.setData({ step: this.data.step - 1 });
|
||||||
|
},
|
||||||
|
onConfirm() {
|
||||||
|
wx.showLoading({ title: '提交中' });
|
||||||
|
setTimeout(() => {
|
||||||
|
wx.hideLoading();
|
||||||
|
wx.showToast({ title: '已创建' });
|
||||||
|
wx.navigateBack();
|
||||||
|
}, 800);
|
||||||
|
},
|
||||||
|
});
|
||||||
33
srde/miniprogram/pages/create/create.wxml
Normal file
33
srde/miniprogram/pages/create/create.wxml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<view class="page">
|
||||||
|
<view class="step-indicator">步骤 {{step}} / 3</view>
|
||||||
|
<view wx:if="{{step === 1}}" class="section card">
|
||||||
|
<view class="label">交易逻辑(≥30字)</view>
|
||||||
|
<textarea class="textarea" value="{{logic}}" placeholder="请输入你的交易逻辑..." maxlength="500" bindinput="onLogicInput" />
|
||||||
|
<view class="count {{logic.length >= 30 ? 'ok' : ''}}">{{logic.length}}/30</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{step === 2}}" class="section card">
|
||||||
|
<view class="label">标的</view>
|
||||||
|
<input class="input" value="{{symbol}}" placeholder="如 BTCUSDT" bindinput="onSymbolInput" />
|
||||||
|
<view class="label">方向</view>
|
||||||
|
<view class="row">
|
||||||
|
<view class="option {{direction === 'long' ? 'active' : ''}}" bindtap="setDir" data-dir="long">做多</view>
|
||||||
|
<view class="option {{direction === 'short' ? 'active' : ''}}" bindtap="setDir" data-dir="short">做空</view>
|
||||||
|
</view>
|
||||||
|
<view class="label">入场价、止损价、止盈价</view>
|
||||||
|
<input class="input" type="digit" value="{{entry_price}}" placeholder="入场价" bindinput="onEntryInput" />
|
||||||
|
<input class="input" type="digit" value="{{stop_loss}}" placeholder="止损价" bindinput="onStopInput" />
|
||||||
|
<input class="input" type="digit" value="{{take_profit}}" placeholder="止盈价" bindinput="onTpInput" />
|
||||||
|
<view class="row calc">上涨% {{upPct}}% 下跌% {{downPct}}%</view>
|
||||||
|
</view>
|
||||||
|
<view wx:if="{{step === 3}}" class="section card">
|
||||||
|
<view class="label">建议仓位(来自后端)</view>
|
||||||
|
<view class="value">{{position_size || '-'}}</view>
|
||||||
|
<view class="label">赔率</view>
|
||||||
|
<view class="value">{{odds || '-'}}</view>
|
||||||
|
<cooldown-button text="确认提交" duration="3" bind:confirm="onConfirm" />
|
||||||
|
</view>
|
||||||
|
<view class="nav">
|
||||||
|
<button wx:if="{{step > 1}}" bindtap="prevStep">上一步</button>
|
||||||
|
<button wx:if="{{step < 3}}" bindtap="nextStep" disabled="{{step === 1 && logic.length < 30}}">下一步</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
15
srde/miniprogram/pages/create/create.wxss
Normal file
15
srde/miniprogram/pages/create/create.wxss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.page { padding: 20rpx; }
|
||||||
|
.section { margin-bottom: 24rpx; }
|
||||||
|
.step-indicator { color: #888; margin-bottom: 24rpx; font-size: 28rpx; }
|
||||||
|
.label { color: #666; margin-bottom: 12rpx; font-size: 26rpx; }
|
||||||
|
.input, .textarea { width: 100%; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; box-sizing: border-box; }
|
||||||
|
.textarea { min-height: 160rpx; }
|
||||||
|
.count { font-size: 24rpx; color: #888; }
|
||||||
|
.count.ok { color: #5a9; }
|
||||||
|
.row { display: flex; gap: 24rpx; margin-bottom: 24rpx; }
|
||||||
|
.option { flex: 1; padding: 24rpx; text-align: center; border: 1rpx solid #ddd; border-radius: 12rpx; }
|
||||||
|
.option.active { background: #5a9; color: #fff; border-color: #5a9; }
|
||||||
|
.calc { justify-content: space-between; color: #666; }
|
||||||
|
.value { font-size: 36rpx; font-weight: 600; color: #333; margin-bottom: 24rpx; }
|
||||||
|
.nav { display: flex; gap: 24rpx; margin-top: 40rpx; }
|
||||||
|
.nav button { flex: 1; }
|
||||||
8
srde/miniprogram/pages/dashboard/dashboard.json
Normal file
8
srde/miniprogram/pages/dashboard/dashboard.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {
|
||||||
|
"risk-card": "/components/risk-card/risk-card",
|
||||||
|
"status-badge": "/components/status-badge/status-badge",
|
||||||
|
"trade-item": "/components/trade-item/trade-item"
|
||||||
|
},
|
||||||
|
"navigationBarTitleText": "风控首页"
|
||||||
|
}
|
||||||
37
srde/miniprogram/pages/dashboard/dashboard.ts
Normal file
37
srde/miniprogram/pages/dashboard/dashboard.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { mockAccount, mockTrades } from '../../services/api';
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
loading: true,
|
||||||
|
error: '',
|
||||||
|
account: mockAccount,
|
||||||
|
trades: mockTrades.slice(0, 3),
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
const app = getApp<IAppOption>();
|
||||||
|
if (app.globalData.account && Date.now() - app.globalData.accountLoadedAt < 30000) {
|
||||||
|
this.setData({ account: app.globalData.account });
|
||||||
|
} else {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load() {
|
||||||
|
this.setData({ loading: true, error: '' });
|
||||||
|
const app = getApp<IAppOption>();
|
||||||
|
setTimeout(() => {
|
||||||
|
app.globalData.account = mockAccount;
|
||||||
|
app.globalData.accountLoadedAt = Date.now();
|
||||||
|
this.setData({
|
||||||
|
loading: false,
|
||||||
|
account: mockAccount,
|
||||||
|
trades: mockTrades.slice(0, 3),
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
goCreate() {
|
||||||
|
wx.navigateTo({ url: '/pages/create/create' });
|
||||||
|
},
|
||||||
|
});
|
||||||
36
srde/miniprogram/pages/dashboard/dashboard.wxml
Normal file
36
srde/miniprogram/pages/dashboard/dashboard.wxml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<view class="page" wx:if="{{!loading && !error}}">
|
||||||
|
<view class="section">
|
||||||
|
<status-badge status="{{account.status}}" />
|
||||||
|
</view>
|
||||||
|
<view class="section card">
|
||||||
|
<view class="row wrap">
|
||||||
|
<risk-card title="总资金" value="{{account.total_capital}}" />
|
||||||
|
<risk-card title="当前回撤" value="{{account.current_drawdown}}%" />
|
||||||
|
<risk-card title="连续亏损" value="{{account.consecutive_losses}}" />
|
||||||
|
</view>
|
||||||
|
<view class="row wrap">
|
||||||
|
<risk-card title="今日最大风险" value="{{account.daily_risk_limit}}" />
|
||||||
|
<risk-card title="单笔最大风险" value="{{account.single_risk_limit}}" />
|
||||||
|
</view>
|
||||||
|
<view class="lock-msg" wx:if="{{account.status === 'locked'}}">当前处于锁仓期,请勿交易</view>
|
||||||
|
</view>
|
||||||
|
<view class="section" wx:if="{{account.status !== 'locked'}}">
|
||||||
|
<button class="btn-primary" bindtap="goCreate">创建交易计划</button>
|
||||||
|
</view>
|
||||||
|
<view class="section">
|
||||||
|
<view class="card-title">最近交易</view>
|
||||||
|
<trade-item
|
||||||
|
wx:for="{{trades}}"
|
||||||
|
wx:key="id"
|
||||||
|
id="{{item.id}}"
|
||||||
|
symbol="{{item.symbol}}"
|
||||||
|
direction="{{item.direction}}"
|
||||||
|
entry_price="{{item.entry_price}}"
|
||||||
|
status="{{item.status}}"
|
||||||
|
position_size="{{item.position_size}}"
|
||||||
|
pnl="{{item.pnl}}"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="loading" wx:if="{{loading}}">加载中...</view>
|
||||||
|
<view class="error" wx:if="{{error}}">{{error}}</view>
|
||||||
15
srde/miniprogram/pages/dashboard/dashboard.wxss
Normal file
15
srde/miniprogram/pages/dashboard/dashboard.wxss
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.page { padding: 20rpx; }
|
||||||
|
.section { margin-bottom: 24rpx; }
|
||||||
|
.row.wrap { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||||
|
.card-title { color: #333; font-size: 28rpx; margin-bottom: 16rpx; padding: 0 20rpx; }
|
||||||
|
.btn-primary {
|
||||||
|
width: 100%;
|
||||||
|
padding: 28rpx;
|
||||||
|
background: #5a9;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.lock-msg { color: #a44; padding: 16rpx 20rpx; font-size: 26rpx; }
|
||||||
|
.loading, .error { padding: 80rpx; text-align: center; color: #888; }
|
||||||
6
srde/miniprogram/pages/profile/profile.json
Normal file
6
srde/miniprogram/pages/profile/profile.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {
|
||||||
|
"status-badge": "/components/status-badge/status-badge"
|
||||||
|
},
|
||||||
|
"navigationBarTitleText": "我的"
|
||||||
|
}
|
||||||
49
srde/miniprogram/pages/profile/profile.ts
Normal file
49
srde/miniprogram/pages/profile/profile.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { mockAccount } from '../../services/api';
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
account: mockAccount,
|
||||||
|
capital: '',
|
||||||
|
saving: false,
|
||||||
|
resetting: false,
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const app = getApp<IAppOption>();
|
||||||
|
const acc = app.globalData.account || mockAccount;
|
||||||
|
this.setData({ account: acc, capital: String(acc.total_capital) });
|
||||||
|
},
|
||||||
|
onCapitalInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ capital: e.detail.value as string });
|
||||||
|
},
|
||||||
|
onSaveCapital() {
|
||||||
|
const cap = parseFloat(this.data.capital);
|
||||||
|
if (!cap || cap <= 0) {
|
||||||
|
wx.showToast({ title: '请输入有效金额', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setData({ saving: true });
|
||||||
|
setTimeout(() => {
|
||||||
|
const acc = { ...mockAccount, total_capital: cap, current_capital: cap };
|
||||||
|
getApp<IAppOption>().globalData.account = acc;
|
||||||
|
this.setData({ saving: false, account: acc });
|
||||||
|
wx.showToast({ title: '已保存' });
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
onReset() {
|
||||||
|
wx.showModal({
|
||||||
|
title: '确认重置',
|
||||||
|
content: '将清空所有交易数据,确定吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
this.setData({ resetting: true });
|
||||||
|
setTimeout(() => {
|
||||||
|
const acc = { ...mockAccount, current_capital: mockAccount.total_capital, consecutive_losses: 0, trading_locked_until: null, status: 'tradable' as const };
|
||||||
|
getApp<IAppOption>().globalData.account = acc;
|
||||||
|
this.setData({ resetting: false, account: acc });
|
||||||
|
wx.showToast({ title: '已重置' });
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
15
srde/miniprogram/pages/profile/profile.wxml
Normal file
15
srde/miniprogram/pages/profile/profile.wxml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<view class="page">
|
||||||
|
<view class="card">
|
||||||
|
<view class="label">当前状态</view>
|
||||||
|
<status-badge status="{{account.status}}" />
|
||||||
|
<view class="lock-until" wx:if="{{account.trading_locked_until}}">锁仓至 {{account.trading_locked_until}}</view>
|
||||||
|
</view>
|
||||||
|
<view class="card">
|
||||||
|
<view class="label">修改总资金</view>
|
||||||
|
<input class="input" type="digit" value="{{capital}}" placeholder="输入新总资金" bindinput="onCapitalInput" />
|
||||||
|
<button class="btn" bindtap="onSaveCapital" loading="{{saving}}">保存</button>
|
||||||
|
</view>
|
||||||
|
<view class="card danger">
|
||||||
|
<button class="btn-reset" bindtap="onReset" loading="{{resetting}}">重置账户(危险操作)</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
6
srde/miniprogram/pages/profile/profile.wxss
Normal file
6
srde/miniprogram/pages/profile/profile.wxss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.page { padding: 20rpx; }
|
||||||
|
.label { color: #888; margin-bottom: 12rpx; }
|
||||||
|
.input { width: 100%; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; box-sizing: border-box; margin-bottom: 24rpx; }
|
||||||
|
.btn { width: 100%; padding: 28rpx; background: #5a9; color: #fff; border-radius: 16rpx; border: none; }
|
||||||
|
.lock-until { color: #a44; margin-top: 16rpx; font-size: 26rpx; }
|
||||||
|
.btn-reset { width: 100%; padding: 28rpx; background: #a44; color: #fff; border-radius: 16rpx; border: none; }
|
||||||
4
srde/miniprogram/pages/review/review.json
Normal file
4
srde/miniprogram/pages/review/review.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {},
|
||||||
|
"navigationBarTitleText": "复盘"
|
||||||
|
}
|
||||||
36
srde/miniprogram/pages/review/review.ts
Normal file
36
srde/miniprogram/pages/review/review.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
id: '',
|
||||||
|
exit_price: '',
|
||||||
|
loading: false,
|
||||||
|
submitted: false,
|
||||||
|
pnlPct: 0,
|
||||||
|
violated: false,
|
||||||
|
score: 0,
|
||||||
|
aiSummary: '',
|
||||||
|
},
|
||||||
|
onLoad(opt: { id?: string }) {
|
||||||
|
this.setData({ id: opt.id || '' });
|
||||||
|
},
|
||||||
|
onExitInput(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
this.setData({ exit_price: e.detail.value as string });
|
||||||
|
},
|
||||||
|
onSubmit() {
|
||||||
|
const exit = parseFloat(this.data.exit_price);
|
||||||
|
if (!exit) {
|
||||||
|
wx.showToast({ title: '请输入退出价', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setData({ loading: true });
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setData({
|
||||||
|
loading: false,
|
||||||
|
submitted: true,
|
||||||
|
pnlPct: 2.1,
|
||||||
|
violated: false,
|
||||||
|
score: 85,
|
||||||
|
aiSummary: '本次交易执行符合计划,止盈目标达成。建议继续保持当前风险控制节奏。',
|
||||||
|
});
|
||||||
|
}, 800);
|
||||||
|
},
|
||||||
|
});
|
||||||
16
srde/miniprogram/pages/review/review.wxml
Normal file
16
srde/miniprogram/pages/review/review.wxml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<view class="page">
|
||||||
|
<view class="card" wx:if="{{!submitted}}">
|
||||||
|
<view class="label">退出价</view>
|
||||||
|
<input class="input" type="digit" value="{{exit_price}}" bindinput="onExitInput" placeholder="输入实际退出价格" />
|
||||||
|
<button class="btn" bindtap="onSubmit" loading="{{loading}}">生成复盘</button>
|
||||||
|
</view>
|
||||||
|
<view class="card" wx:else>
|
||||||
|
<view class="row"><text class="label">盈亏 %</text><text class="{{pnlPct >= 0 ? 'profit' : 'loss'}}">{{pnlPct}}%</text></view>
|
||||||
|
<view class="row"><text class="label">是否违反止损</text><text>{{violated ? '是' : '否'}}</text></view>
|
||||||
|
<view class="row"><text class="label">纪律评分</text><text>{{score}}</text></view>
|
||||||
|
<view class="block">
|
||||||
|
<view class="label">AI 评语</view>
|
||||||
|
<text class="ai">{{aiSummary}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
9
srde/miniprogram/pages/review/review.wxss
Normal file
9
srde/miniprogram/pages/review/review.wxss
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.page { padding: 20rpx; }
|
||||||
|
.label { color: #888; margin-bottom: 12rpx; }
|
||||||
|
.input { width: 100%; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; box-sizing: border-box; margin-bottom: 24rpx; }
|
||||||
|
.btn { width: 100%; padding: 28rpx; background: #5a9; color: #fff; border-radius: 16rpx; border: none; }
|
||||||
|
.row { display: flex; justify-content: space-between; padding: 20rpx 0; border-bottom: 1rpx solid #eee; }
|
||||||
|
.profit { color: #5a9; }
|
||||||
|
.loss { color: #a44; }
|
||||||
|
.block { padding: 24rpx 0; }
|
||||||
|
.ai { font-size: 28rpx; color: #333; line-height: 1.6; }
|
||||||
6
srde/miniprogram/pages/statistics/statistics.json
Normal file
6
srde/miniprogram/pages/statistics/statistics.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {
|
||||||
|
"risk-card": "/components/risk-card/risk-card"
|
||||||
|
},
|
||||||
|
"navigationBarTitleText": "统计数据"
|
||||||
|
}
|
||||||
48
srde/miniprogram/pages/statistics/statistics.ts
Normal file
48
srde/miniprogram/pages/statistics/statistics.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { mockStats } from '../../services/api';
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: {
|
||||||
|
loading: true,
|
||||||
|
stats: mockStats,
|
||||||
|
w: 300,
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const sys = wx.getSystemInfoSync();
|
||||||
|
this.setData({
|
||||||
|
stats: mockStats,
|
||||||
|
loading: false,
|
||||||
|
w: (sys.windowWidth || 320) - 40,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onReady() {
|
||||||
|
this.drawChart();
|
||||||
|
},
|
||||||
|
drawChart() {
|
||||||
|
const curve = mockStats.equity_curve;
|
||||||
|
const query = wx.createSelectorQuery().in(this);
|
||||||
|
query.select('#chart').fields({ node: true, size: true }).exec((res: any) => {
|
||||||
|
const canvas = res[0]?.node;
|
||||||
|
if (!canvas) return;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const dpr = wx.getSystemInfoSync().pixelRatio || 2;
|
||||||
|
canvas.width = this.data.w * dpr;
|
||||||
|
canvas.height = 200 * dpr;
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
const w = this.data.w;
|
||||||
|
const h = 200;
|
||||||
|
const min = Math.min(...curve);
|
||||||
|
const max = Math.max(...curve);
|
||||||
|
const range = max - min || 1;
|
||||||
|
ctx.strokeStyle = '#5a9';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
curve.forEach((v: number, i: number) => {
|
||||||
|
const x = (i / (curve.length - 1)) * (w - 20) + 10;
|
||||||
|
const y = h - 20 - ((v - min) / range) * (h - 40);
|
||||||
|
if (i === 0) ctx.moveTo(x, y);
|
||||||
|
else ctx.lineTo(x, y);
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
16
srde/miniprogram/pages/statistics/statistics.wxml
Normal file
16
srde/miniprogram/pages/statistics/statistics.wxml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<view class="page" wx:if="{{!loading}}">
|
||||||
|
<view class="section card">
|
||||||
|
<view class="row wrap">
|
||||||
|
<risk-card title="胜率" value="{{stats.win_rate}}%" />
|
||||||
|
<risk-card title="平均赔率" value="{{stats.avg_odds}}" />
|
||||||
|
<risk-card title="盈亏比" value="{{stats.profit_factor}}" />
|
||||||
|
<risk-card title="最大回撤" value="{{stats.max_drawdown}}%" />
|
||||||
|
<risk-card title="风险评分" value="{{stats.risk_score}}" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="section card">
|
||||||
|
<view class="card-title">权益曲线</view>
|
||||||
|
<canvas type="2d" id="chart" class="chart" style="width:{{w}}px;height:200px;"></canvas>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="loading" wx:if="{{loading}}">加载中...</view>
|
||||||
5
srde/miniprogram/pages/statistics/statistics.wxss
Normal file
5
srde/miniprogram/pages/statistics/statistics.wxss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.page { padding: 20rpx; }
|
||||||
|
.row.wrap { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||||
|
.card-title { color: #333; font-size: 28rpx; margin-bottom: 24rpx; }
|
||||||
|
.chart { width: 100%; background: #f8f8f8; border-radius: 12rpx; }
|
||||||
|
.loading { padding: 80rpx; text-align: center; color: #888; }
|
||||||
4
srde/miniprogram/pages/trade-detail/trade-detail.json
Normal file
4
srde/miniprogram/pages/trade-detail/trade-detail.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"usingComponents": {},
|
||||||
|
"navigationBarTitleText": "交易详情"
|
||||||
|
}
|
||||||
21
srde/miniprogram/pages/trade-detail/trade-detail.ts
Normal file
21
srde/miniprogram/pages/trade-detail/trade-detail.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { mockTrades } from '../../services/api';
|
||||||
|
|
||||||
|
Page({
|
||||||
|
data: { trade: null as any },
|
||||||
|
onLoad(opt: { id?: string }) {
|
||||||
|
const id = opt.id;
|
||||||
|
const t = mockTrades.find((x: any) => x.id === id) || mockTrades[0];
|
||||||
|
this.setData({
|
||||||
|
trade: {
|
||||||
|
...t,
|
||||||
|
take_profit: (t as any).entry_price * 1.02,
|
||||||
|
stop_loss: (t as any).entry_price * 0.98,
|
||||||
|
position_size: 0.5,
|
||||||
|
logic: '示例交易逻辑文本,用于展示交易计划的结构与内容。',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
wx.navigateTo({ url: '/pages/review/review?id=' + this.data.trade.id });
|
||||||
|
},
|
||||||
|
});
|
||||||
15
srde/miniprogram/pages/trade-detail/trade-detail.wxml
Normal file
15
srde/miniprogram/pages/trade-detail/trade-detail.wxml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<view class="page" wx:if="{{trade}}">
|
||||||
|
<view class="card">
|
||||||
|
<view class="row"><text class="label">标的</text><text>{{trade.symbol}}</text></view>
|
||||||
|
<view class="row"><text class="label">方向</text><text>{{trade.direction === 'long' ? '多' : '空'}}</text></view>
|
||||||
|
<view class="row"><text class="label">入场价</text><text>{{trade.entry_price}}</text></view>
|
||||||
|
<view class="row"><text class="label">目标价</text><text>{{trade.take_profit}}</text></view>
|
||||||
|
<view class="row"><text class="label">止损价</text><text>{{trade.stop_loss}}</text></view>
|
||||||
|
<view class="row"><text class="label">建议仓位</text><text>{{trade.position_size}}</text></view>
|
||||||
|
<view class="row"><text class="label">逻辑</text><text class="logic">{{trade.logic || '-'}}</text></view>
|
||||||
|
</view>
|
||||||
|
<view class="section" wx:if="{{trade.status === 'open'}}">
|
||||||
|
<button class="btn-danger" bindtap="onClose">关闭交易</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view wx:else class="loading">加载中...</view>
|
||||||
6
srde/miniprogram/pages/trade-detail/trade-detail.wxss
Normal file
6
srde/miniprogram/pages/trade-detail/trade-detail.wxss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.page { padding: 20rpx; }
|
||||||
|
.row { display: flex; justify-content: space-between; padding: 20rpx 0; border-bottom: 1rpx solid #eee; }
|
||||||
|
.row .label { color: #888; }
|
||||||
|
.logic { flex: 1; margin-left: 24rpx; word-break: break-all; }
|
||||||
|
.btn-danger { width: 100%; padding: 28rpx; background: #a44; color: #fff; border-radius: 16rpx; border: none; }
|
||||||
|
.loading { padding: 80rpx; text-align: center; color: #888; }
|
||||||
12
srde/miniprogram/project.config.json
Normal file
12
srde/miniprogram/project.config.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"miniprogramRoot": "./",
|
||||||
|
"appid": "touristappid",
|
||||||
|
"projectname": "srde-miniprogram",
|
||||||
|
"setting": {
|
||||||
|
"es6": true,
|
||||||
|
"enhance": true,
|
||||||
|
"postcss": true,
|
||||||
|
"minified": true
|
||||||
|
},
|
||||||
|
"compileType": "miniprogram"
|
||||||
|
}
|
||||||
64
srde/miniprogram/services/api.ts
Normal file
64
srde/miniprogram/services/api.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// API 层 - 对接后端 SRDE,支持模拟数据
|
||||||
|
const USE_MOCK = true;
|
||||||
|
const BASE_URL = 'https://your-api.com'; // 对接时改为真实地址
|
||||||
|
|
||||||
|
function getToken(): string {
|
||||||
|
return wx.getStorageSync('srde_token') || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function request<T>(opt: { url: string; method?: string; data?: object }): Promise<T> {
|
||||||
|
if (USE_MOCK) return Promise.reject(new Error('MOCK模式'));
|
||||||
|
const token = getToken();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.request({
|
||||||
|
url: BASE_URL + (opt.url.startsWith('/') ? opt.url : '/' + opt.url),
|
||||||
|
method: (opt.method as any) || 'GET',
|
||||||
|
data: opt.data,
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
if ((res.statusCode || 0) >= 200 && (res.statusCode || 0) < 300) {
|
||||||
|
resolve(res.data as T);
|
||||||
|
} else reject(new Error((res.data as any)?.detail || '请求失败'));
|
||||||
|
},
|
||||||
|
fail: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMockMode(use: boolean) {
|
||||||
|
(global as any).__SRDE_USE_MOCK__ = use;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMockMode(): boolean {
|
||||||
|
return USE_MOCK || !!(global as any).__SRDE_USE_MOCK__;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mockAccount = {
|
||||||
|
total_capital: 100000,
|
||||||
|
current_capital: 98500,
|
||||||
|
current_drawdown: 1.5,
|
||||||
|
max_drawdown: 5.2,
|
||||||
|
consecutive_losses: 1,
|
||||||
|
trading_locked_until: null as string | null,
|
||||||
|
status: 'tradable' as 'tradable' | 'compressed' | 'locked',
|
||||||
|
daily_risk_limit: 2000,
|
||||||
|
single_risk_limit: 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockTrades = [
|
||||||
|
{ id: '1', symbol: 'BTCUSDT', direction: 'long', entry_price: 42000, status: 'open', position_size: 0.5 },
|
||||||
|
{ id: '2', symbol: 'ETHUSDT', direction: 'short', entry_price: 2200, status: 'closed', pnl: -120 },
|
||||||
|
{ id: '3', symbol: 'SOLUSDT', direction: 'long', entry_price: 98, status: 'closed', pnl: 85 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const mockStats = {
|
||||||
|
win_rate: 62,
|
||||||
|
avg_odds: 1.8,
|
||||||
|
profit_factor: 2.1,
|
||||||
|
max_drawdown: 5.2,
|
||||||
|
risk_score: 72,
|
||||||
|
equity_curve: [100000, 101200, 99800, 100500, 102100, 100800, 98500],
|
||||||
|
};
|
||||||
17
srde/miniprogram/typings/index.d.ts
vendored
Normal file
17
srde/miniprogram/typings/index.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
declare interface IAppOption {
|
||||||
|
globalData: {
|
||||||
|
token: string | null;
|
||||||
|
account: {
|
||||||
|
total_capital: number;
|
||||||
|
current_capital: number;
|
||||||
|
current_drawdown: number;
|
||||||
|
max_drawdown: number;
|
||||||
|
consecutive_losses: number;
|
||||||
|
trading_locked_until: string | null;
|
||||||
|
status: 'tradable' | 'compressed' | 'locked';
|
||||||
|
daily_risk_limit?: number;
|
||||||
|
single_risk_limit?: number;
|
||||||
|
} | null;
|
||||||
|
accountLoadedAt: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user