feat: two-level entry model UI for trend accounts (reversal/trend/swing)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,8 +12,7 @@
|
|||||||
|
|
||||||
- **不写盈亏比(趋势户)**:币安/OKX 止盈/止损随行情人工设定,趋势 MD 不量化 RR。
|
- **不写盈亏比(趋势户)**:币安/OKX 止盈/止损随行情人工设定,趋势 MD 不量化 RR。
|
||||||
- **日内例外**:Gate 日内 **最低 1:1** 才开仓,持仓目标可动态调整(见 [gate-intraday.md](./gate-intraday.md))。
|
- **日内例外**:Gate 日内 **最低 1:1** 才开仓,持仓目标可动态调整(见 [gate-intraday.md](./gate-intraday.md))。
|
||||||
- **界面短标签(趋势户 · 现状 v0.2)**:`大分歧A` / `大分歧B` / `小分歧`。
|
- **界面(趋势户 · v0.4)**:两级 — **反转**(启动 A/B)| **顺势**(大分歧 A/B)| **波段**(小分歧);`lib/trade/entry_model_lib.py` 三所共用。
|
||||||
- **界面规划(趋势户 · v0.4 讨论稿)**:两级 — **反转**(启动 A/B)| **顺势**(大分歧 A/B)| **波段**(小分歧)。做多反转细则见 [binance-alt-trend-long.md §3](./binance-alt-trend-long.md);OKX 做空见 [okx-trend-both.md §3.2](./okx-trend-both.md)。
|
|
||||||
- **界面短标签(日内户)**:`假破` / `结构突破`。
|
- **界面短标签(日内户)**:`假破` / `结构突破`。
|
||||||
- **多空共用三字**:方向由「做多/做空」表达,不复用为「大分歧A多」等。
|
- **多空共用三字**:方向由「做多/做空」表达,不复用为「大分歧A多」等。
|
||||||
- **自动联动**:大分歧 A/B → 趋势单;**小分歧 → 波段单**;平仓写入交易记录 `entry_reason`;复盘「填入」自动带入。
|
- **自动联动**:大分歧 A/B → 趋势单;**小分歧 → 波段单**;平仓写入交易记录 `entry_reason`;复盘「填入」自动带入。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# 币安山寨·多头趋势账户
|
# 币安山寨·多头趋势账户
|
||||||
|
|
||||||
> **状态**:v0.4(讨论稿:反转·启动 A/B 策略正文;**UI 仍为 v0.2 三档**,两级下拉待实现)
|
> **状态**:v0.4(反转·启动 A/B 两级 UI 已实现;策略正文 + `entry_model_lib`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,33 +16,19 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 开仓类型(规划与现状)
|
## 2. 开仓类型(两级 UI · 三所共用)
|
||||||
|
|
||||||
### 2.1 规划:两级选择(讨论定稿,待实现)
|
### 2.1 趋势户:反转 / 顺势 / 波段
|
||||||
|
|
||||||
| 第一级 | 第二级 | 性质 | 计划 code |
|
| 第一级 | 第二级 | code | 联动 |
|
||||||
|--------|--------|------|-----------|
|
|--------|--------|------|------|
|
||||||
| **反转** | 启动 A / 启动 B | 跌后筑底 → 赌新主升 | `launch_a` / `launch_b` |
|
| **反转** | 启动 A / 启动 B | `launch_a` / `launch_b` | 趋势单 |
|
||||||
| **顺势** | 大分歧 A / 大分歧 B | 主升已确立 → 回踩/中继 | `big_div_a` / `big_div_b` |
|
| **顺势** | 大分歧 A / 大分歧 B | `big_div_a` / `big_div_b` | 趋势单 |
|
||||||
| **波段** | 小分歧 | 主升途中短打 | `small_div` |
|
| **波段** | 小分歧 | `small_div` | 波段单 |
|
||||||
|
|
||||||
- 第一级表达 **交易性质**;第二级表达 **突破前 / 实体突破**(小分歧无 A/B)。
|
实现:`lib/trade/entry_model_lib.py` + `order_entry_model_fields.html` + `order_entry_model.js`(币安 / OKX / Gate 趋势户共用)。
|
||||||
- **启动 A** 含两条实操路径(界面仍一个选项,复盘可备注 A1/A2):
|
|
||||||
- **A1**:第二次摸高前的小收敛企稳;
|
|
||||||
- **A2**:启动 B 实体突破失败回箱后,箱子上半区 + 5m 不创新低 + 5m N 字试仓(**不单列类型**)。
|
|
||||||
- **小分歧** 前提:主升**已确立**;反转链进行中**不做**小分歧。
|
|
||||||
|
|
||||||
### 2.2 现状:下单监控 UI(v0.2 已实现)
|
### 2.2 杠杆(与开仓类型无关)
|
||||||
|
|
||||||
| 界面标签 | 存储 code | 联动 |
|
|
||||||
|----------|-----------|------|
|
|
||||||
| 大分歧A | `big_div_a` | 趋势单 |
|
|
||||||
| 大分歧B | `big_div_b` | 趋势单 |
|
|
||||||
| 小分歧 | `small_div` | 波段单 |
|
|
||||||
|
|
||||||
实现反转·启动前,顺势单仍选大分歧 A/B;反转单复盘可先在「其他」或备注中写 **启动 A/B**,待 UI 上线后写入 `entry_model`。
|
|
||||||
|
|
||||||
### 2.3 杠杆(与开仓类型无关)
|
|
||||||
|
|
||||||
| 币种 | 默认杠杆 |
|
| 币种 | 默认杠杆 |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
@@ -193,8 +179,7 @@ MACD 背离(严格筛选)→ 标「参考高点」
|
|||||||
|
|
||||||
| 操作 | 字段 |
|
| 操作 | 字段 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 下单(现状) | `order_monitors.entry_model`:`big_div_a` / `big_div_b` / `small_div` |
|
| 下单 | `order_monitors.entry_model`:`launch_a` / `launch_b` / `big_div_a` / `big_div_b` / `small_div` |
|
||||||
| 下单(规划) | 增加 `launch_a` / `launch_b`;两级 UI |
|
|
||||||
| 平仓 | `trade_records.entry_reason` = 界面标签 |
|
| 平仓 | `trade_records.entry_reason` = 界面标签 |
|
||||||
| 复盘 | 与开仓类型一致 + 策略项 +「其他」 |
|
| 复盘 | 与开仓类型一致 + 策略项 +「其他」 |
|
||||||
|
|
||||||
@@ -204,6 +189,6 @@ MACD 背离(严格筛选)→ 标「参考高点」
|
|||||||
|
|
||||||
| 版本 | 日期 | 说明 |
|
| 版本 | 日期 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| v0.4 | 2026-07-06 | 讨论稿:反转·启动 A/B(含 A1 收敛、A2 突破失败后 5m N);两级 UI 规划 |
|
| v0.4 | 2026-07-06 | 反转·启动 A/B 两级 UI 上线(共用 entry_model_lib) |
|
||||||
| v0.2 | 2026-07-06 | 定稿 UI 短标签(大分歧/小分歧);实现代码联动 |
|
| v0.2 | 2026-07-06 | 定稿 UI 短标签(大分歧/小分歧);实现代码联动 |
|
||||||
| v0.1 | 2026-07-06 | 讨论稿 |
|
| v0.1 | 2026-07-06 | 讨论稿 |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"items": [
|
"items": [
|
||||||
"本账户仅做多,不做空",
|
"本账户仅做多,不做空",
|
||||||
"计仓模式为以损定仓(risk),关键位自动单已关闭",
|
"计仓模式为以损定仓(risk),关键位自动单已关闭",
|
||||||
"已明确第一级:反转 / 顺势 / 波段(UI 待上线时复盘可先备注)"
|
"已明确第一级:反转 / 顺势 / 波段(两级下拉已上线)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,6 +63,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"footnotes": [
|
"footnotes": [
|
||||||
"v0.4:反转·启动为讨论定稿;下单 UI 仍为 v0.2 三档时,反转单请在复盘备注启动 A/B。"
|
"v0.4:两级 UI 已实现;启动 A 含 A1 收敛与 A2(B 失败后 5m N 字)。"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"趋势户 profile(非 Gate 日内 BTC/ETH 白名单)",
|
"趋势户 profile(非 Gate 日内 BTC/ETH 白名单)",
|
||||||
"计仓模式为以损定仓(risk),关键位自动单已关闭",
|
"计仓模式为以损定仓(risk),关键位自动单已关闭",
|
||||||
"同一币种无未计划的对冲叠仓",
|
"同一币种无未计划的对冲叠仓",
|
||||||
"已明确第一级:反转 / 顺势 / 波段(UI 待上线时复盘可先备注)"
|
"已明确第一级:反转 / 顺势 / 波段(两级下拉已上线)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,6 +67,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"footnotes": [
|
"footnotes": [
|
||||||
"v0.4:反转·启动为讨论定稿;做多细则见 binance-alt-trend-long.md §3。UI 仍为 v0.2 三档时,反转单请在复盘备注启动 A/B。"
|
"v0.4:两级 UI 反转/顺势/波段;做多细则见 binance-alt-trend-long.md §3。"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,26 +22,19 @@
|
|||||||
|
|
||||||
## 2. 开仓类型(规划与现状)
|
## 2. 开仓类型(规划与现状)
|
||||||
|
|
||||||
### 2.1 规划:两级选择(讨论定稿,待实现)
|
### 2.1 规划:两级选择(与币安共用实现)
|
||||||
|
|
||||||
| 第一级 | 第二级 | 做多 | 做空 |
|
| 第一级 | 第二级 | 做多 | 做空 |
|
||||||
|--------|--------|------|------|
|
|--------|--------|------|------|
|
||||||
| **反转** | 启动 A / 启动 B | 跌后筑底 → 新主升 | 涨后筑顶 → 新主跌 |
|
| **反转** | 启动 A / 启动 B | `launch_a` / `launch_b` | 同 code,方向在表单 |
|
||||||
| **顺势** | 大分歧 A / B | 主升已确立 | 主跌已确立 |
|
| **顺势** | 大分歧 A / B | `big_div_a` / `big_div_b` | 同左 |
|
||||||
| **波段** | 小分歧 | 主升途中短打 | 主跌途中短打 |
|
| **波段** | 小分歧 | `small_div` | 同左 |
|
||||||
|
|
||||||
计划 code:`launch_a` / `launch_b` / `big_div_a` / `big_div_b` / `small_div`(与币安一致)。
|
`lib/trade/entry_model_lib.py` + 共用模板/JS,三所趋势户一致。
|
||||||
|
|
||||||
- **启动 A**:含 **A1**(第二次摸参考极值前小收敛)与 **A2**(启动 B 实体突破失败回箱后的 5m N 字试仓);**5m 试仓不单列**,统一记启动 A。
|
### 2.2 现状:下单监控 UI
|
||||||
- **小分歧**:仅在大级别趋势**已确立**后;反转链进行中**不做**小分歧。
|
|
||||||
|
|
||||||
### 2.2 现状:下单监控 UI(v0.2 已实现)
|
两级下拉:**性质** → **类型**;提交 `entry_model` code。
|
||||||
|
|
||||||
| 界面标签 | code | 联动 |
|
|
||||||
|----------|------|------|
|
|
||||||
| 大分歧A / B、小分歧 | `big_div_a` 等 | 趋势单 / 波段单 |
|
|
||||||
|
|
||||||
**方向**由表单「做多/做空」决定;**单型**由下拉决定。反转单在 UI 上线前,复盘可备注 **启动 A/B**。
|
|
||||||
|
|
||||||
### 2.3 杠杆
|
### 2.3 杠杆
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
.order-trade-style-hint{font-size:.78rem;color:#8fc8ff;margin-left:4px;white-space:nowrap}
|
.order-trade-style-hint{font-size:.78rem;color:#8fc8ff;margin-left:4px;white-space:nowrap}
|
||||||
|
.order-entry-model-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
|
||||||
|
.order-entry-model-row select.order-entry-category{min-width:4.8em;max-width:6.5em}
|
||||||
|
.order-entry-model-row select.order-entry-model-sub{min-width:7em;max-width:10rem}
|
||||||
.order-leverage-hint{font-size:.78rem;color:#cfd3ef;white-space:nowrap;align-self:center}
|
.order-leverage-hint{font-size:.78rem;color:#cfd3ef;white-space:nowrap;align-self:center}
|
||||||
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px 20px}
|
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:#0b0d14;color:#eaeaea;padding:14px 20px}
|
||||||
.container{width:100%;max-width:min(1440px,94vw);margin:0 auto;padding:0 clamp(8px,1.5vw,20px)}
|
.container{width:100%;max-width:min(1440px,94vw);margin:0 auto;padding:0 clamp(8px,1.5vw,20px)}
|
||||||
|
|||||||
@@ -1,38 +1,125 @@
|
|||||||
(function (global) {
|
(function (global) {
|
||||||
function tradeStyleForSelect(sel) {
|
function categoriesData() {
|
||||||
if (!sel || !sel.value) return "trend";
|
return global.ORDER_ENTRY_MODEL_CATEGORIES || [];
|
||||||
var opt = sel.options[sel.selectedIndex];
|
}
|
||||||
if (opt && opt.getAttribute) {
|
|
||||||
var ds = opt.getAttribute("data-trade-style");
|
function codeToCategoryMap() {
|
||||||
if (ds === "swing" || ds === "trend") return ds;
|
return global.ORDER_ENTRY_MODEL_CODE_TO_CATEGORY || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tradeStyleForCode(code) {
|
||||||
var map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {};
|
var map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {};
|
||||||
return map[sel.value] || "trend";
|
return map[code] || "trend";
|
||||||
|
}
|
||||||
|
|
||||||
|
function findOption(catKey, code) {
|
||||||
|
var cats = categoriesData();
|
||||||
|
for (var i = 0; i < cats.length; i++) {
|
||||||
|
if (cats[i].key !== catKey) continue;
|
||||||
|
var opts = cats[i].options || [];
|
||||||
|
for (var j = 0; j < opts.length; j++) {
|
||||||
|
if (opts[j].code === code) return opts[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildEntryModelSubSelect(preserveCode) {
|
||||||
|
var catSel = document.getElementById("order-entry-category");
|
||||||
|
var modelSel = document.getElementById("order-entry-model");
|
||||||
|
if (!catSel || !modelSel) return;
|
||||||
|
|
||||||
|
var catKey = catSel.value;
|
||||||
|
var cats = categoriesData();
|
||||||
|
var cat = null;
|
||||||
|
for (var i = 0; i < cats.length; i++) {
|
||||||
|
if (cats[i].key === catKey) {
|
||||||
|
cat = cats[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modelSel.innerHTML = "";
|
||||||
|
var placeholder = document.createElement("option");
|
||||||
|
placeholder.value = "";
|
||||||
|
placeholder.textContent = "类型";
|
||||||
|
modelSel.appendChild(placeholder);
|
||||||
|
|
||||||
|
if (!cat || !cat.options || !cat.options.length) {
|
||||||
|
modelSel.disabled = true;
|
||||||
|
modelSel.value = "";
|
||||||
|
syncOrderEntryModelTradeStyle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modelSel.disabled = false;
|
||||||
|
var pick = preserveCode || "";
|
||||||
|
for (var k = 0; k < cat.options.length; k++) {
|
||||||
|
var o = cat.options[k];
|
||||||
|
var opt = document.createElement("option");
|
||||||
|
opt.value = o.code;
|
||||||
|
opt.textContent = o.label;
|
||||||
|
if (o.trade_style) opt.setAttribute("data-trade-style", o.trade_style);
|
||||||
|
if (o.help) opt.title = o.help;
|
||||||
|
modelSel.appendChild(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pick && findOption(catKey, pick)) {
|
||||||
|
modelSel.value = pick;
|
||||||
|
} else if (cat.options.length === 1) {
|
||||||
|
modelSel.value = cat.options[0].code;
|
||||||
|
} else {
|
||||||
|
modelSel.value = "";
|
||||||
|
}
|
||||||
|
syncOrderEntryModelTradeStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncOrderEntryModelTradeStyle() {
|
function syncOrderEntryModelTradeStyle() {
|
||||||
var sel = document.getElementById("order-entry-model");
|
var modelSel = document.getElementById("order-entry-model");
|
||||||
var hidden = document.getElementById("order-trade-style-hidden");
|
var hidden = document.getElementById("order-trade-style-hidden");
|
||||||
var hint = document.getElementById("order-trade-style-hint");
|
var hint = document.getElementById("order-trade-style-hint");
|
||||||
if (!sel || !hidden) return;
|
if (!modelSel || !hidden) return;
|
||||||
var labels = { trend: "趋势单", swing: "波段单" };
|
var labels = { trend: "趋势单", swing: "波段单" };
|
||||||
var ts = tradeStyleForSelect(sel);
|
var code = modelSel.value || "";
|
||||||
|
var ts = tradeStyleForCode(code);
|
||||||
hidden.value = ts;
|
hidden.value = ts;
|
||||||
if (hint) hint.textContent = labels[ts] || ts;
|
if (hint) hint.textContent = labels[ts] || ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initOrderEntryModelSelect(root) {
|
function initOrderEntryModelSelect(root) {
|
||||||
var scope = root && root.querySelector ? root : document;
|
var scope = root && root.querySelector ? root : document;
|
||||||
var sel =
|
var catSel =
|
||||||
|
scope === document
|
||||||
|
? document.getElementById("order-entry-category")
|
||||||
|
: scope.querySelector("#order-entry-category");
|
||||||
|
var modelSel =
|
||||||
scope === document
|
scope === document
|
||||||
? document.getElementById("order-entry-model")
|
? document.getElementById("order-entry-model")
|
||||||
: scope.querySelector("#order-entry-model");
|
: scope.querySelector("#order-entry-model");
|
||||||
if (!sel) return;
|
if (!catSel || !modelSel) return;
|
||||||
if (sel.dataset.entryModelWired !== "1") {
|
|
||||||
sel.dataset.entryModelWired = "1";
|
if (catSel.dataset.entryModelWired !== "1") {
|
||||||
sel.addEventListener("change", syncOrderEntryModelTradeStyle);
|
catSel.dataset.entryModelWired = "1";
|
||||||
|
catSel.addEventListener("change", function () {
|
||||||
|
rebuildEntryModelSubSelect("");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
syncOrderEntryModelTradeStyle();
|
if (modelSel.dataset.entryModelWired !== "1") {
|
||||||
|
modelSel.dataset.entryModelWired = "1";
|
||||||
|
modelSel.addEventListener("change", syncOrderEntryModelTradeStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
var presetCode = modelSel.getAttribute("data-preset-code") || modelSel.value || "";
|
||||||
|
if (presetCode) {
|
||||||
|
var catMap = codeToCategoryMap();
|
||||||
|
var catKey = catMap[presetCode];
|
||||||
|
if (catKey) {
|
||||||
|
catSel.value = catKey;
|
||||||
|
rebuildEntryModelSubSelect(presetCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rebuildEntryModelSubSelect("");
|
||||||
}
|
}
|
||||||
|
|
||||||
global.paintOrderLeverageHint = function (leverage) {
|
global.paintOrderLeverageHint = function (leverage) {
|
||||||
@@ -51,12 +138,9 @@
|
|||||||
|
|
||||||
global.initOrderEntryModelSelect = initOrderEntryModelSelect;
|
global.initOrderEntryModelSelect = initOrderEntryModelSelect;
|
||||||
global.syncOrderEntryModelTradeStyle = syncOrderEntryModelTradeStyle;
|
global.syncOrderEntryModelTradeStyle = syncOrderEntryModelTradeStyle;
|
||||||
|
global.rebuildEntryModelSubSelect = rebuildEntryModelSubSelect;
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
initOrderEntryModelSelect();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
initOrderEntryModelSelect();
|
initOrderEntryModelSelect();
|
||||||
}
|
});
|
||||||
})(typeof window !== "undefined" ? window : globalThis);
|
})(typeof window !== "undefined" ? window : globalThis);
|
||||||
|
|||||||
@@ -1,52 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||||
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
|
||||||
|
|
||||||
function tradeStyleForEntryModelSelect(sel){
|
|
||||||
if(!sel || !sel.value) return "trend";
|
|
||||||
var opt = sel.options[sel.selectedIndex];
|
|
||||||
if(opt){
|
|
||||||
var ds = opt.getAttribute("data-trade-style");
|
|
||||||
if(ds === "swing" || ds === "trend") return ds;
|
|
||||||
}
|
|
||||||
return (ORDER_ENTRY_MODEL_TRADE_STYLE && ORDER_ENTRY_MODEL_TRADE_STYLE[sel.value]) || "trend";
|
|
||||||
}
|
|
||||||
function syncOrderEntryModelTradeStyle(){
|
|
||||||
var sel = document.getElementById("order-entry-model");
|
|
||||||
var hidden = document.getElementById("order-trade-style-hidden");
|
|
||||||
var hint = document.getElementById("order-trade-style-hint");
|
|
||||||
if(!sel || !hidden) return;
|
|
||||||
var labels = { trend: "趋势单", swing: "波段单" };
|
|
||||||
var ts = tradeStyleForEntryModelSelect(sel);
|
|
||||||
hidden.value = ts;
|
|
||||||
if(hint) hint.textContent = labels[ts] || ts;
|
|
||||||
}
|
|
||||||
function paintOrderLeverageHint(leverage){
|
|
||||||
var hidden = document.getElementById("order-leverage");
|
|
||||||
var hint = document.getElementById("order-leverage-hint");
|
|
||||||
if(!hidden && !hint) return;
|
|
||||||
var lev = parseInt(leverage, 10);
|
|
||||||
if(!Number.isFinite(lev) || lev <= 0){
|
|
||||||
if(hint) hint.textContent = "杠杆 —";
|
|
||||||
if(hidden) hidden.value = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(hidden) hidden.value = String(lev);
|
|
||||||
if(hint) hint.textContent = "杠杆 " + lev + "x";
|
|
||||||
}
|
|
||||||
function initOrderEntryModelSelect(root){
|
|
||||||
var scope = (root && root.querySelector) ? root : document;
|
|
||||||
var sel = scope.querySelector ? scope.querySelector("#order-entry-model") : document.getElementById("order-entry-model");
|
|
||||||
if(!sel) return;
|
|
||||||
if(sel.dataset.entryModelWired !== "1"){
|
|
||||||
sel.dataset.entryModelWired = "1";
|
|
||||||
sel.addEventListener("change", syncOrderEntryModelTradeStyle);
|
|
||||||
}
|
|
||||||
syncOrderEntryModelTradeStyle();
|
|
||||||
}
|
|
||||||
window.syncOrderEntryModelTradeStyle = syncOrderEntryModelTradeStyle;
|
|
||||||
window.paintOrderLeverageHint = paintOrderLeverageHint;
|
|
||||||
window.initOrderEntryModelSelect = initOrderEntryModelSelect;
|
|
||||||
|
|
||||||
function reloadInstancePage(){
|
function reloadInstancePage(){
|
||||||
if(document.body && document.body.getAttribute("data-embed-shell") === "1" && window.InstanceEmbed){
|
if(document.body && document.body.getAttribute("data-embed-shell") === "1" && window.InstanceEmbed){
|
||||||
|
|||||||
@@ -122,7 +122,12 @@
|
|||||||
<script src="/static/time_close_ui.js?v=3"></script>
|
<script src="/static/time_close_ui.js?v=3"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script src="/static/order_entry_model.js?v=3"></script>
|
<script>
|
||||||
|
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
||||||
|
const ORDER_ENTRY_MODEL_CATEGORIES = {{ entry_model_categories | tojson }};
|
||||||
|
const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }};
|
||||||
|
</script>
|
||||||
|
<script src="/static/order_entry_model.js?v=4"></script>
|
||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/symbol_live_price.js?v=2"></script>
|
<script src="/static/symbol_live_price.js?v=2"></script>
|
||||||
<script src="/static/strategy_roll.js?v=6"></script>
|
<script src="/static/strategy_roll.js?v=6"></script>
|
||||||
|
|||||||
@@ -554,13 +554,17 @@
|
|||||||
<script src="/static/time_close_ui.js?v=3"></script>
|
<script src="/static/time_close_ui.js?v=3"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script src="/static/order_entry_model.js?v=3"></script>
|
<script>
|
||||||
|
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
||||||
|
const ORDER_ENTRY_MODEL_CATEGORIES = {{ entry_model_categories | tojson }};
|
||||||
|
const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | tojson }};
|
||||||
|
</script>
|
||||||
|
<script src="/static/order_entry_model.js?v=4"></script>
|
||||||
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
<script src="/static/manual_order_rr_preview.js?v=5"></script>
|
||||||
<script src="/static/symbol_live_price.js?v=2"></script>
|
<script src="/static/symbol_live_price.js?v=2"></script>
|
||||||
<script src="/static/strategy_roll.js?v=6"></script>
|
<script src="/static/strategy_roll.js?v=6"></script>
|
||||||
<script>
|
<script>
|
||||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||||
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
|
||||||
|
|
||||||
function validateJournalEntryReason(){
|
function validateJournalEntryReason(){
|
||||||
const form = document.getElementById("journal-form");
|
const form = document.getElementById("journal-form");
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
{# 趋势户:开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #}
|
{# 趋势户:两级开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #}
|
||||||
{% macro order_entry_type_fields() -%}
|
{% macro order_entry_type_fields() -%}
|
||||||
{% if order_entry_profile == 'trend_div' %}
|
{% if order_entry_profile == 'trend_div' %}
|
||||||
<select name="entry_model" id="order-entry-model" required title="大分歧A/B为趋势单,小分歧为波段单" onchange="syncOrderEntryModelTradeStyle()">
|
<div class="order-entry-model-row">
|
||||||
<option value="">开仓类型</option>
|
<select id="order-entry-category" class="order-entry-category" required title="反转 / 顺势 / 波段" aria-label="开仓性质">
|
||||||
{% for opt in entry_model_options %}
|
<option value="">性质</option>
|
||||||
<option value="{{ opt.code }}" data-trade-style="{{ opt.trade_style }}" title="{{ opt.help }}">{{ opt.label }}</option>
|
{% for cat in entry_model_categories %}
|
||||||
{% endfor %}
|
<option value="{{ cat.key }}">{{ cat.label }}</option>
|
||||||
</select>
|
{% endfor %}
|
||||||
<input type="hidden" name="trade_style" id="order-trade-style-hidden" value="trend">
|
</select>
|
||||||
<span id="order-trade-style-hint" class="order-trade-style-hint" title="由开仓类型自动设定">趋势单</span>
|
<select name="entry_model" id="order-entry-model" class="order-entry-model-sub" required disabled title="启动A/B、大分歧A/B、小分歧" aria-label="开仓类型">
|
||||||
|
<option value="">类型</option>
|
||||||
|
</select>
|
||||||
|
<input type="hidden" name="trade_style" id="order-trade-style-hidden" value="trend">
|
||||||
|
<span id="order-trade-style-hint" class="order-trade-style-hint" title="由开仓类型自动设定">趋势单</span>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<select name="trade_style" required>
|
<select name="trade_style" required>
|
||||||
<option value="trend">趋势单</option>
|
<option value="trend">趋势单</option>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<input name="real_rr" class="journal-field-num" placeholder="实际RR">
|
<input name="real_rr" class="journal-field-num" placeholder="实际RR">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-grid journal-form-row2">
|
<div class="form-grid journal-form-row2">
|
||||||
<select name="entry_reason" id="journal-entry-reason" class="journal-field-entry-reason" required title="大分歧A/B/小分歧或策略项">
|
<select name="entry_reason" id="journal-entry-reason" class="journal-field-entry-reason" required title="启动A/B、大分歧A/B、小分歧或策略项">
|
||||||
<option value="">开仓类型(必选)</option>
|
<option value="">开仓类型(必选)</option>
|
||||||
{% for er in entry_reason_options %}
|
{% for er in entry_reason_options %}
|
||||||
<option value="{{ er }}">{{ er }}</option>
|
<option value="{{ er }}">{{ er }}</option>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""大分歧 / 小分歧开仓类型(趋势户);日内户沿用 trade_style,单独 profile。"""
|
"""趋势户开仓类型:反转·启动 / 顺势·大分歧 / 波段·小分歧;日内户单独 profile。"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -9,19 +9,35 @@ from lib.trade.trade_policy_lib import TradePolicy
|
|||||||
PROFILE_TREND_DIV = "trend_div"
|
PROFILE_TREND_DIV = "trend_div"
|
||||||
PROFILE_INTRADAY = "intraday"
|
PROFILE_INTRADAY = "intraday"
|
||||||
|
|
||||||
|
ENTRY_CATEGORY_REVERSAL = "reversal"
|
||||||
|
ENTRY_CATEGORY_TREND = "trend"
|
||||||
|
ENTRY_CATEGORY_SWING = "swing"
|
||||||
|
|
||||||
|
ENTRY_MODEL_LAUNCH_A = "launch_a"
|
||||||
|
ENTRY_MODEL_LAUNCH_B = "launch_b"
|
||||||
ENTRY_MODEL_BIG_DIV_A = "big_div_a"
|
ENTRY_MODEL_BIG_DIV_A = "big_div_a"
|
||||||
ENTRY_MODEL_BIG_DIV_B = "big_div_b"
|
ENTRY_MODEL_BIG_DIV_B = "big_div_b"
|
||||||
ENTRY_MODEL_SMALL_DIV = "small_div"
|
ENTRY_MODEL_SMALL_DIV = "small_div"
|
||||||
|
|
||||||
VALID_ENTRY_MODEL_CODES = frozenset(
|
VALID_ENTRY_MODEL_CODES = frozenset(
|
||||||
{
|
{
|
||||||
|
ENTRY_MODEL_LAUNCH_A,
|
||||||
|
ENTRY_MODEL_LAUNCH_B,
|
||||||
ENTRY_MODEL_BIG_DIV_A,
|
ENTRY_MODEL_BIG_DIV_A,
|
||||||
ENTRY_MODEL_BIG_DIV_B,
|
ENTRY_MODEL_BIG_DIV_B,
|
||||||
ENTRY_MODEL_SMALL_DIV,
|
ENTRY_MODEL_SMALL_DIV,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ENTRY_CATEGORY_LABELS: dict[str, str] = {
|
||||||
|
ENTRY_CATEGORY_REVERSAL: "反转",
|
||||||
|
ENTRY_CATEGORY_TREND: "顺势",
|
||||||
|
ENTRY_CATEGORY_SWING: "波段",
|
||||||
|
}
|
||||||
|
|
||||||
TREND_DIV_ENTRY_REASON_LABELS: Tuple[str, ...] = (
|
TREND_DIV_ENTRY_REASON_LABELS: Tuple[str, ...] = (
|
||||||
|
"启动A",
|
||||||
|
"启动B",
|
||||||
"大分歧A",
|
"大分歧A",
|
||||||
"大分歧B",
|
"大分歧B",
|
||||||
"小分歧",
|
"小分歧",
|
||||||
@@ -35,31 +51,56 @@ INTRADAY_LEGACY_TREND_ENTRY_REASONS: Tuple[str, ...] = (
|
|||||||
"波段单:5m顺势突破,确认条件:2根k线+成交量放大+4h同向+日成交量前20",
|
"波段单:5m顺势突破,确认条件:2根k线+成交量放大+4h同向+日成交量前20",
|
||||||
)
|
)
|
||||||
|
|
||||||
_ENTRY_SPECS: Tuple[Tuple[str, str, str, str], ...] = (
|
# code, label, category, trade_style, help
|
||||||
|
_ENTRY_SPECS: Tuple[Tuple[str, str, str, str, str], ...] = (
|
||||||
|
(
|
||||||
|
ENTRY_MODEL_LAUNCH_A,
|
||||||
|
"启动A",
|
||||||
|
ENTRY_CATEGORY_REVERSAL,
|
||||||
|
"trend",
|
||||||
|
"反转链结构内:摸参考极值前小收敛,或 B 失败后 5m N 字试仓(不单列)",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ENTRY_MODEL_LAUNCH_B,
|
||||||
|
"启动B",
|
||||||
|
ENTRY_CATEGORY_REVERSAL,
|
||||||
|
"trend",
|
||||||
|
"第二次到参考极值附近、无小收敛时的实体突破",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
ENTRY_MODEL_BIG_DIV_A,
|
ENTRY_MODEL_BIG_DIV_A,
|
||||||
"大分歧A",
|
"大分歧A",
|
||||||
|
ENTRY_CATEGORY_TREND,
|
||||||
"trend",
|
"trend",
|
||||||
"突破前收敛小结构,不创新低企稳(空:不创新高)",
|
"主升已确立:突破前收敛,不创新低企稳(空:不创新高)",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
ENTRY_MODEL_BIG_DIV_B,
|
ENTRY_MODEL_BIG_DIV_B,
|
||||||
"大分歧B",
|
"大分歧B",
|
||||||
|
ENTRY_CATEGORY_TREND,
|
||||||
"trend",
|
"trend",
|
||||||
"结构突破确认后入场",
|
"主升已确立:结构实体突破确认后入场",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
ENTRY_MODEL_SMALL_DIV,
|
ENTRY_MODEL_SMALL_DIV,
|
||||||
"小分歧",
|
"小分歧",
|
||||||
|
ENTRY_CATEGORY_SWING,
|
||||||
"swing",
|
"swing",
|
||||||
"二次探底 N 字突破,或 5m 三均线重新多头(空:二次探顶 / 空头均线)",
|
"主升已确立:二次探底 N 字或 5m 三均线重新多头(空:二次探顶 / 空头均线)",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_CODE_TO_LABEL = {code: label for code, label, _, _ in _ENTRY_SPECS}
|
_CODE_TO_LABEL = {code: label for code, label, _, _, _ in _ENTRY_SPECS}
|
||||||
_CODE_TO_STYLE = {code: style for code, _, style, _ in _ENTRY_SPECS}
|
_CODE_TO_STYLE = {code: style for code, _, _, style, _ in _ENTRY_SPECS}
|
||||||
_LABEL_TO_CODE = {label: code for code, label, _, _ in _ENTRY_SPECS}
|
_CODE_TO_CATEGORY = {code: cat for code, _, cat, _, _ in _ENTRY_SPECS}
|
||||||
_CODE_TO_HELP = {code: help for code, _, _, help in _ENTRY_SPECS}
|
_LABEL_TO_CODE = {label: code for code, label, _, _, _ in _ENTRY_SPECS}
|
||||||
|
_CODE_TO_HELP = {code: help for code, _, _, _, help in _ENTRY_SPECS}
|
||||||
|
|
||||||
|
_CATEGORY_ORDER: Tuple[str, ...] = (
|
||||||
|
ENTRY_CATEGORY_REVERSAL,
|
||||||
|
ENTRY_CATEGORY_TREND,
|
||||||
|
ENTRY_CATEGORY_SWING,
|
||||||
|
)
|
||||||
|
|
||||||
_INTRADAY_WHITELIST = frozenset({"BTC", "ETH"})
|
_INTRADAY_WHITELIST = frozenset({"BTC", "ETH"})
|
||||||
|
|
||||||
@@ -68,6 +109,7 @@ _INTRADAY_WHITELIST = frozenset({"BTC", "ETH"})
|
|||||||
class EntryModelOption:
|
class EntryModelOption:
|
||||||
code: str
|
code: str
|
||||||
label: str
|
label: str
|
||||||
|
category: str
|
||||||
trade_style: str
|
trade_style: str
|
||||||
help: str
|
help: str
|
||||||
|
|
||||||
@@ -87,11 +129,43 @@ def order_entry_profile(policy: TradePolicy) -> str:
|
|||||||
|
|
||||||
def entry_model_options() -> Tuple[EntryModelOption, ...]:
|
def entry_model_options() -> Tuple[EntryModelOption, ...]:
|
||||||
return tuple(
|
return tuple(
|
||||||
EntryModelOption(code=code, label=label, trade_style=style, help=help)
|
EntryModelOption(code=code, label=label, category=cat, trade_style=style, help=help)
|
||||||
for code, label, style, help in _ENTRY_SPECS
|
for code, label, cat, style, help in _ENTRY_SPECS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def entry_model_categories() -> list[dict[str, Any]]:
|
||||||
|
"""两级 UI:反转 / 顺势 / 波段 → 子选项。"""
|
||||||
|
opts = entry_model_options()
|
||||||
|
out: list[dict[str, Any]] = []
|
||||||
|
for cat_key in _CATEGORY_ORDER:
|
||||||
|
children = [
|
||||||
|
{
|
||||||
|
"code": o.code,
|
||||||
|
"label": o.label,
|
||||||
|
"trade_style": o.trade_style,
|
||||||
|
"help": o.help,
|
||||||
|
}
|
||||||
|
for o in opts
|
||||||
|
if o.category == cat_key
|
||||||
|
]
|
||||||
|
if not children:
|
||||||
|
continue
|
||||||
|
out.append(
|
||||||
|
{
|
||||||
|
"key": cat_key,
|
||||||
|
"label": ENTRY_CATEGORY_LABELS.get(cat_key, cat_key),
|
||||||
|
"options": children,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def entry_model_category(code: Optional[str]) -> str:
|
||||||
|
c = normalize_entry_model_code(code)
|
||||||
|
return _CODE_TO_CATEGORY.get(c, "")
|
||||||
|
|
||||||
|
|
||||||
def normalize_entry_model_code(raw: Optional[str]) -> str:
|
def normalize_entry_model_code(raw: Optional[str]) -> str:
|
||||||
v = (raw or "").strip().lower()
|
v = (raw or "").strip().lower()
|
||||||
if v in VALID_ENTRY_MODEL_CODES:
|
if v in VALID_ENTRY_MODEL_CODES:
|
||||||
@@ -160,7 +234,7 @@ def parse_manual_order_style_fields(
|
|||||||
|
|
||||||
entry_model = normalize_entry_model_code(form.get("entry_model"))
|
entry_model = normalize_entry_model_code(form.get("entry_model"))
|
||||||
if not entry_model:
|
if not entry_model:
|
||||||
return "", None, "请选择开仓类型(大分歧A / 大分歧B / 小分歧)"
|
return "", None, "请选择开仓类型(反转 / 顺势 / 波段)"
|
||||||
trade_style = trade_style_for_entry_model(entry_model)
|
trade_style = trade_style_for_entry_model(entry_model)
|
||||||
return trade_style, entry_model, None
|
return trade_style, entry_model, None
|
||||||
|
|
||||||
@@ -197,6 +271,10 @@ def enrich_entry_model_display(item: dict) -> dict:
|
|||||||
if code:
|
if code:
|
||||||
item["entry_model"] = code
|
item["entry_model"] = code
|
||||||
item["entry_model_label"] = entry_model_label(code)
|
item["entry_model_label"] = entry_model_label(code)
|
||||||
|
cat = entry_model_category(code)
|
||||||
|
if cat:
|
||||||
|
item["entry_model_category"] = cat
|
||||||
|
item["entry_model_category_label"] = ENTRY_CATEGORY_LABELS.get(cat, "")
|
||||||
else:
|
else:
|
||||||
item.setdefault("entry_model_label", "")
|
item.setdefault("entry_model_label", "")
|
||||||
return item
|
return item
|
||||||
@@ -209,10 +287,18 @@ def order_entry_template_context(policy: TradePolicy) -> dict:
|
|||||||
"order_entry_profile": profile,
|
"order_entry_profile": profile,
|
||||||
"intraday_discipline": profile == PROFILE_INTRADAY,
|
"intraday_discipline": profile == PROFILE_INTRADAY,
|
||||||
"entry_model_options": [
|
"entry_model_options": [
|
||||||
{"code": o.code, "label": o.label, "trade_style": o.trade_style, "help": o.help}
|
{
|
||||||
|
"code": o.code,
|
||||||
|
"label": o.label,
|
||||||
|
"category": o.category,
|
||||||
|
"trade_style": o.trade_style,
|
||||||
|
"help": o.help,
|
||||||
|
}
|
||||||
for o in opts
|
for o in opts
|
||||||
],
|
],
|
||||||
|
"entry_model_categories": entry_model_categories(),
|
||||||
"entry_model_trade_style_map": {o.code: o.trade_style for o in opts},
|
"entry_model_trade_style_map": {o.code: o.trade_style for o in opts},
|
||||||
|
"entry_model_code_to_category": {o.code: o.category for o in opts},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,14 @@ import unittest
|
|||||||
from lib.trade.entry_model_lib import (
|
from lib.trade.entry_model_lib import (
|
||||||
ENTRY_MODEL_BIG_DIV_A,
|
ENTRY_MODEL_BIG_DIV_A,
|
||||||
ENTRY_MODEL_BIG_DIV_B,
|
ENTRY_MODEL_BIG_DIV_B,
|
||||||
|
ENTRY_MODEL_LAUNCH_A,
|
||||||
|
ENTRY_MODEL_LAUNCH_B,
|
||||||
ENTRY_MODEL_SMALL_DIV,
|
ENTRY_MODEL_SMALL_DIV,
|
||||||
|
ENTRY_CATEGORY_REVERSAL,
|
||||||
|
ENTRY_CATEGORY_TREND,
|
||||||
build_trend_div_entry_reason_options,
|
build_trend_div_entry_reason_options,
|
||||||
|
entry_model_categories,
|
||||||
|
entry_model_category,
|
||||||
entry_model_label,
|
entry_model_label,
|
||||||
hub_meta_entry_context,
|
hub_meta_entry_context,
|
||||||
is_intraday_trading_profile,
|
is_intraday_trading_profile,
|
||||||
@@ -35,12 +41,23 @@ class TestEntryModelLib(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.assertFalse(is_intraday_trading_profile(policy))
|
self.assertFalse(is_intraday_trading_profile(policy))
|
||||||
self.assertEqual(trend_manual_entry_reason_count(policy), 3)
|
self.assertEqual(trend_manual_entry_reason_count(policy), 5)
|
||||||
|
|
||||||
def test_entry_model_maps_trade_style(self):
|
def test_entry_model_maps_trade_style(self):
|
||||||
|
self.assertEqual(trade_style_for_entry_model(ENTRY_MODEL_LAUNCH_A), "trend")
|
||||||
self.assertEqual(trade_style_for_entry_model(ENTRY_MODEL_BIG_DIV_A), "trend")
|
self.assertEqual(trade_style_for_entry_model(ENTRY_MODEL_BIG_DIV_A), "trend")
|
||||||
self.assertEqual(trade_style_for_entry_model(ENTRY_MODEL_SMALL_DIV), "swing")
|
self.assertEqual(trade_style_for_entry_model(ENTRY_MODEL_SMALL_DIV), "swing")
|
||||||
self.assertEqual(entry_model_label(ENTRY_MODEL_BIG_DIV_A), "大分歧A")
|
self.assertEqual(entry_model_label(ENTRY_MODEL_LAUNCH_B), "启动B")
|
||||||
|
self.assertEqual(entry_model_category(ENTRY_MODEL_LAUNCH_A), ENTRY_CATEGORY_REVERSAL)
|
||||||
|
self.assertEqual(entry_model_category(ENTRY_MODEL_BIG_DIV_B), ENTRY_CATEGORY_TREND)
|
||||||
|
|
||||||
|
def test_entry_model_categories_two_level(self):
|
||||||
|
cats = entry_model_categories()
|
||||||
|
keys = [c["key"] for c in cats]
|
||||||
|
self.assertEqual(keys, ["reversal", "trend", "swing"])
|
||||||
|
reversal = cats[0]["options"]
|
||||||
|
self.assertEqual([o["code"] for o in reversal], ["launch_a", "launch_b"])
|
||||||
|
self.assertEqual(len(cats[2]["options"]), 1)
|
||||||
|
|
||||||
def test_parse_trend_div_requires_entry_model(self):
|
def test_parse_trend_div_requires_entry_model(self):
|
||||||
policy = TradePolicy(False, "both", False, ())
|
policy = TradePolicy(False, "both", False, ())
|
||||||
@@ -55,6 +72,13 @@ class TestEntryModelLib(unittest.TestCase):
|
|||||||
self.assertEqual(code, ENTRY_MODEL_SMALL_DIV)
|
self.assertEqual(code, ENTRY_MODEL_SMALL_DIV)
|
||||||
self.assertEqual(style, "swing")
|
self.assertEqual(style, "swing")
|
||||||
|
|
||||||
|
style, code, err = parse_manual_order_style_fields(
|
||||||
|
policy, {"entry_model": ENTRY_MODEL_LAUNCH_A}
|
||||||
|
)
|
||||||
|
self.assertIsNone(err)
|
||||||
|
self.assertEqual(code, ENTRY_MODEL_LAUNCH_A)
|
||||||
|
self.assertEqual(style, "trend")
|
||||||
|
|
||||||
def test_hub_meta_intraday(self):
|
def test_hub_meta_intraday(self):
|
||||||
policy = load_trade_policy(
|
policy = load_trade_policy(
|
||||||
{
|
{
|
||||||
@@ -74,10 +98,12 @@ class TestEntryModelLib(unittest.TestCase):
|
|||||||
def test_resolve_entry_reason_from_model(self):
|
def test_resolve_entry_reason_from_model(self):
|
||||||
er = resolve_trade_record_entry_reason(entry_model=ENTRY_MODEL_BIG_DIV_B)
|
er = resolve_trade_record_entry_reason(entry_model=ENTRY_MODEL_BIG_DIV_B)
|
||||||
self.assertEqual(er, "大分歧B")
|
self.assertEqual(er, "大分歧B")
|
||||||
|
er2 = resolve_trade_record_entry_reason(entry_model=ENTRY_MODEL_LAUNCH_A)
|
||||||
|
self.assertEqual(er2, "启动A")
|
||||||
|
|
||||||
def test_build_trend_div_journal_options(self):
|
def test_build_trend_div_journal_options(self):
|
||||||
opts = build_trend_div_entry_reason_options(("趋势回调",))
|
opts = build_trend_div_entry_reason_options(("趋势回调",))
|
||||||
self.assertEqual(opts[:3], ("大分歧A", "大分歧B", "小分歧"))
|
self.assertEqual(opts[:5], ("启动A", "启动B", "大分歧A", "大分歧B", "小分歧"))
|
||||||
self.assertIn("趋势回调", opts)
|
self.assertIn("趋势回调", opts)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user