feat: add structured manual trading journal
This commit is contained in:
+113
@@ -297,6 +297,30 @@ class ReviewDatabase:
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_alerts_user_due
|
||||
ON alerts(user_id, available_date, is_read, id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS trade_entries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
trade_date TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
price REAL NOT NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 0,
|
||||
position_pct REAL NOT NULL DEFAULT 0,
|
||||
pnl_amount REAL,
|
||||
pnl_pct REAL,
|
||||
thesis TEXT NOT NULL DEFAULT '',
|
||||
execution TEXT NOT NULL DEFAULT '',
|
||||
emotion TEXT NOT NULL DEFAULT 'calm',
|
||||
tags TEXT NOT NULL DEFAULT '[]',
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_trade_entries_user_date
|
||||
ON trade_entries(user_id, trade_date DESC, id DESC);
|
||||
"""
|
||||
)
|
||||
user_columns = {
|
||||
@@ -1550,6 +1574,95 @@ class ReviewDatabase:
|
||||
)
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def save_trade_entry(
|
||||
self,
|
||||
user_id: int,
|
||||
trade_date: str,
|
||||
code: str,
|
||||
name: str,
|
||||
action: str,
|
||||
price: float,
|
||||
quantity: int,
|
||||
position_pct: float,
|
||||
pnl_amount: float | None,
|
||||
pnl_pct: float | None,
|
||||
thesis: str,
|
||||
execution: str,
|
||||
emotion: str,
|
||||
tags: list[str],
|
||||
trade_id: int | None = None,
|
||||
) -> int:
|
||||
now = datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
tags_json = json.dumps(tags, ensure_ascii=False, separators=(",", ":"))
|
||||
with self.connect() as connection:
|
||||
if trade_id:
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
UPDATE trade_entries SET
|
||||
trade_date=?, code=?, name=?, action=?, price=?, quantity=?,
|
||||
position_pct=?, pnl_amount=?, pnl_pct=?, thesis=?, execution=?,
|
||||
emotion=?, tags=?, updated_at=?
|
||||
WHERE id=? AND user_id=?
|
||||
""",
|
||||
(
|
||||
trade_date, code, name, action, price, quantity, position_pct,
|
||||
pnl_amount, pnl_pct, thesis, execution, emotion, tags_json, now,
|
||||
int(trade_id), int(user_id),
|
||||
),
|
||||
)
|
||||
if cursor.rowcount == 0:
|
||||
raise ValueError("交易记录不存在或无权修改。")
|
||||
return int(trade_id)
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
INSERT INTO trade_entries
|
||||
(user_id, trade_date, code, name, action, price, quantity,
|
||||
position_pct, pnl_amount, pnl_pct, thesis, execution, emotion,
|
||||
tags, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
int(user_id), trade_date, code, name, action, price, quantity,
|
||||
position_pct, pnl_amount, pnl_pct, thesis, execution, emotion,
|
||||
tags_json, now, now,
|
||||
),
|
||||
)
|
||||
return int(cursor.lastrowid)
|
||||
|
||||
def list_trade_entries(
|
||||
self, user_id: int, start_date: str = "", end_date: str = "", code: str = "",
|
||||
limit: int = 300,
|
||||
) -> list[dict[str, Any]]:
|
||||
clauses = ["user_id = ?"]
|
||||
parameters: list[Any] = [int(user_id)]
|
||||
if start_date:
|
||||
clauses.append("trade_date >= ?")
|
||||
parameters.append(start_date)
|
||||
if end_date:
|
||||
clauses.append("trade_date <= ?")
|
||||
parameters.append(end_date)
|
||||
if code:
|
||||
clauses.append("code = ?")
|
||||
parameters.append(code)
|
||||
parameters.append(max(1, min(1000, int(limit))))
|
||||
with self.connect() as connection:
|
||||
rows = connection.execute(
|
||||
f"""
|
||||
SELECT * FROM trade_entries WHERE {' AND '.join(clauses)}
|
||||
ORDER BY trade_date DESC, id DESC LIMIT ?
|
||||
""",
|
||||
parameters,
|
||||
).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def delete_trade_entry(self, user_id: int, trade_id: int) -> bool:
|
||||
with self.connect() as connection:
|
||||
cursor = connection.execute(
|
||||
"DELETE FROM trade_entries WHERE id = ? AND user_id = ?",
|
||||
(int(trade_id), int(user_id)),
|
||||
)
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def start_sync(self, trade_date: str, source: str) -> int:
|
||||
started_at = datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
with self.connect() as connection:
|
||||
|
||||
Reference in New Issue
Block a user