tradingSystem/app/services/plan_recheck.py

79 lines
4.8 KiB
Python

"""采纳前核验;复用原扫描、合议、研判与规则闸,不解释自然语言。"""
from datetime import datetime
from app.core.plan_revision import compare, fingerprint, portfolio_context
def current_basis(hard, code, proposal_id=None):
from app.services import plan_feed, portfolio, command_service, proposal_service as ps
view = portfolio.positions_view()
stock = command_service.effective_stock_params()
params = ps._scan_params(view)
selected = plan_feed.candidates(held=[x['ts_code'] for x in view['held']],
black=[c for c, v in stock.items() if v.get('black')], force=True,
route_by_verdict=bool(params.get('open_route_by_verdict')), recheck_code=code)
current = next((r for r in selected['items'] if r['ts_code'] == code), None)
if not (selected.get('decision') or {}).get('id'):
return {'ok': False, 'error': '当前计划没有可核验版本'}
if current is None:
return {'ok': False, 'error': '该票已不满足当前候选资格', 'material': True}
result = compare(hard.get('plan_basis'), current, code)
if result == 'unavailable':
return {'ok': False, 'error': '当前计划修订或消费留痕尚未完成'}
ph = fingerprint({k: v for k, v in params.items() if not k.startswith('_')})
commitments = ps._inflight_open_commitments(view, exclude_proposal=proposal_id)
context = portfolio_context(view, commitments, stock.get(code))
material = (result == 'material' or hard.get('scan_params_hash') != ph
or hard.get('portfolio_context_hash') != context)
return {'ok': not material, 'material': material,
'error': '研究、资格或仓位参数已变化,须重新评估' if material else None,
'view': view, 'stock': stock, 'params': params,
'provenance': {'decision': current['decision'], 'consumption_id': current['consumption_id'],
'version_check': {'status': 'passed', 'at': datetime.now().astimezone().isoformat(),
'reason': '依据相同,名次变化不撤销'}}}
def before_accept(p, expected_revision):
from app.services import proposal_service as ps
from app.repo import pms_repo
hard = p.get('hard_numbers') or {}
if p.get('action') != 'OPEN' or hard.get('kind') == 'strategy':
return {'ok': True, 'proposal': p}
try:
check = current_basis(hard, p['ts_code'], proposal_id=p.get('proposal_id'))
if check.get('ok'):
if hard.get('proposal_revision') and expected_revision != hard['proposal_revision']:
return {'ok': False, 'error': '提议内容已更新,请刷新后再次确认', 'proposal': p}
return {'ok': True, 'proposal': dict(p, hard_numbers=dict(hard, **check['provenance']))}
if not check.get('material') or not check.get('view'):
return check
now = datetime.now()
out = {'skipped': [], 'rejected': [], 'queued': [], 'executed': []}
params, view = check['params'], check['view']
mkt = {}
params['_mkt'] = mkt
fresh = ps._scan_open(view, params, check['stock'], {}, mkt, out, now=now,
review_code=p['ts_code'], exclude_proposal=p['proposal_id'])
if not fresh:
return {'ok': False, 'error': '重新评估未产出可采纳提议', 'details': out}
c = fresh[0]
c['_plan_recheck'] = True
from app.services import param_store
from app.core import tradedays as td
brake = td.ymd() < param_store.get_int('PMS_BRAKE_UNTIL', 0)
result = ps._route_one(c, view, params, check['stock'], brake, now, True, out, review_only=True)
if not result:
return {'ok': False, 'error': '新版重新评估未通过', 'details': out}
hn = c['hard_numbers']
hn['version_check'] = {'status': 'reconfirmed_required', 'at': now.astimezone().isoformat(),
'reason': '依据变化,已重跑合议、研判、数量及规则闸'}
hn['proposal_revision'] = fingerprint({'qty': c['qty'], 'hard_numbers': hn})
if not pms_repo.revise_pending_proposal(p, c, result['verdict']):
return {'ok': False, 'error': '提议被并发更新,请刷新'}
pms_repo.insert_ledger(ts_code=p['ts_code'], action=p['action'], arbiter='rule',
verdict='WARN', price_at=hn.get('price') or 0, hard_numbers=hn,
ref_id=p['proposal_id'], reason='新版依据变化,更新提议并要求重新确认')
return {'ok': False, 'error': '提议已按新版重新评估,请查看更新内容后再次确认',
'proposal': dict(p, qty=c['qty'], hard_numbers=hn)}
except Exception as e:
return {'ok': False, 'error': f'版本核验暂不可用,保留提议:{type(e).__name__}'}