Compare commits

..

4 Commits

@ -101,6 +101,8 @@ async def batch_import_settlement(
total_skipped_all = 0 total_skipped_all = 0
processed_count = 0 processed_count = 0
failed_count = 0 failed_count = 0
# 维护跨文件的去重键集合,用于文件间去重
batch_existing_keys = set()
for file in files: for file in files:
if not file.filename.endswith(('.xls', '.xlsx')): if not file.filename.endswith(('.xls', '.xlsx')):
@ -125,7 +127,12 @@ async def batch_import_settlement(
failed_count += 1 failed_count += 1
continue continue
result = save_to_db(db, df_futures, df_options, file.filename) # 传入已处理文件的去重键,实现文件间去重
result = save_to_db(db, df_futures, df_options, file.filename, batch_existing_keys)
# 将本次导入的新记录去重键加入集合,供后续文件去重使用
if 'new_keys' in result:
batch_existing_keys.update(result['new_keys'])
file_new = result['futures_count'] + result['options_count'] file_new = result['futures_count'] + result['options_count']
file_skipped = result['futures_skipped'] + result['options_skipped'] file_skipped = result['futures_skipped'] + result['options_skipped']

@ -270,13 +270,16 @@ def save_to_db(
df_futures: pd.DataFrame, df_futures: pd.DataFrame,
df_options: pd.DataFrame, df_options: pd.DataFrame,
filename: str, filename: str,
batch_existing_keys: set = None,
) -> dict: ) -> dict:
""" """
将解析后的交易记录保存到数据库含逐条去重校验 将解析后的交易记录保存到数据库含逐条去重校验
去重规则同一交易日 + 同一品种 + 同一交易时间 + 同一价格 => 视为重复跳过 去重规则同一交易日 + 同一品种 + 同一交易时间 + 同一价格 => 视为重复跳过
:param batch_existing_keys: 批量导入时已处理文件的去重键集合用于文件间去重
:return: { :return: {
"batch_id": str, "futures_count": int, "options_count": int, "trade_dates": str, "batch_id": str, "futures_count": int, "options_count": int, "trade_dates": str,
"futures_skipped": int, "options_skipped": int, "total_parsed": int "futures_skipped": int, "options_skipped": int, "total_parsed": int,
"new_keys": set # 本次导入新增的去重键(用于批量导入时传递给下一个文件)
} }
""" """
_load_variety_name_map() _load_variety_name_map()
@ -309,9 +312,12 @@ def save_to_db(
# 批量加载已有记录的去重键 # 批量加载已有记录的去重键
existing_keys = _load_existing_dedup_keys(db, all_dates) existing_keys = _load_existing_dedup_keys(db, all_dates)
# 合并批量导入时已处理文件的去重键
if batch_existing_keys:
existing_keys = existing_keys | batch_existing_keys
records = [] records = []
new_keys_in_batch = set() # 防止本次导入文件内部重复 new_keys_in_batch = set() # 本次导入新增的去重键
skipped_details = [] # 记录被跳过的重复记录详情 skipped_details = [] # 记录被跳过的重复记录详情
# 处理期货记录 # 处理期货记录
@ -331,7 +337,7 @@ def save_to_db(
price = safe_float(row.get('成交价')) price = safe_float(row.get('成交价'))
dedup_key = _build_dedup_key(trade_date, variety, trade_time, price) dedup_key = _build_dedup_key(trade_date, variety, trade_time, price)
if dedup_key in existing_keys or dedup_key in new_keys_in_batch: if dedup_key in existing_keys:
futures_skipped += 1 futures_skipped += 1
skipped_details.append({ skipped_details.append({
'type': '期货', 'type': '期货',
@ -342,7 +348,7 @@ def save_to_db(
}) })
continue continue
# 记录前几条的去重键用于调试 # 记录前几笔期货去重键用于调试
if futures_parsed <= 3: if futures_parsed <= 3:
logger.info(f"[去重校验] 期货记录 #{futures_parsed}: raw_date={raw_date!r}, trade_date={trade_date!r}, variety={variety!r}, time={trade_time!r}, price={price!r} => key={dedup_key!r}") logger.info(f"[去重校验] 期货记录 #{futures_parsed}: raw_date={raw_date!r}, trade_date={trade_date!r}, variety={variety!r}, time={trade_time!r}, price={price!r} => key={dedup_key!r}")
@ -383,7 +389,7 @@ def save_to_db(
price = safe_float(row.get('权利金单价')) price = safe_float(row.get('权利金单价'))
dedup_key = _build_dedup_key(trade_date, variety, trade_time, price) dedup_key = _build_dedup_key(trade_date, variety, trade_time, price)
if dedup_key in existing_keys or dedup_key in new_keys_in_batch: if dedup_key in existing_keys:
options_skipped += 1 options_skipped += 1
skipped_details.append({ skipped_details.append({
'type': '期权', 'type': '期权',
@ -394,9 +400,9 @@ def save_to_db(
}) })
continue continue
new_keys_in_batch.add(dedup_key)
# 期权盈亏来自权利金(带正负号:卖为正,买为负) # 期权盈亏来自权利金(带正负号:卖为正,买为负)
premium = safe_float(row.get('权利金')) premium = safe_float(row.get('权利金'))
new_keys_in_batch.add(dedup_key)
rec = TradeRecord( rec = TradeRecord(
trade_type='期权', trade_type='期权',
symbol=contract, symbol=contract,
@ -457,6 +463,7 @@ def save_to_db(
"futures_parsed": futures_parsed, "futures_parsed": futures_parsed,
"options_parsed": options_parsed, "options_parsed": options_parsed,
"skipped_details": skipped_details, # 被跳过的重复记录详情 "skipped_details": skipped_details, # 被跳过的重复记录详情
"new_keys": new_keys_in_batch,
} }

@ -1193,8 +1193,8 @@
<div id="tr-tab-stats" class="tr-tab-panel active"> <div id="tr-tab-stats" class="tr-tab-panel active">
<!-- 快捷日期 --> <!-- 快捷日期 -->
<div class="tr-quick-dates" id="tr-quick-dates"> <div class="tr-quick-dates" id="tr-quick-dates">
<button class="tr-quick-date active" data-range="today">当日</button> <button class="tr-quick-date" data-range="today">当日</button>
<button class="tr-quick-date" data-range="week">近一周</button> <button class="tr-quick-date active" data-range="week">近一周</button>
<button class="tr-quick-date" data-range="half-month">近半月</button> <button class="tr-quick-date" data-range="half-month">近半月</button>
<button class="tr-quick-date" data-range="month">近一月</button> <button class="tr-quick-date" data-range="month">近一月</button>
<button class="tr-quick-date" data-range="quarter">近三月</button> <button class="tr-quick-date" data-range="quarter">近三月</button>

@ -1632,20 +1632,10 @@ function initTradeReview() {
} }
async function trInitDefaultDate() { async function trInitDefaultDate() {
try {
const res = await fetch(`${TR_API_BASE}/latest-trade-date`);
const json = await res.json();
if (json.success && json.data.trade_date) {
const dateStr = json.data.trade_date;
document.getElementById('tr-start-date').value = dateStr;
document.getElementById('tr-end-date').value = dateStr;
}
} catch (e) {
console.error('获取最后交易日失败', e);
}
// 初始化快捷日期按钮 // 初始化快捷日期按钮
trInitQuickDates(); trInitQuickDates();
trLoadAllData(); // 默认加载近一周数据
trSetDateRange('week');
} }
function trInitQuickDates() { function trInitQuickDates() {

Binary file not shown.

@ -0,0 +1,13 @@
# 批量导入跨文件去重
## 动机
当前批量导入时,每个文件独立处理,文件之间没有重复性校验。如果多个文件包含相同的交易记录,会导致数据重复。同时,一个文件内部的一次委托分多次成交不应被误判为重复。
## 目标
1. 单文件导入时,文件内部不去重,允许一次委托分多次成交
2. 批量导入时,跨文件进行去重,避免重复导入
3. 数据库中已有记录仍需去重
## 范围
- `app/services/trade_parser.py``save_to_db` 增加 `batch_existing_keys` 参数,返回新增去重键
- `app/api/trade_review.py` — 批量导入时维护跨文件去重键集合
Loading…
Cancel
Save