91 lines
3.4 KiB
Python
91 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from database import ReviewDatabase
|
|
from backend.features.review.trade_journal import TradeJournalService
|
|
|
|
|
|
class TradeJournalTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.database = ReviewDatabase(Path(self.temp.name) / "review.db")
|
|
self.owner = self.database.create_user("trade_owner", "salt", "hash")
|
|
self.other = self.database.create_user("trade_other", "salt", "hash")
|
|
self.service = TradeJournalService(self.database)
|
|
|
|
def tearDown(self) -> None:
|
|
self.temp.cleanup()
|
|
|
|
@staticmethod
|
|
def payload(**overrides):
|
|
data = {
|
|
"trade_date": "2026-07-22",
|
|
"code": "002141",
|
|
"name": "贤丰控股",
|
|
"action": "buy",
|
|
"price": 10.25,
|
|
"quantity": 1000,
|
|
"position_pct": 20,
|
|
"pnl_amount": "",
|
|
"pnl_pct": "",
|
|
"thesis": "修复期低吸",
|
|
"execution": "按计划成交",
|
|
"emotion": "calm",
|
|
"tags": "计划内,低吸",
|
|
}
|
|
data.update(overrides)
|
|
return data
|
|
|
|
def test_unrealized_entry_does_not_enter_win_rate(self):
|
|
self.service.save(self.owner["id"], self.payload())
|
|
result = self.service.list_entries(self.owner["id"])
|
|
|
|
self.assertEqual(result["summary"]["total"], 1)
|
|
self.assertEqual(result["summary"]["realized"], 0)
|
|
self.assertIsNone(result["summary"]["win_rate"])
|
|
self.assertEqual(result["items"][0]["tags"], ["计划内", "低吸"])
|
|
|
|
def test_realized_entries_build_summary(self):
|
|
self.service.save(
|
|
self.owner["id"], self.payload(action="sell", pnl_amount=500, pnl_pct=5)
|
|
)
|
|
self.service.save(
|
|
self.owner["id"],
|
|
self.payload(code="600000", name="浦发银行", action="sell", pnl_amount=-200, pnl_pct=-2, position_pct=40),
|
|
)
|
|
result = self.service.list_entries(self.owner["id"])
|
|
|
|
self.assertEqual(result["summary"]["realized"], 2)
|
|
self.assertEqual(result["summary"]["win_rate"], 50.0)
|
|
self.assertEqual(result["summary"]["pnl_amount"], 300.0)
|
|
self.assertEqual(result["summary"]["average_position"], 30.0)
|
|
|
|
def test_update_and_delete_require_ownership(self):
|
|
trade_id = self.service.save(self.owner["id"], self.payload())
|
|
with self.assertRaises(ValueError):
|
|
self.service.save(
|
|
self.other["id"], self.payload(id=trade_id, thesis="越权修改")
|
|
)
|
|
self.assertFalse(self.database.delete_trade_entry(self.other["id"], trade_id))
|
|
|
|
self.service.save(
|
|
self.owner["id"], self.payload(id=trade_id, thesis="复盘后修订")
|
|
)
|
|
self.assertEqual(
|
|
self.service.list_entries(self.owner["id"])["items"][0]["thesis"],
|
|
"复盘后修订",
|
|
)
|
|
self.assertTrue(self.database.delete_trade_entry(self.owner["id"], trade_id))
|
|
self.assertEqual(self.service.list_entries(self.owner["id"])["summary"]["total"], 0)
|
|
|
|
def test_entries_are_isolated_between_accounts(self):
|
|
self.service.save(self.owner["id"], self.payload())
|
|
self.assertEqual(self.service.list_entries(self.other["id"])["items"], [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|