feat: 趋势户开仓类型大分歧A/B/小分歧,自动联动趋势波段与复盘
下单监控增加 entry_model 下拉;平仓写入短标签并预填复盘。Binance/OKX 用趋势 profile,Gate 日内仍用手选 trade_style。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+9792
-9773
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@
|
||||
<link rel="manifest" href="/static/icons/manifest.webmanifest">
|
||||
<title>{{ exchange_display }} · 加密货币 | 交易监控复盘系统</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
.order-trade-style-hint{font-size:.78rem;color:#8fc8ff;margin-left:4px;white-space:nowrap}
|
||||
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)}
|
||||
.header{display:flex;flex-direction:column;align-items:center;gap:8px;margin-bottom:12px}
|
||||
@@ -385,10 +385,8 @@
|
||||
<option value="price">止盈止损:价格模式</option>
|
||||
<option value="pct">止盈止损:百分比模式</option>
|
||||
</select>
|
||||
<select name="trade_style" required>
|
||||
<option value="trend">趋势单</option>
|
||||
<option value="swing">波段单</option>
|
||||
</select>
|
||||
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
|
||||
{{ order_entry_type_fields() }}
|
||||
{% if position_sizing_mode != 'full_margin' %}
|
||||
<input id="order-leverage" name="leverage" type="number" min="1" step="1" placeholder="杠杆(可选)">
|
||||
{% endif %}
|
||||
@@ -464,7 +462,7 @@
|
||||
</div>
|
||||
<div class="pos-meta">
|
||||
<span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风格: {{ o.trade_style or 'trend' }}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %}</span>
|
||||
<span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
|
||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||
@@ -798,11 +796,13 @@
|
||||
<script src="/static/time_close_ui.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/order_entry_model.js?v=1"></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/strategy_roll.js?v=6"></script>
|
||||
<script>
|
||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||
|
||||
function syncJournalEntryReasonOtherUi(){
|
||||
@@ -1354,6 +1354,8 @@ function fillJournalFromTrade(t){
|
||||
setJournalField("entry_reason", "趋势回调");
|
||||
} else if(mt === "顺势加仓" && JOURNAL_ENTRY_REASON_OPTIONS.includes("顺势加仓")){
|
||||
setJournalField("entry_reason", "顺势加仓");
|
||||
} else if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
|
||||
setJournalField("entry_reason", t.effective_entry_reason);
|
||||
} else {
|
||||
const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
|
||||
if(erFromKey && JOURNAL_ENTRY_REASON_OPTIONS.includes(erFromKey)){
|
||||
|
||||
+9627
-9604
File diff suppressed because it is too large
Load Diff
@@ -365,10 +365,8 @@
|
||||
<option value="price">止盈止损:价格模式</option>
|
||||
<option value="pct">止盈止损:百分比模式</option>
|
||||
</select>
|
||||
<select name="trade_style" required>
|
||||
<option value="trend">趋势单</option>
|
||||
<option value="swing">波段单</option>
|
||||
</select>
|
||||
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
|
||||
{{ order_entry_type_fields() }}
|
||||
{% if position_sizing_mode != 'full_margin' %}
|
||||
<input id="order-leverage" name="leverage" type="number" min="1" step="1" placeholder="杠杆(可选)">
|
||||
{% endif %}
|
||||
@@ -431,7 +429,7 @@
|
||||
</div>
|
||||
<div class="pos-meta">
|
||||
<span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风格: {{ o.trade_style or 'trend' }}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %}</span>
|
||||
<span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
|
||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||
@@ -765,11 +763,13 @@
|
||||
<script src="/static/time_close_ui.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/order_entry_model.js?v=1"></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/strategy_roll.js?v=6"></script>
|
||||
<script>
|
||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||
|
||||
function syncJournalEntryReasonOtherUi(){
|
||||
@@ -1321,6 +1321,8 @@ function fillJournalFromTrade(t){
|
||||
setJournalField("entry_reason", "趋势回调");
|
||||
} else if(mt === "顺势加仓" && JOURNAL_ENTRY_REASON_OPTIONS.includes("顺势加仓")){
|
||||
setJournalField("entry_reason", "顺势加仓");
|
||||
} else if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
|
||||
setJournalField("entry_reason", t.effective_entry_reason);
|
||||
} else {
|
||||
const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
|
||||
if(erFromKey && JOURNAL_ENTRY_REASON_OPTIONS.includes(erFromKey)){
|
||||
|
||||
+9168
-9149
File diff suppressed because it is too large
Load Diff
@@ -394,10 +394,8 @@
|
||||
<option value="price">止盈止损:价格模式</option>
|
||||
<option value="pct">止盈止损:百分比模式</option>
|
||||
</select>
|
||||
<select name="trade_style" required>
|
||||
<option value="trend">趋势单</option>
|
||||
<option value="swing">波段单</option>
|
||||
</select>
|
||||
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
|
||||
{{ order_entry_type_fields() }}
|
||||
{% if position_sizing_mode != 'full_margin' %}
|
||||
<input id="order-leverage" name="leverage" type="number" min="1" step="1" placeholder="杠杆(可选)">
|
||||
{% endif %}
|
||||
@@ -460,7 +458,7 @@
|
||||
</div>
|
||||
<div class="pos-meta">
|
||||
<span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风格: {{ o.trade_style or 'trend' }}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %}</span>
|
||||
<span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
|
||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||
@@ -794,11 +792,13 @@
|
||||
<script src="/static/time_close_ui.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/order_entry_model.js?v=1"></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/strategy_roll.js?v=6"></script>
|
||||
<script>
|
||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||
|
||||
function syncJournalEntryReasonOtherUi(){
|
||||
@@ -1350,6 +1350,8 @@ function fillJournalFromTrade(t){
|
||||
setJournalField("entry_reason", "趋势回调");
|
||||
} else if(mt === "顺势加仓" && JOURNAL_ENTRY_REASON_OPTIONS.includes("顺势加仓")){
|
||||
setJournalField("entry_reason", "顺势加仓");
|
||||
} else if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
|
||||
setJournalField("entry_reason", t.effective_entry_reason);
|
||||
} else {
|
||||
const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
|
||||
if(erFromKey && JOURNAL_ENTRY_REASON_OPTIONS.includes(erFromKey)){
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# 策略文档
|
||||
|
||||
各交易实例的人工下单策略,供 UI / 复盘对齐。
|
||||
|
||||
| 文档 | 实例 | 状态 |
|
||||
|------|------|------|
|
||||
| [binance-alt-trend-long.md](./binance-alt-trend-long.md) | 币安山寨·多头趋势 | v0.2 |
|
||||
| [okx-trend-both.md](./okx-trend-both.md) | OKX·多空趋势 | v0.2 |
|
||||
| gate-intraday.md | Gate·BTC/ETH 日内 | **待定** |
|
||||
|
||||
## 约定
|
||||
|
||||
- **不写盈亏比**:止盈/止损随行情人工设定,不写入策略 MD。
|
||||
- **界面短标签**:`大分歧A` / `大分歧B` / `小分歧`;全称在 option `title` / 策略 MD 说明。
|
||||
- **多空共用三字**:方向由「做多/做空」表达,不复用为「大分歧A多」等。
|
||||
- **自动联动**:大分歧 A/B → 趋势单;小分歧 → 波段单;平仓写入交易记录 `entry_reason`;复盘「填入」自动带入。
|
||||
- **日内 profile 独立**:env 启用 `TRADE_SYMBOL_WHITELIST=BTC,ETH` 且限制开启时,**不显示**上述三项(Gate 当前);日内开仓类型后续单独定义。
|
||||
- **策略模块独立**:趋势回调、顺势加仓不走上述三项。
|
||||
|
||||
## 系统实现
|
||||
|
||||
- 库:`lib/trade/entry_model_lib.py`
|
||||
- 趋势户表单:`lib/instance/templates/order_entry_model_fields.html`
|
||||
- 日内判定:`is_intraday_trading_profile()`(白名单仅含 BTC/ETH)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [计仓模式](../position-sizing-mode.md)
|
||||
- [趋势回调策略](../trend-pullback-strategy.md)
|
||||
@@ -0,0 +1,88 @@
|
||||
# 币安山寨·多头趋势账户
|
||||
|
||||
> **状态**:v0.2(已实现开仓类型 UI + 复盘联动)
|
||||
|
||||
---
|
||||
|
||||
## 1. 账户定位
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| 交易所 | 币安合约 |
|
||||
| 方向 | **仅做多**(`TRADE_DIRECTION=long_only`) |
|
||||
| 计仓 | `POSITION_SIZING_MODE=risk` |
|
||||
| 关键位自动单 | `KEY_AUTO_ORDER_ENABLED=false` |
|
||||
| UI profile | **趋势户**(非 BTC/ETH 白名单日内) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 开仓类型(下单监控)
|
||||
|
||||
| 界面标签 | 存储 code | 联动 | 说明(hover / 本文) |
|
||||
|----------|-----------|------|----------------------|
|
||||
| **大分歧A** | `big_div_a` | 趋势单 | 突破前收敛小结构,不创新低企稳 |
|
||||
| **大分歧B** | `big_div_b` | 趋势单 | 结构突破确认后入场 |
|
||||
| **小分歧** | `small_div` | 波段单 | 二次探底 N 字突破,或 5m 三均线重新多头 |
|
||||
|
||||
- 下拉 **仅三选一**,不可手输。
|
||||
- 原「趋势单/波段单」手选已隐藏,由开仓类型自动设定。
|
||||
- A/B 差异在 **入场方式**,不绑定盈亏比。
|
||||
|
||||
---
|
||||
|
||||
## 3. 大分歧(趋势单)
|
||||
|
||||
**前提**:主升浪、上方有空间、大级别结构顺势。
|
||||
|
||||
### 3.1 大分歧A
|
||||
|
||||
- 4h/日线大结构向上
|
||||
- 5m/15m 收敛,**不创新低** 企稳进(不等突破)
|
||||
|
||||
### 3.2 大分歧B
|
||||
|
||||
- 同上大级别多头结构
|
||||
- **突破确认** 后入场
|
||||
|
||||
---
|
||||
|
||||
## 4. 小分歧(波段单)
|
||||
|
||||
- **前两次**可做,**第三次不做**
|
||||
- 低吸为主,不追突破
|
||||
- 入场:二次探底 → N 字突破;或 5m 三均线重新多头排列
|
||||
|
||||
---
|
||||
|
||||
## 5. 纪律
|
||||
|
||||
1. 不做空
|
||||
2. 第三次小分歧不做新单
|
||||
3. 小分歧不追突破
|
||||
4. 止盈/止损/是否手平:**随行情**,本文档不量化
|
||||
|
||||
---
|
||||
|
||||
## 6. 持仓与出场(定性)
|
||||
|
||||
- 大分歧:可长持;途中两次小分歧后远目标未到,**可手平**
|
||||
- 小分歧:短拿,常手平
|
||||
|
||||
---
|
||||
|
||||
## 7. 系统字段
|
||||
|
||||
| 操作 | 字段 |
|
||||
|------|------|
|
||||
| 下单 | `order_monitors.entry_model` + 自动 `trade_style` |
|
||||
| 平仓 | `trade_records.entry_reason` = 界面标签 |
|
||||
| 复盘 | 下拉同三项 + 策略项 +「其他」 |
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| v0.2 | 2026-07-06 | 定稿 UI 短标签;实现代码联动 |
|
||||
| v0.1 | 2026-07-06 | 讨论稿 |
|
||||
@@ -0,0 +1,41 @@
|
||||
# OKX·多空趋势账户
|
||||
|
||||
> **状态**:v0.2
|
||||
> 与 [binance-alt-trend-long.md](./binance-alt-trend-long.md) 共用开仓类型与 UI;本节仅补 **空侧镜像**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 账户差异
|
||||
|
||||
| 项 | 币安 | OKX |
|
||||
|----|------|-----|
|
||||
| 方向 | 仅多 | 多 + 空 |
|
||||
| 开仓类型 UI | 大分歧A / B / 小分歧 | **相同三字** |
|
||||
| profile | 趋势户 | 趋势户(非 BTC/ETH 日内白名单) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 空侧镜像(标签不变)
|
||||
|
||||
| 标签 | 做多 | 做空 |
|
||||
|------|------|------|
|
||||
| 大分歧A | 不创新低企稳 | **不创新高**企稳 |
|
||||
| 大分歧B | 向上突破确认 | **向下突破**确认 |
|
||||
| 小分歧 | 二次探底 / N 字 / 5m 多头 | 二次**探顶** / 倒 N / 5m **空头** |
|
||||
|
||||
方向由表单「做多/做空」决定;复盘与交易记录仍存 `大分歧A` 等短标签。
|
||||
|
||||
---
|
||||
|
||||
## 3–6. 纪律 / 出场 / 系统字段
|
||||
|
||||
同 Binance 文档 §3–§7(含不写盈亏比、第三次小分歧不做等)。
|
||||
|
||||
---
|
||||
|
||||
## 修订记录
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
|------|------|------|
|
||||
| v0.2 | 2026-07-06 | 与 Binance 同步实现 |
|
||||
| v0.1 | 2026-07-06 | 讨论稿 |
|
||||
@@ -64,6 +64,7 @@
|
||||
}
|
||||
if (tab === "trade") {
|
||||
if (typeof global.refreshOrderDefaults === "function") global.refreshOrderDefaults();
|
||||
if (typeof global.initOrderEntryModelSelect === "function") global.initOrderEntryModelSelect();
|
||||
if (global.ManualOrderRrPreview && typeof global.ManualOrderRrPreview.wire === "function") {
|
||||
global.ManualOrderRrPreview.wire();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
.order-trade-style-hint{font-size:.78rem;color:#8fc8ff;margin-left:4px;white-space:nowrap}
|
||||
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)}
|
||||
.header{display:flex;flex-direction:column;align-items:center;gap:8px;margin-bottom:12px}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
(function (global) {
|
||||
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 map = global.ORDER_ENTRY_MODEL_TRADE_STYLE || {};
|
||||
var labels = { trend: "趋势单", swing: "波段单" };
|
||||
var ts = map[sel.value] || "trend";
|
||||
hidden.value = ts;
|
||||
if (hint) hint.textContent = labels[ts] || ts;
|
||||
}
|
||||
|
||||
function initOrderEntryModelSelect() {
|
||||
var sel = document.getElementById("order-entry-model");
|
||||
if (!sel || sel.dataset.entryModelWired === "1") return;
|
||||
sel.dataset.entryModelWired = "1";
|
||||
sel.addEventListener("change", syncOrderEntryModelTradeStyle);
|
||||
syncOrderEntryModelTradeStyle();
|
||||
}
|
||||
|
||||
global.initOrderEntryModelSelect = initOrderEntryModelSelect;
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initOrderEntryModelSelect);
|
||||
} else {
|
||||
initOrderEntryModelSelect();
|
||||
}
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
const JOURNAL_ENTRY_REASON_OPTIONS = {{ entry_reason_options | tojson }};
|
||||
const ORDER_ENTRY_MODEL_TRADE_STYLE = {{ entry_model_trade_style_map | tojson }};
|
||||
const JOURNAL_ENTRY_REASON_OTHER = {{ entry_reason_other_value | tojson }};
|
||||
|
||||
function reloadInstancePage(){
|
||||
@@ -559,6 +560,8 @@ function fillJournalFromTrade(t){
|
||||
setJournalField("entry_reason", "趋势回调");
|
||||
} else if(mt === "顺势加仓" && JOURNAL_ENTRY_REASON_OPTIONS.includes("顺势加仓")){
|
||||
setJournalField("entry_reason", "顺势加仓");
|
||||
} else if(t.effective_entry_reason && JOURNAL_ENTRY_REASON_OPTIONS.includes(t.effective_entry_reason)){
|
||||
setJournalField("entry_reason", t.effective_entry_reason);
|
||||
} else {
|
||||
const erFromKey = KEY_ENTRY_REASON_BY_SIGNAL[kst] || "";
|
||||
if(erFromKey && JOURNAL_ENTRY_REASON_OPTIONS.includes(erFromKey)){
|
||||
|
||||
@@ -42,10 +42,8 @@
|
||||
<option value="price">止盈止损:价格模式</option>
|
||||
<option value="pct">止盈止损:百分比模式</option>
|
||||
</select>
|
||||
<select name="trade_style" required>
|
||||
<option value="trend">趋势单</option>
|
||||
<option value="swing">波段单</option>
|
||||
</select>
|
||||
{% from 'order_entry_model_fields.html' import order_entry_type_fields with context %}
|
||||
{{ order_entry_type_fields() }}
|
||||
{% if position_sizing_mode != 'full_margin' %}
|
||||
<input id="order-leverage" name="leverage" type="number" min="1" step="1" placeholder="杠杆(可选)">
|
||||
{% endif %}
|
||||
@@ -108,7 +106,7 @@
|
||||
</div>
|
||||
<div class="pos-meta">
|
||||
<span class="pos-meta-item">来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风格: {{ o.trade_style or 'trend' }}</span>
|
||||
<span class="pos-meta-item">{% if o.entry_model_label %}开仓: {{ o.entry_model_label }}{% else %}风格: {{ '波段单' if o.trade_style == 'swing' else '趋势单' }}{% endif %}</span>
|
||||
<span class="pos-meta-item">风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %}</span>
|
||||
<span class="pos-meta-item" id="order-latest-risk-wrap-{{ o.id }}" style="display:none">最新风险: —</span>
|
||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
<script src="/static/time_close_ui.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/order_entry_model.js?v=1"></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/strategy_roll.js?v=6"></script>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{# 趋势户:开仓类型 → 自动 trade_style;日内户:仍选手动 trend/swing #}
|
||||
{% macro order_entry_type_fields() -%}
|
||||
{% if order_entry_profile == 'trend_div' %}
|
||||
<select name="entry_model" id="order-entry-model" required title="大分歧A/B为趋势单,小分歧为波段单">
|
||||
<option value="">开仓类型</option>
|
||||
{% for opt in entry_model_options %}
|
||||
<option value="{{ opt.code }}" title="{{ opt.help }}">{{ opt.label }}</option>
|
||||
{% endfor %}
|
||||
</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>
|
||||
{% else %}
|
||||
<select name="trade_style" required>
|
||||
<option value="trend">趋势单</option>
|
||||
<option value="swing">波段单</option>
|
||||
</select>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
@@ -10,7 +10,7 @@
|
||||
<input name="real_rr" placeholder="实际RR">
|
||||
</div>
|
||||
<div class="form-grid journal-form-row2">
|
||||
<select name="entry_reason" id="journal-entry-reason" required title="固定五种或选其他手写">
|
||||
<select name="entry_reason" id="journal-entry-reason" required title="大分歧A/B/小分歧或策略项;选其他可手写">
|
||||
<option value="">开仓类型(必选)</option>
|
||||
{% for er in entry_reason_options %}
|
||||
<option value="{{ er }}">{{ er }}</option>
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
"""大分歧 / 小分歧开仓类型(趋势户);日内户沿用 trade_style,单独 profile。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Mapping, Optional, Sequence, Tuple
|
||||
|
||||
from lib.trade.trade_policy_lib import TradePolicy
|
||||
|
||||
PROFILE_TREND_DIV = "trend_div"
|
||||
PROFILE_INTRADAY = "intraday"
|
||||
|
||||
ENTRY_MODEL_BIG_DIV_A = "big_div_a"
|
||||
ENTRY_MODEL_BIG_DIV_B = "big_div_b"
|
||||
ENTRY_MODEL_SMALL_DIV = "small_div"
|
||||
|
||||
VALID_ENTRY_MODEL_CODES = frozenset(
|
||||
{
|
||||
ENTRY_MODEL_BIG_DIV_A,
|
||||
ENTRY_MODEL_BIG_DIV_B,
|
||||
ENTRY_MODEL_SMALL_DIV,
|
||||
}
|
||||
)
|
||||
|
||||
TREND_DIV_ENTRY_REASON_LABELS: Tuple[str, ...] = (
|
||||
"大分歧A",
|
||||
"大分歧B",
|
||||
"小分歧",
|
||||
)
|
||||
|
||||
INTRADAY_LEGACY_TREND_ENTRY_REASONS: Tuple[str, ...] = (
|
||||
"趋势多头:4h大结构突破前进场,确认条件:三次探顶,5m收敛不创新低",
|
||||
"趋势空头:4h大结构突破前进场,确认条件:三次探底,5m收敛不创新高",
|
||||
"趋势多头:小分歧低吸入场(左侧),确认条件:二次探底",
|
||||
"趋势空头:小分歧高吸入场(左侧),确认条件:二次探顶",
|
||||
"波段单:5m顺势突破,确认条件:2根k线+成交量放大+4h同向+日成交量前20",
|
||||
)
|
||||
|
||||
_ENTRY_SPECS: Tuple[Tuple[str, str, str, str], ...] = (
|
||||
(
|
||||
ENTRY_MODEL_BIG_DIV_A,
|
||||
"大分歧A",
|
||||
"trend",
|
||||
"突破前收敛小结构,不创新低企稳(空:不创新高)",
|
||||
),
|
||||
(
|
||||
ENTRY_MODEL_BIG_DIV_B,
|
||||
"大分歧B",
|
||||
"trend",
|
||||
"结构突破确认后入场",
|
||||
),
|
||||
(
|
||||
ENTRY_MODEL_SMALL_DIV,
|
||||
"小分歧",
|
||||
"swing",
|
||||
"二次探底 N 字突破,或 5m 三均线重新多头(空:二次探顶 / 空头均线)",
|
||||
),
|
||||
)
|
||||
|
||||
_CODE_TO_LABEL = {code: label for code, label, _, _ 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_HELP = {code: help for code, _, _, help in _ENTRY_SPECS}
|
||||
|
||||
_INTRADAY_WHITELIST = frozenset({"BTC", "ETH"})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EntryModelOption:
|
||||
code: str
|
||||
label: str
|
||||
trade_style: str
|
||||
help: str
|
||||
|
||||
|
||||
def is_intraday_trading_profile(policy: TradePolicy) -> bool:
|
||||
"""日内户:启用 BTC/ETH 白名单(env 中 TRADE_SYMBOL_WHITELIST)。"""
|
||||
if not policy.symbol_restrict_enabled:
|
||||
return False
|
||||
if not policy.symbol_whitelist:
|
||||
return False
|
||||
return all(s in _INTRADAY_WHITELIST for s in policy.symbol_whitelist)
|
||||
|
||||
|
||||
def order_entry_profile(policy: TradePolicy) -> str:
|
||||
return PROFILE_INTRADAY if is_intraday_trading_profile(policy) else PROFILE_TREND_DIV
|
||||
|
||||
|
||||
def entry_model_options() -> Tuple[EntryModelOption, ...]:
|
||||
return tuple(
|
||||
EntryModelOption(code=code, label=label, trade_style=style, help=help)
|
||||
for code, label, style, help in _ENTRY_SPECS
|
||||
)
|
||||
|
||||
|
||||
def normalize_entry_model_code(raw: Optional[str]) -> str:
|
||||
v = (raw or "").strip().lower()
|
||||
if v in VALID_ENTRY_MODEL_CODES:
|
||||
return v
|
||||
label = (raw or "").strip()
|
||||
if label in _LABEL_TO_CODE:
|
||||
return _LABEL_TO_CODE[label]
|
||||
return ""
|
||||
|
||||
|
||||
def entry_model_label(code: Optional[str]) -> str:
|
||||
c = normalize_entry_model_code(code)
|
||||
return _CODE_TO_LABEL.get(c, "")
|
||||
|
||||
|
||||
def trade_style_for_entry_model(code: Optional[str]) -> str:
|
||||
c = normalize_entry_model_code(code)
|
||||
return _CODE_TO_STYLE.get(c, "trend")
|
||||
|
||||
|
||||
def trade_style_label_zh(trade_style: str) -> str:
|
||||
return "波段单" if (trade_style or "").strip().lower() == "swing" else "趋势单"
|
||||
|
||||
|
||||
def trend_manual_entry_reason_count(policy: TradePolicy) -> int:
|
||||
if is_intraday_trading_profile(policy):
|
||||
return len(INTRADAY_LEGACY_TREND_ENTRY_REASONS)
|
||||
return len(TREND_DIV_ENTRY_REASON_LABELS)
|
||||
|
||||
|
||||
def build_trend_div_entry_reason_options(
|
||||
strategy_options: Sequence[str],
|
||||
) -> Tuple[str, ...]:
|
||||
return TREND_DIV_ENTRY_REASON_LABELS + tuple(strategy_options)
|
||||
|
||||
|
||||
def build_intraday_entry_reason_options(
|
||||
key_options: Sequence[str],
|
||||
strategy_options: Sequence[str],
|
||||
) -> Tuple[str, ...]:
|
||||
return INTRADAY_LEGACY_TREND_ENTRY_REASONS + tuple(key_options) + tuple(strategy_options)
|
||||
|
||||
|
||||
def entry_reason_options_for_policy(
|
||||
policy: TradePolicy,
|
||||
key_options: Sequence[str],
|
||||
strategy_options: Sequence[str],
|
||||
) -> Tuple[str, ...]:
|
||||
if is_intraday_trading_profile(policy):
|
||||
return build_intraday_entry_reason_options(key_options, strategy_options)
|
||||
return build_trend_div_entry_reason_options(strategy_options)
|
||||
|
||||
|
||||
def parse_manual_order_style_fields(
|
||||
policy: TradePolicy,
|
||||
form: Mapping[str, Any],
|
||||
*,
|
||||
default_trade_style: str = "trend",
|
||||
) -> Tuple[str, Optional[str], Optional[str]]:
|
||||
"""返回 (trade_style, entry_model_code|None, error_message|None)。"""
|
||||
if is_intraday_trading_profile(policy):
|
||||
trade_style = (form.get("trade_style") or default_trade_style or "trend").strip().lower()
|
||||
if trade_style not in ("trend", "swing"):
|
||||
trade_style = "trend"
|
||||
return trade_style, None, None
|
||||
|
||||
entry_model = normalize_entry_model_code(form.get("entry_model"))
|
||||
if not entry_model:
|
||||
return "", None, "请选择开仓类型(大分歧A / 大分歧B / 小分歧)"
|
||||
trade_style = trade_style_for_entry_model(entry_model)
|
||||
return trade_style, entry_model, None
|
||||
|
||||
|
||||
def resolve_trade_record_entry_reason(
|
||||
*,
|
||||
entry_reason: Optional[str] = None,
|
||||
entry_model: Optional[str] = None,
|
||||
key_signal_type: Optional[str] = None,
|
||||
monitor_type: Optional[str] = None,
|
||||
entry_reason_from_key_signal=None,
|
||||
entry_reason_for_monitor_type=None,
|
||||
) -> str:
|
||||
er = (entry_reason or "").strip()
|
||||
if er:
|
||||
return er
|
||||
label = entry_model_label(entry_model)
|
||||
if label:
|
||||
return label
|
||||
kst = (key_signal_type or "").strip()
|
||||
if kst and entry_reason_from_key_signal is not None:
|
||||
from_key = (entry_reason_from_key_signal(kst) or "").strip()
|
||||
if from_key:
|
||||
return from_key
|
||||
if entry_reason_for_monitor_type is not None:
|
||||
from_mt = (entry_reason_for_monitor_type(monitor_type) or "").strip()
|
||||
if from_mt:
|
||||
return from_mt
|
||||
return ""
|
||||
|
||||
|
||||
def enrich_entry_model_display(item: dict) -> dict:
|
||||
code = normalize_entry_model_code(item.get("entry_model"))
|
||||
if code:
|
||||
item["entry_model"] = code
|
||||
item["entry_model_label"] = entry_model_label(code)
|
||||
else:
|
||||
item.setdefault("entry_model_label", "")
|
||||
return item
|
||||
|
||||
|
||||
def order_entry_template_context(policy: TradePolicy) -> dict:
|
||||
profile = order_entry_profile(policy)
|
||||
opts = entry_model_options()
|
||||
return {
|
||||
"order_entry_profile": profile,
|
||||
"entry_model_options": [
|
||||
{"code": o.code, "label": o.label, "trade_style": o.trade_style, "help": o.help}
|
||||
for o in opts
|
||||
],
|
||||
"entry_model_trade_style_map": {o.code: o.trade_style for o in opts},
|
||||
}
|
||||
|
||||
|
||||
def migrate_entry_model_columns(conn) -> None:
|
||||
for table in ("order_monitors", "trade_records"):
|
||||
try:
|
||||
conn.execute(f"ALTER TABLE {table} ADD COLUMN entry_model TEXT")
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Patch binance/okx/gate app.py for entry_model support."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
IMPORT_BLOCK = """from lib.trade.entry_model_lib import (
|
||||
build_intraday_entry_reason_options,
|
||||
build_trend_div_entry_reason_options,
|
||||
enrich_entry_model_display,
|
||||
migrate_entry_model_columns,
|
||||
order_entry_template_context,
|
||||
parse_manual_order_style_fields,
|
||||
resolve_trade_record_entry_reason,
|
||||
trend_manual_entry_reason_count,
|
||||
)
|
||||
"""
|
||||
|
||||
KEY_IMPORT = "from lib.key_monitor.key_auto_order_lib import (\n check_monitor_type_add_allowed,\n effective_entry_reason_options,\n effective_stats_segment_defs,\n load_key_auto_order_enabled,"
|
||||
|
||||
KEY_IMPORT_WITH_KEY_OPTS = "from lib.key_monitor.key_auto_order_lib import (\n KEY_ENTRY_REASON_OPTIONS,\n check_monitor_type_add_allowed,\n effective_entry_reason_options,\n effective_stats_segment_defs,\n load_key_auto_order_enabled,"
|
||||
|
||||
|
||||
def patch_file(path: str, exchange: str) -> bool:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
orig = text
|
||||
|
||||
if "from lib.trade.entry_model_lib import" not in text:
|
||||
text = text.replace(
|
||||
"from lib.trade.trade_policy_app_lib import (",
|
||||
IMPORT_BLOCK + "from lib.trade.trade_policy_app_lib import (",
|
||||
1,
|
||||
)
|
||||
|
||||
if exchange == "gate":
|
||||
old_er = '''# 与用户约定的固定开仓类型
|
||||
ENTRY_REASON_OPTIONS = (
|
||||
"趋势多头:4h大结构突破前进场,确认条件:三次探顶,5m收敛不创新低",
|
||||
"趋势空头:4h大结构突破前进场,确认条件:三次探底,5m收敛不创新高",
|
||||
"趋势多头:小分歧低吸入场(左侧),确认条件:二次探底",
|
||||
"趋势空头:小分歧高吸入场(左侧),确认条件:二次探顶",
|
||||
"波段单:5m顺势突破,确认条件:2根k线+成交量放大+4h同向+日成交量前20",
|
||||
"关键位箱体突破",
|
||||
"关键位收敛突破",
|
||||
"关键位斐波0.618",
|
||||
"关键位斐波0.786",
|
||||
"关键位假突破",
|
||||
"关键位回调触价开仓",
|
||||
"关键位突破触价开仓",
|
||||
) + STRATEGY_ENTRY_REASON_OPTIONS'''
|
||||
new_er = """# 日内户:长句开仓类型 + 关键位 + 策略(大分歧 A/B/小分歧 仅趋势户)
|
||||
ENTRY_REASON_OPTIONS = build_intraday_entry_reason_options(
|
||||
KEY_ENTRY_REASON_OPTIONS,
|
||||
STRATEGY_ENTRY_REASON_OPTIONS,
|
||||
)"""
|
||||
text = text.replace(old_er, new_er)
|
||||
if "KEY_ENTRY_REASON_OPTIONS," not in text.split("load_key_auto_order_enabled")[0]:
|
||||
text = text.replace(KEY_IMPORT, KEY_IMPORT_WITH_KEY_OPTS, 1)
|
||||
else:
|
||||
old_er = '''# 与用户约定的固定开仓类型(仅做这几类单子)
|
||||
ENTRY_REASON_OPTIONS = (
|
||||
"趋势多头:4h大结构突破前进场,确认条件:三次探顶,5m收敛不创新低",
|
||||
"趋势空头:4h大结构突破前进场,确认条件:三次探底,5m收敛不创新高",
|
||||
"趋势多头:小分歧低吸入场(左侧),确认条件:二次探底",
|
||||
"趋势空头:小分歧高吸入场(左侧),确认条件:二次探顶",
|
||||
"波段单:5m顺势突破,确认条件:2根k线+成交量放大+4h同向+日成交量前20",
|
||||
"关键位箱体突破",
|
||||
"关键位收敛突破",
|
||||
"关键位斐波0.618",
|
||||
"关键位斐波0.786",
|
||||
"关键位假突破",
|
||||
"关键位回调触价开仓",
|
||||
"关键位突破触价开仓",
|
||||
) + STRATEGY_ENTRY_REASON_OPTIONS'''
|
||||
new_er = """# 趋势户:大分歧A/B/小分歧 + 策略(关键位本实例关闭)
|
||||
ENTRY_REASON_OPTIONS = build_trend_div_entry_reason_options(STRATEGY_ENTRY_REASON_OPTIONS)"""
|
||||
text = text.replace(old_er, new_er)
|
||||
|
||||
if "migrate_entry_model_columns(conn)" not in text:
|
||||
text = text.replace(
|
||||
" conn.commit()\n conn.close()\n\n\ndef get_db",
|
||||
" migrate_entry_model_columns(conn)\n conn.commit()\n conn.close()\n\n\ndef get_db",
|
||||
1,
|
||||
)
|
||||
|
||||
text = re.sub(
|
||||
r" er = \(\n \(entry_reason or \"\"\)\.strip\(\)\n or entry_reason_from_key_signal\(kst\)\n or entry_reason_for_monitor_type\(monitor_type\)\n or \"\"\n \)",
|
||||
""" er = resolve_trade_record_entry_reason(
|
||||
entry_reason=entry_reason,
|
||||
entry_model=entry_model,
|
||||
key_signal_type=kst,
|
||||
monitor_type=monitor_type,
|
||||
entry_reason_from_key_signal=entry_reason_from_key_signal,
|
||||
entry_reason_for_monitor_type=entry_reason_for_monitor_type,
|
||||
)""",
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
|
||||
if "entry_model=None," not in text:
|
||||
text = text.replace(
|
||||
" entry_reason=None,\n trend_plan_id=None,",
|
||||
" entry_reason=None,\n entry_model=None,\n trend_plan_id=None,",
|
||||
1,
|
||||
)
|
||||
|
||||
if "enrich_entry_model_display(item)" not in text:
|
||||
text = text.replace(
|
||||
" enrich_order_display_fields(item, calc_rr_ratio)\n try:",
|
||||
" enrich_order_display_fields(item, calc_rr_ratio)\n enrich_entry_model_display(item)\n try:",
|
||||
1,
|
||||
)
|
||||
|
||||
text = text.replace(
|
||||
""" trade_style = (d.get("trade_style") or DEFAULT_TRADE_STYLE or "trend").strip().lower()
|
||||
if trade_style not in ("trend", "swing"):
|
||||
trade_style = "trend"
|
||||
available_usdt = get_available_trading_usdt()""",
|
||||
""" trade_style, entry_model, style_err = parse_manual_order_style_fields(
|
||||
TRADE_POLICY, d, default_trade_style=DEFAULT_TRADE_STYLE or "trend"
|
||||
)
|
||||
if style_err:
|
||||
conn.close()
|
||||
flash(style_err)
|
||||
return redirect("/trade")
|
||||
available_usdt = get_available_trading_usdt()""",
|
||||
1,
|
||||
)
|
||||
|
||||
old_insert = (
|
||||
'"INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, time_close_enabled, time_close_hours, time_close_at_ms) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",\n'
|
||||
" (\n"
|
||||
" symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit,\n"
|
||||
" margin_capital, leverage, trade_style, risk_percent_db, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price,\n"
|
||||
" breakeven_enabled,\n"
|
||||
" notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,\n"
|
||||
" ORDER_MONITOR_TYPE_MANUAL,\n"
|
||||
" tc_en, tc_h, tc_at,\n"
|
||||
" )"
|
||||
)
|
||||
new_insert = (
|
||||
'"INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, entry_model, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, time_close_enabled, time_close_hours, time_close_at_ms) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",\n'
|
||||
" (\n"
|
||||
" symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit,\n"
|
||||
" margin_capital, leverage, trade_style, entry_model, risk_percent_db, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price,\n"
|
||||
" breakeven_enabled,\n"
|
||||
" notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,\n"
|
||||
" ORDER_MONITOR_TYPE_MANUAL,\n"
|
||||
" tc_en, tc_h, tc_at,\n"
|
||||
" )"
|
||||
)
|
||||
text = text.replace(old_insert, new_insert)
|
||||
|
||||
text = text.replace(
|
||||
""" effective_entry_reason_options(
|
||||
ENTRY_REASON_OPTIONS,
|
||||
POSITION_SIZING_MODE,
|
||||
KEY_AUTO_ORDER_ENABLED,
|
||||
)""",
|
||||
""" effective_entry_reason_options(
|
||||
ENTRY_REASON_OPTIONS,
|
||||
POSITION_SIZING_MODE,
|
||||
KEY_AUTO_ORDER_ENABLED,
|
||||
trend_manual_count=trend_manual_entry_reason_count(TRADE_POLICY),
|
||||
)""",
|
||||
1,
|
||||
)
|
||||
|
||||
if "**order_entry_template_context(TRADE_POLICY)," not in text:
|
||||
text = text.replace(
|
||||
" trade_policy=trade_policy_template_context(TRADE_POLICY),",
|
||||
" trade_policy=trade_policy_template_context(TRADE_POLICY),\n **order_entry_template_context(TRADE_POLICY),",
|
||||
1,
|
||||
)
|
||||
|
||||
# insert_trade_record from order row: add entry_model
|
||||
text = re.sub(
|
||||
r"(insert_trade_record\(\n\s+conn,\n(?:[^\n]+\n)+?\s+trade_style=r\[\"trade_style\"\],\n)",
|
||||
r"\1 entry_model=(r[\"entry_model\"] if \"entry_model\" in r.keys() else None),\n",
|
||||
text,
|
||||
)
|
||||
|
||||
if text != orig:
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(text)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
for ex in ("binance", "okx", "gate"):
|
||||
path = os.path.join(REPO, f"crypto_monitor_{ex}", "app.py")
|
||||
changed = patch_file(path, ex)
|
||||
print(f"{ex}: {'patched' if changed else 'no change'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,75 @@
|
||||
import unittest
|
||||
|
||||
from lib.trade.entry_model_lib import (
|
||||
ENTRY_MODEL_BIG_DIV_A,
|
||||
ENTRY_MODEL_BIG_DIV_B,
|
||||
ENTRY_MODEL_SMALL_DIV,
|
||||
build_trend_div_entry_reason_options,
|
||||
entry_model_label,
|
||||
is_intraday_trading_profile,
|
||||
parse_manual_order_style_fields,
|
||||
resolve_trade_record_entry_reason,
|
||||
trade_style_for_entry_model,
|
||||
trend_manual_entry_reason_count,
|
||||
)
|
||||
from lib.trade.trade_policy_lib import TradePolicy, load_trade_policy
|
||||
|
||||
|
||||
class TestEntryModelLib(unittest.TestCase):
|
||||
def test_intraday_profile_btc_eth_whitelist(self):
|
||||
policy = load_trade_policy(
|
||||
{
|
||||
"TRADE_SYMBOL_RESTRICT_ENABLED": "true",
|
||||
"TRADE_SYMBOL_WHITELIST": "BTC,ETH",
|
||||
}
|
||||
)
|
||||
self.assertTrue(is_intraday_trading_profile(policy))
|
||||
self.assertEqual(trend_manual_entry_reason_count(policy), 5)
|
||||
|
||||
def test_trend_div_profile_alt(self):
|
||||
policy = load_trade_policy(
|
||||
{
|
||||
"TRADE_SYMBOL_RESTRICT_ENABLED": "false",
|
||||
"TRADE_SYMBOL_WHITELIST": "BTC,ETH",
|
||||
}
|
||||
)
|
||||
self.assertFalse(is_intraday_trading_profile(policy))
|
||||
self.assertEqual(trend_manual_entry_reason_count(policy), 3)
|
||||
|
||||
def test_entry_model_maps_trade_style(self):
|
||||
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(entry_model_label(ENTRY_MODEL_BIG_DIV_A), "大分歧A")
|
||||
|
||||
def test_parse_trend_div_requires_entry_model(self):
|
||||
policy = TradePolicy(False, "both", False, ())
|
||||
style, code, err = parse_manual_order_style_fields(policy, {})
|
||||
self.assertTrue(err)
|
||||
self.assertEqual(code, None)
|
||||
|
||||
style, code, err = parse_manual_order_style_fields(
|
||||
policy, {"entry_model": ENTRY_MODEL_SMALL_DIV}
|
||||
)
|
||||
self.assertIsNone(err)
|
||||
self.assertEqual(code, ENTRY_MODEL_SMALL_DIV)
|
||||
self.assertEqual(style, "swing")
|
||||
|
||||
def test_parse_intraday_uses_trade_style(self):
|
||||
policy = TradePolicy(False, "both", True, ("BTC", "ETH"))
|
||||
style, code, err = parse_manual_order_style_fields(policy, {"trade_style": "swing"})
|
||||
self.assertIsNone(err)
|
||||
self.assertIsNone(code)
|
||||
self.assertEqual(style, "swing")
|
||||
|
||||
def test_resolve_entry_reason_from_model(self):
|
||||
er = resolve_trade_record_entry_reason(entry_model=ENTRY_MODEL_BIG_DIV_B)
|
||||
self.assertEqual(er, "大分歧B")
|
||||
|
||||
def test_build_trend_div_journal_options(self):
|
||||
opts = build_trend_div_entry_reason_options(("趋势回调",))
|
||||
self.assertEqual(opts[:3], ("大分歧A", "大分歧B", "小分歧"))
|
||||
self.assertIn("趋势回调", opts)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user