Fix options review empty lists when search box has symbol text.
Treat the filter as fuzzy q over underlying/inst/strategy (BTCUSDT->BTC) instead of exact strategy_tag, which always wiped pending rows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -98,12 +98,12 @@
|
||||
p.set("source_type", activeSource);
|
||||
var uly = ($("or-filter-uly") || {}).value || "";
|
||||
var opt = ($("or-filter-opt") || {}).value || "";
|
||||
var strategy = (($("or-filter-strategy") || {}).value || "").trim();
|
||||
var q = (($("or-filter-q") || $("or-filter-strategy") || {}).value || "").trim();
|
||||
var from = ($("or-filter-from") || {}).value || "";
|
||||
var to = ($("or-filter-to") || {}).value || "";
|
||||
if (uly) p.set("underlying", uly);
|
||||
if (opt) p.set("opt_type", opt);
|
||||
if (strategy) p.set("strategy_tag", strategy);
|
||||
if (q) p.set("q", q);
|
||||
if (from) p.set("closed_from", from.replace("T", " ") + ":00");
|
||||
if (to) p.set("closed_to", to.replace("T", " ") + ":00");
|
||||
if (($("or-include-hedge-legs") || {}).checked) p.set("include_hedge_legs", "1");
|
||||
@@ -1152,7 +1152,7 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
["or-filter-strategy", "or-filter-from", "or-filter-to"].forEach(function (id) {
|
||||
["or-filter-q", "or-filter-strategy", "or-filter-from", "or-filter-to"].forEach(function (id) {
|
||||
var el = $(id);
|
||||
if (el) {
|
||||
el.addEventListener("change", function () {
|
||||
|
||||
@@ -591,12 +591,29 @@ def enrich_trade_row(row: dict[str, Any], entry: dict[str, Any] | None = None) -
|
||||
return out
|
||||
|
||||
|
||||
def _review_search_tokens(q: str) -> list[str]:
|
||||
"""自由搜索词:BTCUSDT 同时匹配 BTC / BTCUSDT."""
|
||||
raw = str(q or "").strip()
|
||||
if not raw:
|
||||
return []
|
||||
tokens = [raw]
|
||||
u = raw.upper()
|
||||
for suf in ("-USDT", "-USD", "-USDC", "USDT", "USD", "USDC"):
|
||||
if u.endswith(suf) and len(u) > len(suf):
|
||||
base = u[: -len(suf)].rstrip("-_")
|
||||
if base and base not in {t.upper() for t in tokens}:
|
||||
tokens.append(base)
|
||||
break
|
||||
return tokens
|
||||
|
||||
|
||||
def _review_trades_filters(
|
||||
*,
|
||||
source_type: str | None = None,
|
||||
underlying: str | None = None,
|
||||
opt_type: str | None = None,
|
||||
strategy_tag: str | None = None,
|
||||
q: str | None = None,
|
||||
reviewed: str | None = None,
|
||||
include_hedge_legs: bool = False,
|
||||
closed_from: str | None = None,
|
||||
@@ -635,9 +652,26 @@ def _review_trades_filters(
|
||||
if closed_to:
|
||||
wheres.append("COALESCE(t.closed_at,'')<=?")
|
||||
args.append(closed_to)
|
||||
if strategy_tag:
|
||||
wheres.append("e.strategy_tag=?")
|
||||
# 兼容旧参数:精确策略标签;前端已改用 q 模糊搜索
|
||||
if strategy_tag and not q:
|
||||
wheres.append("UPPER(COALESCE(e.strategy_tag,''))=UPPER(?)")
|
||||
args.append(strategy_tag)
|
||||
search_tokens = _review_search_tokens(q or "")
|
||||
if search_tokens:
|
||||
token_ors: list[str] = []
|
||||
for tok in search_tokens:
|
||||
like = f"%{tok}%"
|
||||
token_ors.append(
|
||||
"""(
|
||||
UPPER(COALESCE(t.underlying,'')) LIKE UPPER(?)
|
||||
OR UPPER(COALESCE(t.inst_id,'')) LIKE UPPER(?)
|
||||
OR UPPER(COALESCE(t.legs_json,'')) LIKE UPPER(?)
|
||||
OR UPPER(COALESCE(e.strategy_tag,'')) LIKE UPPER(?)
|
||||
OR UPPER(COALESCE(e.result_tag,'')) LIKE UPPER(?)
|
||||
)"""
|
||||
)
|
||||
args.extend([like, like, like, like, like])
|
||||
wheres.append("(" + " OR ".join(token_ors) + ")")
|
||||
if reviewed == "1" or reviewed == "yes":
|
||||
wheres.append("e.id IS NOT NULL")
|
||||
elif reviewed == "0" or reviewed == "no":
|
||||
@@ -653,6 +687,7 @@ def count_review_trades(
|
||||
underlying: str | None = None,
|
||||
opt_type: str | None = None,
|
||||
strategy_tag: str | None = None,
|
||||
q: str | None = None,
|
||||
reviewed: str | None = None,
|
||||
include_hedge_legs: bool = False,
|
||||
closed_from: str | None = None,
|
||||
@@ -664,6 +699,7 @@ def count_review_trades(
|
||||
underlying=underlying,
|
||||
opt_type=opt_type,
|
||||
strategy_tag=strategy_tag,
|
||||
q=q,
|
||||
reviewed=reviewed,
|
||||
include_hedge_legs=include_hedge_legs,
|
||||
closed_from=closed_from,
|
||||
@@ -688,6 +724,7 @@ def list_review_trades(
|
||||
underlying: str | None = None,
|
||||
opt_type: str | None = None,
|
||||
strategy_tag: str | None = None,
|
||||
q: str | None = None,
|
||||
reviewed: str | None = None,
|
||||
include_hedge_legs: bool = False,
|
||||
closed_from: str | None = None,
|
||||
@@ -701,6 +738,7 @@ def list_review_trades(
|
||||
underlying=underlying,
|
||||
opt_type=opt_type,
|
||||
strategy_tag=strategy_tag,
|
||||
q=q,
|
||||
reviewed=reviewed,
|
||||
include_hedge_legs=include_hedge_legs,
|
||||
closed_from=closed_from,
|
||||
|
||||
@@ -135,6 +135,7 @@ def register_options_review_routes(app: Flask, cfg: dict[str, Any], repo_root: s
|
||||
underlying=(request.args.get("underlying") or "").strip() or None,
|
||||
opt_type=(request.args.get("opt_type") or "").strip() or None,
|
||||
strategy_tag=(request.args.get("strategy_tag") or "").strip() or None,
|
||||
q=(request.args.get("q") or "").strip() or None,
|
||||
reviewed=(request.args.get("reviewed") or "").strip() or None,
|
||||
include_hedge_legs=(request.args.get("include_hedge_legs") or "")
|
||||
.strip()
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<option value="C">Call</option>
|
||||
<option value="P">Put</option>
|
||||
</select>
|
||||
<input type="text" id="or-filter-strategy" placeholder="策略标签" style="max-width:110px;font-size:.76rem">
|
||||
<input type="text" id="or-filter-q" placeholder="搜索标的/合约/策略" autocomplete="off" style="max-width:150px;font-size:.76rem">
|
||||
<input type="datetime-local" id="or-filter-from" title="平仓起" style="font-size:.76rem">
|
||||
<input type="datetime-local" id="or-filter-to" title="平仓止" style="font-size:.76rem">
|
||||
<label class="muted" style="display:flex;align-items:center;gap:4px;font-size:.72rem">
|
||||
@@ -261,4 +261,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/options_review.js?v=12"></script>
|
||||
<script src="/static/options_review.js?v=13"></script>
|
||||
|
||||
@@ -337,6 +337,47 @@ class OptionsReviewTests(unittest.TestCase):
|
||||
self.assertEqual(stats["by_strategy"][0]["key"], "假破")
|
||||
self.assertEqual(stats["kpi"]["total"], 2)
|
||||
|
||||
def test_q_search_btcusdt_matches_btc_pending(self):
|
||||
from lib.options.options_review_lib import count_review_trades
|
||||
|
||||
conn = _conn()
|
||||
upsert_option_history_row(
|
||||
conn,
|
||||
{
|
||||
"history_key": "ex:btc1",
|
||||
"inst_id": "BTC-USD-260328-90000-C",
|
||||
"underlying": "BTC",
|
||||
"opt_type": "C",
|
||||
"realized_pnl": 1.2,
|
||||
"created_at": "2026-01-01 00:00:00",
|
||||
"closed_at": "2026-01-01 02:00:00",
|
||||
},
|
||||
)
|
||||
upsert_option_history_row(
|
||||
conn,
|
||||
{
|
||||
"history_key": "ex:eth1",
|
||||
"inst_id": "ETH-USD-260328-2000-C",
|
||||
"underlying": "ETH",
|
||||
"opt_type": "C",
|
||||
"realized_pnl": 2.0,
|
||||
"created_at": "2026-01-01 00:00:00",
|
||||
"closed_at": "2026-01-01 03:00:00",
|
||||
},
|
||||
)
|
||||
# 旧精确 strategy_tag 会把待复盘滤成空
|
||||
self.assertEqual(
|
||||
count_review_trades(conn, strategy_tag="BTCUSDT", reviewed="0"),
|
||||
0,
|
||||
)
|
||||
self.assertEqual(
|
||||
count_review_trades(conn, q="BTCUSDT", reviewed="0"),
|
||||
1,
|
||||
)
|
||||
rows = list_review_trades(conn, q="BTCUSDT", reviewed="0")
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(rows[0]["underlying"], "BTC")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user