"""R635:输入交易单位,多期目标/可执行/实际;不归一化抹现金。""" from decimal import Decimal as D,InvalidOperation LOTS={'A':1,'B':10} def finite(value,positive=False): try:result=D(str(value)) except (InvalidOperation,ValueError) as error:raise ValueError('invalid decimal value') from error if not result.is_finite() or (result<=0 if positive else result<0):raise ValueError('invalid finite value') return result def step(cash,shares,prices,weights,blocked=()): cash=finite(cash) if set(shares)!=set(LOTS) or set(prices)!=set(LOTS) or set(weights)!=set(LOTS):raise ValueError('identity mismatch') if any(type(unit) is not int or unit<=0 for unit in LOTS.values()):raise ValueError('unverified trading unit') if any(type(shares[s]) is not int or shares[s]<0 or shares[s]%LOTS[s] for s in LOTS):raise ValueError('unsupported starting quantity') p={s:finite(prices[s],True) for s in LOTS};w={s:finite(weights[s]) for s in LOTS} if abs(sum(w.values())-1)>D('1e-20'):raise ValueError('bad weight budget') nav=cash+sum(D(shares[s])*p[s] for s in LOTS) if not nav.is_finite() or nav<=0:raise ValueError('zero or invalid NAV') desired={s:int((nav*w[s])//(p[s]*LOTS[s]))*LOTS[s] for s in LOTS} planned_cash=nav-sum(D(desired[s])*p[s] for s in LOTS);actual=dict(shares);events=[] for side in ['sell','buy']: for s in sorted(LOTS): delta=desired[s]-actual[s] if (side=='sell' and delta>=0) or (side=='buy' and delta<=0):continue if (s,side) in blocked: events.append((s,side,'blocked',abs(delta),0));continue qty=abs(delta) if side=='buy':qty=min(qty,int(cash//(p[s]*LOTS[s]))*LOTS[s]) signed=qty if side=='buy' else -qty cash-=D(signed)*p[s];actual[s]+=signed events.append((s,side,'cash_limited' if qty=0 and all(actual[s]%LOTS[s]==0 for s in LOTS) assert sum(D(actual[s])*p[s] for s in LOTS)+cash==nav executable_weights={s:D(desired[s])*p[s]/nav for s in LOTS} actual_weights={s:D(actual[s])*p[s]/nav for s in LOTS} assert abs(sum(executable_weights.values())+planned_cash/nav-1)