股票交易记录系统的核心诉求是把每一笔买卖操作结构化存储,并能随时回答持仓数量、成本和盈亏情况。SQLite作为嵌入式数据库,无需独立服务端,特别适合个人投资者或小团队搭建轻量级复盘工具。下面按数据表设计、交易计算、自动化维护和Python集成四个部分展开。

一、股票交易记录系统的表结构设计
设计数据库前先明确几个关键实体:股票本身、交易流水和每日行情。股票基础信息表用来保存代码、名称和交易市场,交易流水表记录每一次买入卖出,每日行情快照表则用于计算持仓市值。把这三类信息分开存储,可以避免数据重复,也为后续扩展多账户、多币种留出空间。
先建立股票基础信息表和交易流水表。交易流水表中需要包含交易方向、数量、价格和手续费,并通过外键关联股票表。同时用检查约束限制交易方向只能是买入或卖出,数量必须大于零,防止脏数据写入。建表语句如下:
CREATE TABLE stocks (
stock_id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL UNIQUE,
name TEXT,
exchange TEXT,
currency TEXT DEFAULT 'CNY'
);
CREATE TABLE transactions (
tx_id INTEGER PRIMARY KEY AUTOINCREMENT,
stock_id INTEGER NOT NULL,
trade_date TEXT NOT NULL,
side TEXT NOT NULL CHECK(side IN ('BUY','SELL')),
quantity INTEGER NOT NULL CHECK(quantity > 0),
price REAL NOT NULL CHECK(price >= 0),
commission REAL DEFAULT 0,
account TEXT,
note TEXT,
FOREIGN KEY(stock_id) REFERENCES stocks(stock_id)
);
CREATE TABLE daily_prices (
price_id INTEGER PRIMARY KEY AUTOINCREMENT,
stock_id INTEGER NOT NULL,
trade_date TEXT NOT NULL,
close_price REAL,
high_price REAL,
low_price REAL,
volume INTEGER,
UNIQUE(stock_id,trade_date),
FOREIGN KEY(stock_id) REFERENCES stocks(stock_id)
);
外键约束在SQLite中默认关闭,每次连接数据库后需要执行PRAGMA foreign_keys = ON才会生效。另外,交易流水表会随着时间快速增长,建议在stock_id和trade_date上建立联合索引,提升按股票和日期筛选的速度。例如:
CREATE INDEX idx_tx_stock_date ON transactions(stock_id, trade_date);
如果交易涉及多个证券账户,还可以增加account字段并建立索引,后续按账户分别统计收益时会更加高效。表结构设计完成后,接下来要考虑如何计算持仓成本。
二、交易录入与移动加权平均成本计算
对于个人投资者,移动加权平均法是最直观的持仓成本计算方式。每次买入后,用买入金额加上手续费除以总持仓股数,得到新的平均成本;卖出时不改变平均成本,只减少持仓数量。这样做的好处是计算简单,不需要像先进先出那样逐笔匹配买卖记录,适合快速复盘。
先插入一笔买入记录,然后查询当前持仓和平均成本。买入交易的SQL如下:
PRAGMA foreign_keys = ON;
INSERT INTO stocks(symbol, name, exchange) VALUES ('600519', '贵州茅台', 'SSE');
INSERT INTO transactions(stock_id, trade_date, side, quantity, price, commission, account)
VALUES (1, '2024-01-15', 'BUY', 100, 1650.00, 5.00, 'main');
查询当前持仓数量和累计买入成本可以用聚合函数结合条件表达式:
SELECT s.symbol,
SUM(CASE WHEN t.side = 'BUY' THEN t.quantity ELSE -t.quantity END) AS hold_quantity,
SUM(CASE WHEN t.side = 'BUY' THEN t.quantity * t.price + t.commission ELSE 0 END) AS total_buy_cost
FROM stocks s
JOIN transactions t ON t.stock_id = s.stock_id
WHERE s.stock_id = 1
GROUP BY s.symbol;
已实现盈亏的计算相对复杂一些。卖出时收入减去对应买入成本,严格来说需要从最早买入的批次开始抵扣。如果采用移动加权平均,可以把卖出时的平均成本作为抵扣单价,公式为:卖出收入减去卖出数量乘以当前平均成本。这个逻辑可以放在应用层计算,也可以用SQL子查询实现,但后者在数据量大时性能会下降,建议在Python脚本中处理。
实际使用中,很多投资者还会关心每笔交易的手续费对成本的影响。手续费可以单独记录,也可以折算进买入成本。本文示例中把买入手续费加入成本,卖出手续费从收入中扣减,这样更贴近真实资金变动。
三、触发器与视图自动维护账户汇总
手动维护账户总成本、总市值和已实现盈亏容易遗漏,SQLite的触发器可以在交易流水写入后自动更新汇总表。先创建一张账户汇总表:
CREATE TABLE account_summary (
account TEXT PRIMARY KEY,
total_cost REAL DEFAULT 0,
realized_pnl REAL DEFAULT 0
);
然后写一个AFTER INSERT触发器,当交易流水表新增记录时,把对应账户的总成本和已实现盈亏累加。这里只处理买入和卖出对成本与盈亏的影响:
CREATE TRIGGER trg_tx_after_insert
AFTER INSERT ON transactions
BEGIN
INSERT INTO account_summary(account, total_cost, realized_pnl)
VALUES (NEW.account,
CASE WHEN NEW.side = 'BUY' THEN NEW.quantity * NEW.price + NEW.commission ELSE 0 END,
CASE WHEN NEW.side = 'SELL' THEN NEW.quantity * NEW.price - NEW.commission ELSE 0 END)
ON CONFLICT(account) DO UPDATE SET
total_cost = total_cost + excluded.total_cost,
realized_pnl = realized_pnl + excluded.realized_pnl;
END;
这个触发器只记录了交易带来的直接成本和卖出收入,并没有真正冲减买入成本。要得到更准确的已实现盈亏,还需要在更新时读取当前平均成本,但SQLite触发器里直接查询同一张表的聚合值容易造成递归或锁问题。更稳妥的做法是使用应用层事务:先计算盈亏,再更新汇总表。不过对于简单的个人记账,上述触发器已经能给出大致资金流向。
视图可以把频繁使用的持仓查询固化下来,避免每次手写长SQL。例如创建一个当前持仓视图:
CREATE VIEW v_current_positions AS
SELECT s.symbol,
s.name,
SUM(CASE WHEN t.side = 'BUY' THEN t.quantity ELSE -t.quantity END) AS hold_quantity,
SUM(CASE WHEN t.side = 'BUY' THEN t.quantity * t.price + t.commission ELSE 0 END) AS total_cost
FROM stocks s
JOIN transactions t ON t.stock_id = s.stock_id
GROUP BY s.symbol;
之后只需SELECT * FROM v_current_positions就能拿到所有股票的持仓和成本。连接每日行情快照表后,即可算出持仓市值和浮动盈亏。
四、用Python录入交易并生成每日收益报表
Python标准库自带的sqlite3模块操作SQLite非常方便。连接数据库后,先开启外键约束,再插入股票信息或交易记录。示例代码如下:
import sqlite3
conn = sqlite3.connect('stock_trading.db')
cur = conn.cursor()
cur.execute('PRAGMA foreign_keys = ON')
cur.execute(
'INSERT INTO stocks(symbol, name, exchange) VALUES (?, ?, ?)',
('000001', '平安银行', 'SZSE')
)
conn.commit()
如果需要批量导入历史成交记录,可以配合csv模块读取文件,循环写入交易流水表。假设CSV文件包含stock_id、trade_date、side、quantity、price和commission列,导入代码如下:
import csv
with open('trades.csv', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
cur.execute(
'INSERT INTO transactions(stock_id, trade_date, side, quantity, price, commission) '
'VALUES (?, ?, ?, ?, ?, ?)',
(
row['stock_id'],
row['trade_date'],
row['side'],
int(row['quantity']),
float(row['price']),
float(row.get('commission', 0))
)
)
conn.commit()
生成每日收益报表时,可以把当前持仓视图与每日行情快照表关联。例如查询某一天所有持仓的市值和浮动盈亏:
SELECT d.trade_date,
SUM(p.hold_quantity * d.close_price) AS market_value,
SUM(p.total_cost) AS total_cost,
SUM(p.hold_quantity * d.close_price) - SUM(p.total_cost) AS floating_pnl
FROM daily_prices d
JOIN stocks s ON s.stock_id = d.stock_id
JOIN v_current_positions p ON p.symbol = s.symbol
WHERE d.trade_date = '2024-02-18'
GROUP BY d.trade_date;
这个查询假设v_current_positions中的symbol与行情快照表对应的股票符号一致。实际工程中最好使用stock_id关联,避免股票代码重复或修改带来的问题。除此之外,SQLite的日期字段建议统一使用ISO 8601格式如2024-01-15,这样字符串排序和区间查询都不会出问题。
通过以上几个步骤,一个基础但完整的股票交易记录系统就搭建起来了。后续还可以扩展自动获取行情、计算技术指标、导出Excel报表等功能,而SQLite轻量、零配置的特性让这些迭代变得非常顺手。

