From 28053f3bfcf3eddda96ef31dd43c7ba7c63c2b84 Mon Sep 17 00:00:00 2001 From: baobao Date: Mon, 19 Oct 2026 14:24:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A4=E6=98=93=E6=89=A7=E8=A1=8C=EF=BC=9A?= =?UTF-8?q?=E4=BA=BA=E5=B7=A5=E6=B8=85=E4=BB=93=E4=BF=9D=E7=95=99=E5=BE=85?= =?UTF-8?q?=E5=8F=AF=E5=8D=96=E4=BB=BB=E5=8A=A1=EF=BC=8C=E5=BD=92=E9=9B=B6?= =?UTF-8?q?=E4=BB=8D=E5=81=9C=E6=AD=A2=E6=97=A7=E4=B9=B0=E5=85=A5=EF=BC=88?= =?UTF-8?q?=E5=8F=B0=E8=B4=A6=20076=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、人工采纳清仓原来按当日可卖量裁剪。T+1为零时返回已采纳却丢弃执行安排,旧买入仍可能续单。现在只按确认数量与当前总持仓取较小值,执行器继续限制当日可卖量。持仓已归零仍停止旧买入,不额外生成卖单。 二、补齐两项真实入口回归,24专项与45批本地隔离禁网验证通过。普通减仓、成交核销与业务阈值保持。首次部署已由自然调度停止科士达剩余买入,补修尚待部署判收。 --- DEVLOG.md | 15 ++++++++++ app/web/main.py | 17 +++++++++-- docs/开发规格_买卖方向冲突修复_2026-10-19.md | 7 +++++ scripts/run_tests.py | 2 +- scripts/test_batch41_units.py | 31 ++++++++++++++++++-- 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 0b6cfc7..3f55b82 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2029,3 +2029,18 @@ bafecb7 经 45 策略用例、44 批本地及候选镜像禁网回归通过,11 **真机判收。** 修复前14:10仅科士达存在买卖重叠。已保存指令、成交、账务与参数原值,详见规格。未改成交,不恢复七条历史暂停指令,不操作188。 **还欠什么。** 候选镜像测试、部署、生效与页面核验,按真实证据追加。 + + +### 2026-10-19 14:22 首次部署与补查 + +fad1bb1于14:19:39在155四容器生效。镜像2e5213b9d37f9c7337f4857ab0401e7426ea196f80d1e6b78325aecc1a993d6b。 +45批候选镜像禁网测试通过,22专项通过。14:20:05正常调度终止科士达剩余买入。 +买入600/400保持,清仓400/200保持。两条新审计6912、6913均为system。 +部署前后15项参数一致,全部持仓账务字段及批次账一致,零新增委托。 +14:21再次调度正常,对账0差异,QMT在线。93行指令展示查询0.344秒。 +本地Chrome使用接口替身渲染,显示等待可卖与等待下一批,无页面错误。 +生产读取显示科士达清仓等待可卖、旧买入已撤销。未冒充登录后的生产浏览器验收。 + +补查人工清仓:T+1不可卖时原路径会采纳但不留指令。 +改为保留不超过确认量与当前总持仓的清仓任务;持仓已归零仍停止旧买入。 +新增两项专项,共24例。本项待候选镜像和第二次部署;没有代替用户采纳。 diff --git a/app/web/main.py b/app/web/main.py index 50e5493..fab6e6b 100644 --- a/app/web/main.py +++ b/app/web/main.py @@ -573,10 +573,23 @@ def api_decide(request: Request, proposal_id: str, payload: dict = Body(default= # 等人点采纳时持仓可能已被别的路卖少, 照原数落单会被卖出前的检查整条驳回, # 人点了「清掉」结果一股没卖, 且那条指令会永久在途、挡住后续所有清仓。 cur = pms_repo.get_position(p["ts_code"]) or {} - qty = ae.clamp_sell_qty(qty, cur) + if p['action'] == 'EXIT': + # 与命令清仓、信号清仓一致:T+1限制当次出手,不丢弃已确认的清仓安排。 + # 不扩大人确认的股数;已卖少的持仓按当前总量缩减。 + qty = min(qty, max(0, int(cur.get('total_qty') or 0))) + else: + qty = ae.clamp_sell_qty(qty, cur) if qty <= 0: + if p['action'] == 'EXIT': + from app.services.execution_conflicts import enforce_exit + stopped = enforce_exit({'instruction_id': proposal_id, 'ts_code': p['ts_code'], + 'action': 'EXIT', 'side': 'sell'}) + if not stopped['ok']: + return {'ok': False, 'decision': decision, + 'error': '已确认清仓,但旧买入收尾未完成:' + ';'.join(stopped['errors'])} return {"ok": True, "decision": decision, "instruction_id": None, - "note": "这只票现在没有可卖的股数 (已被清掉或今天刚买入), 未落单"} + "note": "当前无需要卖出的股数,清仓对应的旧买入已停止" if p['action'] == 'EXIT' + else "当前没有可卖的股数,本次未落单"} pms_repo.insert_instruction( instruction_id=instruction_id, origin_type="proposal", origin_id=proposal_id, ts_code=p["ts_code"], action=p["action"], side=side, qty=qty, diff --git a/docs/开发规格_买卖方向冲突修复_2026-10-19.md b/docs/开发规格_买卖方向冲突修复_2026-10-19.md index 559893e..4c2257b 100644 --- a/docs/开发规格_买卖方向冲突修复_2026-10-19.md +++ b/docs/开发规格_买卖方向冲突修复_2026-10-19.md @@ -46,3 +46,10 @@ 22项专项回归通过,覆盖五类清仓入口共用执行器、撤单失败与旧进度覆盖、T0不误挡、人工确认被拦时保持待确认。 45批配置隔离、禁止联网的回归通过,包含三页面守卫。生产状态另记部署判收。 原临时虚拟环境被系统清理,本轮另建 /private/tmp/pms-conflict-20261019-venv。 + +## 人工清仓补查 + +人工采纳EXIT原来按当日可卖量裁剪,T+1可卖量为零时会返回已采纳却不留清仓指令。 +改为按已确认数量与当前总持仓取较小值,执行器继续按当日可卖量分批,不扩大用户授权数量。 +持仓已经归零时不再生成卖单,但仍停止未完成的旧买入。普通减仓保持按可卖量处理。 +增加两项回归,专项共24例。此项为代码发现的边界,尚无自然人工确认事件判收。 diff --git a/scripts/run_tests.py b/scripts/run_tests.py index 56b24d6..f8eca4a 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -5,7 +5,7 @@ 运行: docker compose run --rm pms-web python scripts/run_tests.py 包含 (例数按 2026-10-12 隔离回归实跑校正): - test_batch41_units.py 跨流程买卖互斥、清仓收尾、出口撤单与真实进展 (22 例) + test_batch41_units.py 跨流程买卖互斥、清仓收尾、出口撤单与真实进展及人工清仓T+1 (24 例) test_batch40_units.py 逐笔历史修复、进度暂停、并发与事务回滚与均价失效 (22 例) test_batch39_units.py 成交双来源归属、迟到、部分成交与失败守成 (12 例) test_batch38_units.py 每日计划顺序、版本核验、重新确认、并发采纳、历史保留及采纳失败说明、明确否决撤下 (30 例) diff --git a/scripts/test_batch41_units.py b/scripts/test_batch41_units.py index cbc6d7c..00f80b8 100644 --- a/scripts/test_batch41_units.py +++ b/scripts/test_batch41_units.py @@ -251,6 +251,33 @@ class ConflictsTest(unittest.TestCase): self.assertEqual(self.fake.proposals['p']['status'],'WAIT_USER') self.assertEqual(len(self.fake.instructions),1) + def test_human_exit_retains_t1_locked_quantity(self): + from app.web import main + self.fake.positions[CODE]={'ts_code':CODE,'total_qty':400,'avail_qty':0} + self.fake.insert_proposal(proposal_id='exit_prop',ts_code=CODE,action='EXIT',qty=200, + hard_numbers={},expire_at=datetime.now()+timedelta(hours=1)) + with patch.object(main,'ok_logged',side_effect=lambda name,fn,**kw:fn()), \ + patch.object(main,'_actor',return_value='test'), \ + patch.object(main.td,'window_deadline',return_value='2026-10-21'): + result=main.api_decide(None,'exit_prop',{'decision':'ACCEPTED','reason':'确认清仓'}) + self.assertTrue(result['ok'],result) + row=self.fake.instructions[result['instruction_id']] + self.assertEqual(row['qty'],200) # 不扩成当前400股 + self.assertEqual(row['action'],'EXIT') + self.assertEqual(row['exec_qty'],0) + + def test_human_exit_after_position_closed_still_stops_old_buy(self): + from app.web import main + buy=ins(self.fake,'old','OPEN','buy',600,400) + self.fake.positions[CODE]={'ts_code':CODE,'total_qty':0,'avail_qty':0} + self.fake.insert_proposal(proposal_id='exit_prop',ts_code=CODE,action='EXIT',qty=200, + hard_numbers={},expire_at=datetime.now()+timedelta(hours=1)) + with patch.object(main,'ok_logged',side_effect=lambda name,fn,**kw:fn()), \ + patch.object(main,'_actor',return_value='test'): + result=main.api_decide(None,'exit_prop',{'decision':'ACCEPTED','reason':'确认清仓'}) + self.assertTrue(result['ok'],result) + self.assertIsNone(result['instruction_id']) + self.assertEqual(buy['status'],'CANCELLED') + self.assertEqual(buy['exec_qty'],400) + if __name__=='__main__':unittest.main() - -