143 lines
5.0 KiB
Python
143 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
from backend.database import MIGRATIONS, Database, MigrationRunner
|
|
from backend.features.review.prompt import messages
|
|
from backend.features.review.repository import ReviewRepository
|
|
from backend.features.review.views import trade_summary
|
|
|
|
|
|
def _database(tmp_path) -> Database:
|
|
database = Database(tmp_path / "review.db")
|
|
MigrationRunner(database).upgrade(MIGRATIONS)
|
|
with database.transaction() as connection:
|
|
connection.executemany(
|
|
"""INSERT INTO users (
|
|
id, username, username_key, password_hash, is_admin,
|
|
status, created_at, updated_at
|
|
) VALUES (?, ?, ?, 'hash', 0, 'active', 'now', 'now')""",
|
|
((1, "first", "first"), (2, "second", "second")),
|
|
)
|
|
return database
|
|
|
|
|
|
def test_private_review_records_are_strictly_scoped_and_daily_notes_upsert(tmp_path) -> None:
|
|
database = _database(tmp_path)
|
|
repository = ReviewRepository()
|
|
note = {
|
|
"code": "",
|
|
"stock_name": "",
|
|
"trade_date": "2026-07-30",
|
|
"summary": "缩量分化",
|
|
"content": "追高一次",
|
|
"plan": "等待承接",
|
|
}
|
|
trade = {
|
|
"trade_date": "2026-07-30",
|
|
"code": "000001",
|
|
"name": "平安银行",
|
|
"action": "buy",
|
|
"price": 10.0,
|
|
"quantity": 100,
|
|
"position_pct": 20.0,
|
|
"pnl_amount": None,
|
|
"pnl_pct": None,
|
|
"emotion": "calm",
|
|
"tags": ["计划内"],
|
|
"thesis": "承接",
|
|
"execution": "符合",
|
|
"id": None,
|
|
}
|
|
with database.transaction() as connection:
|
|
repository.save_watch(
|
|
connection,
|
|
1,
|
|
{
|
|
"identifier": "000001.SZ",
|
|
"name": "平安银行",
|
|
"sector": "银行",
|
|
},
|
|
"now",
|
|
)
|
|
repository.save_watch(
|
|
connection,
|
|
2,
|
|
{
|
|
"identifier": "600000.SH",
|
|
"name": "浦发银行",
|
|
"sector": "银行",
|
|
},
|
|
"now",
|
|
)
|
|
assert repository.save_watch_remark(connection, 1, "000001.SZ", "观察承接")
|
|
repository.save_watch(
|
|
connection,
|
|
1,
|
|
{"identifier": "000001.SZ", "name": "平安银行", "sector": "银行"},
|
|
"later",
|
|
)
|
|
note_id = repository.save_note(connection, 1, note, "now")
|
|
note["summary"] = "更新后的盘面"
|
|
assert repository.save_note(connection, 1, note, "later") == note_id
|
|
trade_id = repository.save_trade(connection, 1, trade, "now")
|
|
repository.save_alert(
|
|
connection,
|
|
1,
|
|
{
|
|
"kind": "manual",
|
|
"title": "复核",
|
|
"content": "看承接",
|
|
"available_date": "2026-07-30",
|
|
"code": "000001",
|
|
"dedupe_key": "one",
|
|
},
|
|
"now",
|
|
)
|
|
repository.add_message(
|
|
connection,
|
|
1,
|
|
{
|
|
"role": "user",
|
|
"content": "今天如何",
|
|
"context_date": "2026-07-30",
|
|
"request_id": None,
|
|
"status": "complete",
|
|
},
|
|
"now",
|
|
)
|
|
with database.read() as connection:
|
|
assert [row["identifier"] for row in repository.watchlist(connection, 1)] == ["000001.SZ"]
|
|
assert repository.watchlist(connection, 1)[0]["remark"] == "观察承接"
|
|
assert [row["identifier"] for row in repository.watchlist(connection, 2)] == ["600000.SH"]
|
|
assert repository.note(connection, 1, "", "2026-07-30")["summary"] == "更新后的盘面"
|
|
assert repository.note(connection, 2, "", "2026-07-30") is None
|
|
assert [row["id"] for row in repository.trades(connection, 1)] == [trade_id]
|
|
assert repository.trades(connection, 2) == ()
|
|
assert len(repository.alerts(connection, 1, False)) == 1
|
|
assert repository.alerts(connection, 2, False) == ()
|
|
assert len(repository.messages(connection, 1)) == 1
|
|
assert repository.messages(connection, 2) == ()
|
|
|
|
|
|
def test_trade_summary_uses_only_realized_fields_and_keeps_zero_in_denominator() -> None:
|
|
rows = [
|
|
{"pnl_amount": None, "pnl_pct": None, "position_pct": None},
|
|
{"pnl_amount": 100.0, "pnl_pct": None, "position_pct": 20.0},
|
|
{"pnl_amount": None, "pnl_pct": 0.0, "position_pct": 40.0},
|
|
{"pnl_amount": -20.0, "pnl_pct": -2.0, "position_pct": 60.0},
|
|
]
|
|
summary = trade_summary(rows)
|
|
assert summary == {
|
|
"total": 4,
|
|
"realized": 3,
|
|
"win_rate": 33.3,
|
|
"pnl_amount": 80.0,
|
|
"average_position": 40.0,
|
|
}
|
|
|
|
|
|
def test_review_prompt_separates_facts_records_inference_and_plan() -> None:
|
|
prompt = messages({"market_facts": {"temperature": 42}}, [], "明天怎么看")
|
|
system = prompt[0]["content"]
|
|
assert all(label in system for label in ("市场事实", "用户记录", "推断", "条件化计划"))
|
|
assert "不执行交易" in system
|