"""C01公司行动构造算例;Python 3.10+标准库,离线从任意目录运行。""" from decimal import Decimal, ROUND_DOWN, ROUND_HALF_UP, getcontext from fractions import Fraction from pathlib import Path import json getcontext().prec = 40 D = Decimal def money(value): return str(D(value).quantize(D('0.01'), rounding=ROUND_HALF_UP)) def case(inputs, assumptions, tables, checks): assert all(checks.values()), checks return {'inputs': inputs, 'assumptions': assumptions, 'tables': tables, 'checks': checks} def calculate(): out = {} total, excluded, actual, held, price = D(120000000), D(6000000), D('0.50'), D(2400), D(12) eligible = total-excluded pool = eligible*actual virtual = pool/total out['DJ3-001'] = case( {'total_shares': str(total), 'excluded_shares': str(excluded), 'actual_per_share_CNY': str(actual), 'account_shares': str(held), 'previous_price_CNY': str(price)}, ['全部主体与金额均构造;账户股份全部合资格,暂不计税费', 'Decimal精度40位,金额展示四舍五入到分;虚拟每股及参考价保留模型精度', '实际市场价格未知,不要求个人参考估值加派息机械等于前值'], {'denominators': [{'label':'总股本','shares':str(total)}, {'label':'排除回购','shares':str(excluded)}, {'label':'参与分派','shares':str(eligible)}], 'two_tracks': {'pool_CNY':money(pool),'actual_per_share_CNY':str(actual),'virtual_per_share_CNY':str(virtual),'actual_account_CNY':money(held*actual),'wrong_account_CNY':money(held*virtual),'understatement_CNY':money(held*(actual-virtual)),'reference_price_CNY':str(price-virtual),'wrong_reference_CNY':str(price-actual)}, 'excluded_change': [{'excluded_shares':str(e), 'eligible_shares':str(total-e), 'pool_CNY':money((total-e)*actual),'virtual_per_share_CNY':str((total-e)*actual/total)} for e in [D(0),excluded,D(12000000)]]}, {'pool_maps_both_denominators':pool==total*virtual==eligible*actual,'zero_exclusion_same_per_share':total*actual/total==actual,'zero_dividend_zero_pool':eligible*D(0)==0}) old, new, per, account = D(90000000), D(96000000), D('.40'), D(1600) fixed_pool = old*per fixed_per = fixed_pool/new out['DJ3-002'] = case( {'old_eligible_shares':str(old),'new_eligible_shares':str(new),'old_per_share_CNY':str(per),'old_pool_CNY':money(fixed_pool),'account_shares':str(account)}, ['两套均为构造实施方案,不是对同一真实公告的两种任选解释','不计税费;持有1600股均合资格;精确十进制计算,金额到分'], {'original_plan':{'shares':str(old),'pool_CNY':money(fixed_pool),'per_share_CNY':str(per),'account_CNY':money(account*per)}, 'plans':[{'constraint':'固定总额','shares':str(new),'pool_CNY':money(fixed_pool),'per_share_CNY':str(fixed_per),'account_CNY':money(account*fixed_per)}, {'constraint':'固定每股','shares':str(new),'pool_CNY':money(new*per),'per_share_CNY':str(per),'account_CNY':money(account*per)}], 'differences':{'pool_CNY':money(new*per-fixed_pool),'account_CNY':money(account*(per-fixed_per))}, 'fixed_pool_share_sensitivity':[{'shares':str(n),'per_share_CNY':str(fixed_pool/n)} for n in [D(80000000),old,new]]}, {'fixed_pool_unchanged':new*fixed_per==fixed_pool,'same_shares_same_results':fixed_pool/old==per,'zero_dividend_zero_both':old*D(0)==new*D(0)==0}) n,p,cash,div,charge=D(3000),D(8),D(500),D('.20'),D(30) gross=n*div; net=gross-charge; after=n*(p-div) states=[{'date':'2026-09-01','voucher':'期末持仓快照','shares':str(n),'price_CNY':str(p),'stock_CNY':money(n*p),'receivable_CNY':'0.00','cash_CNY':money(cash),'total_CNY':money(n*p+cash)}, {'date':'2026-09-02','voucher':'构造权益确认单E-0902','shares':str(n),'price_CNY':str(p-div),'stock_CNY':money(after),'receivable_CNY':money(gross),'cash_CNY':money(cash),'total_CNY':money(after+gross+cash)}, {'date':'2026-09-09','voucher':'构造支付单P-0909和扣款单F-0909','shares':str(n),'price_CNY':str(p-div),'stock_CNY':money(after),'receivable_CNY':'0.00','cash_CNY':money(cash+net),'total_CNY':money(after+cash+net)}] out['DJ3-003']=case( {'shares':str(n),'pre_price_CNY':str(p),'initial_cash_CNY':money(cash),'dividend_per_share_CNY':str(div),'confirmed_charge_CNY':money(charge),'dates':[r['date'] for r in states]}, ['单币人民币;09-02凭构造确认单确认应收,09-09才实收,日期不对应真实交易日程','为隔离状态差异,除息后两日估值均7.80;扣款30为构造已确认金额而非税率推断','金额四舍五入到分,不把应收同时记作可用现金'], {'states':states,'settlement':{'receivable_reduction_CNY':money(gross),'cash_increase_CNY':money(net),'charge_CNY':money(charge)},'errors':[{'state':'09-02漏应收','reported_total_CNY':money(after+cash),'difference_CNY':money(-gross)},{'state':'09-09未冲应收','reported_total_CNY':money(after+cash+net+gross),'difference_CNY':money(gross)}], 'changed_price':{'post_price_CNY':'7.60','stock_CNY':money(n*D('7.60')),'receivable_CNY':money(gross),'cash_CNY':money(cash),'total_CNY':money(n*D('7.60')+gross+cash)}}, {'receivable_settlement_balances':gross==net+charge,'before_after_ex_charge_equal':n*p+cash==after+gross+cash,'charge_explains_last_change':D(states[1]['total_CNY'])-D(states[2]['total_CNY'])==charge,'zero_dividend_no_receivable':n*D(0)==0}) n,chosen,d,sp=D(10000),D(6300),D('.36'),D('7.40') elected=chosen*d; integer=int(elected/sp); used=D(integer)*sp; rem=elected-used; unchosen=(n-chosen)*d out['DJ3-004']=case( {'old_shares':str(n),'elected_old_shares':str(chosen),'dividend_per_share_HKD':str(d),'scrip_price_HKD':str(sp)}, ['构造香港直接账户且获有效选择;选股股数必须介于0和原持股之间','构造条款:新增股向下取整数,余款退现金并向下截到仙;无税费','金额以港元计,代息价不是上市后市场价;不混用送转比例或基金小数份额'], {'allocation':{'total_dividend_HKD':money(n*d),'elected_dividend_HKD':money(elected),'new_integer_shares':integer,'used_dividend_HKD':money(used),'residual_cash_HKD':str(rem.quantize(D('.01'),rounding=ROUND_DOWN)),'unelected_cash_HKD':money(unchosen),'total_cash_HKD':money(rem+unchosen),'ending_shares':str(n+integer)}, 'election_comparison':[{'elected_old_shares':str(e),'new_shares':int(e*d/sp),'used_dividend_HKD':money(D(int(e*d/sp))*sp),'cash_HKD':money(n*d-D(int(e*d/sp))*sp)} for e in [D(0),chosen,n]], 'wrong_rounding':{'round_up_shares':integer+1,'required_HKD':money(D(integer+1)*sp),'excess_over_elected_HKD':money(D(integer+1)*sp-elected)}}, {'dividend_allocation_balances':used+rem+unchosen==n*d,'integer_not_exceed_elected':used<=elected<(integer+1)*sp,'zero_election_zero_new':int(D(0)*d/sp)==0}) q,r,s,fee1,fee2=D(1800),D('.28'),D('2.20'),D(18),D(12) buy=q*r+fee1; subscribe=q*s+fee2; total=buy+subscribe out['DJ3-005']=case( {'rights_units':str(q),'right_price_HKD':str(r),'shares_per_right':'1','subscription_price_HKD':str(s),'buy_fee_HKD':money(fee1),'subscription_fee_HKD':money(fee2)}, ['只限允许供股权买卖与后续有效认购的香港直接账户构造','全部1800权有效认购1800新股;无额外费用;每股单位成本展示6位,不先舍入再乘','失效场景未提交有效认购,认购费用也未发生,权利终值为零'], {'payment_stages':[{'stage':'买权','principal_HKD':money(q*r),'fee_HKD':money(fee1),'cash_out_HKD':money(buy)},{'stage':'行权','principal_HKD':money(q*s),'fee_HKD':money(fee2),'cash_out_HKD':money(subscribe)}], 'completed':{'total_principal_HKD':money(q*(r+s)),'total_fees_HKD':money(fee1+fee2),'total_cost_HKD':money(total),'new_shares':str(q),'unit_cost_HKD':str((total/q).quantize(D('.000001'),rounding=ROUND_HALF_UP)),'unit_cost_exact_fraction':str(Fraction(total)/Fraction(q))}, 'lapse':{'cash_out_HKD':money(buy),'new_shares':0,'rights_terminal_value_HKD':'0.00','loss_HKD':money(buy)}, 'zero_fee':{'cost_HKD':money(q*(r+s)),'unit_cost_HKD':str(r+s)}}, {'two_payments_equal_total':buy+subscribe==total,'fees_explain_difference':total-q*(r+s)==fee1+fee2,'zero_right_price_still_subscription':D(0)+s==s}) old,ratio,right_price,fee,end_price=D(8000),D(4),D('.30'),D(20),D('3.10') rights=old/ratio; proceeds=rights*right_price-fee; stock=old*end_price out['DJ3-006']=case( {'old_shares':str(old),'old_shares_per_right':str(ratio),'sale_price_per_right_HKD':str(right_price),'sale_fee_HKD':money(fee),'ending_old_share_price_HKD':str(end_price)}, ['构造香港直接账户、每权认1新股且允许买卖;两路径均不认购','出售路径2000权全部成交,现金保留;失效路径无补偿安排','权利收到时数量备查,不把理论价当真实成交价;金额到分'], {'paths':[{'path':'出售','old_shares':str(old),'new_subscribed_shares':'0','rights_received':str(rights),'rights_sold':str(rights),'rights_expired':'0','rights_remaining':'0','cash_HKD':money(proceeds),'stock_HKD':money(stock),'total_HKD':money(stock+proceeds)}, {'path':'失效','old_shares':str(old),'new_subscribed_shares':'0','rights_received':str(rights),'rights_sold':'0','rights_expired':str(rights),'rights_remaining':'0','cash_HKD':'0.00','stock_HKD':money(stock),'total_HKD':money(stock)}], 'sale_cash':{'gross_HKD':money(rights*right_price),'fee_HKD':money(fee),'net_HKD':money(proceeds)}, 'price_sensitivity':[{'sale_price_HKD':str(p),'gross_HKD':money(rights*p),'net_HKD':money(rights*p-fee)} for p in [D('.10'),right_price,D('.40')]]}, {'same_old_stock_both_paths':stock==old*end_price,'sale_net_difference':stock+proceeds-stock==proceeds,'rights_quantity_conserved':rights-rights==0,'zero_fee_equals_gross':rights*right_price-D(0)==rights*right_price}) applied,p,allotted=D(12000),D('1.25'),D(4500) prepaid=applied*p; used=allotted*p; refund=prepaid-used out['DJ3-007']=case( {'applied_extra_shares':str(applied),'subscription_price_HKD':str(p),'prepayment_HKD':money(prepaid),'actual_allotted_shares':str(allotted),'separate_service_fee_HKD':'30.00','partial_refund_HKD':'6000.00'}, ['限允许额外申请的香港直接账户,港股通排除;构造配发比例不预测真实分配','退款不计利息;服务费30已扣且不退,独立于认购预缴','退款通知与到账分开,分两次退回属构造;金额到分'], {'rolling':[{'state':'申请预缴','prepayment_HKD':money(prepaid),'used_HKD':'0.00','pending_classification_HKD':money(prepaid),'refund_received_HKD':'0.00','refund_due_HKD':None}, {'state':'配发确认','prepayment_HKD':money(prepaid),'used_HKD':money(used),'pending_classification_HKD':'0.00','refund_received_HKD':'0.00','refund_due_HKD':money(refund)}, {'state':'部分到账','prepayment_HKD':money(prepaid),'used_HKD':money(used),'pending_classification_HKD':'0.00','refund_received_HKD':'6000.00','refund_due_HKD':money(refund-D(6000))}, {'state':'全部到账','prepayment_HKD':money(prepaid),'used_HKD':money(used),'pending_classification_HKD':'0.00','refund_received_HKD':money(refund),'refund_due_HKD':'0.00'}], 'allocation_boundaries':[{'allotted_shares':str(q),'used_HKD':money(q*p),'refund_HKD':money(prepaid-q*p)} for q in [D(0),allotted,applied]], 'final_total_cost_HKD':money(used+D(30))}, {'prepayment_equals_used_and_refund':prepaid==used+refund,'partial_refund_balances':D(6000)+(refund-D(6000))==refund,'zero_allocation_full_refund':prepaid-D(0)*p==prepaid,'full_allocation_zero_refund':prepaid-applied*p==0}) rows=[] for old in [12347,5000,4999,9,0]: quotient=D(old)/10; whole=int(quotient); fraction=quotient-whole; lots,remainder=divmod(whole,500) rows.append({'old_shares':old,'raw_new_shares':str(quotient),'integer_new_shares':whole,'fractional_entitlement':str(fraction),'whole_lots':lots,'integer_odd_lot_shares':remainder}) out['DJ3-008']=case( {'consolidation_old_per_new':10,'new_board_lot_shares':500,'old_holdings':[12347,5000,4999,9,0]}, ['构造香港直接账户:每名持有人合计计算,小于1新股不发行、也不支付现金','整数新股全部保留;每手500只是交易单位,不是所有权最小整百规则','港股通另有舍尾后精确算法分配差额,本模型不是其最终账户分配'], {'decomposition':rows,'wrong_board_lot_floor':{'old_shares':12347,'correct_integer_new_shares':1234,'wrong_only_full_lot_shares':1000,'missing_integer_shares':234}}, {'all_rows_reconstruct_quotient':all(D(r['integer_new_shares'])+D(r['fractional_entitlement'])==D(r['raw_new_shares']) for r in rows),'all_rows_reconstruct_integer':all(r['whole_lots']*500+r['integer_odd_lot_shares']==r['integer_new_shares'] for r in rows),'zero_holding_zero':rows[-1]['integer_new_shares']==0}) old,pre,div,bonus,rights,sub=D(2000),D(10),D('.30'),D('.20'),D('.25'),D(4) cash=old*div; free=old*bonus; paid=old*rights; external=paid*sub; final=old+free+paid stock=old*pre-cash+external; reference=Fraction(stock)/Fraction(final) out['DJ3-009']=case( {'old_shares':str(old),'pre_price_CNY':str(pre),'cash_per_old_share_CNY':str(div),'free_new_per_old_share':str(bonus),'paid_new_per_old_share':str(rights),'subscription_price_CNY':str(sub)}, ['全部权益均按登记旧股2000股计,免费新增不再获得本次现金或付费配额','纯构造组合,无税费;全额认购;不是沪深或香港统一流程','参考价用Fraction精确比值,展示6位;守恒用精确分数而非舍入价乘股数'], {'base_matrix':[{'item':'现金','base_old_shares':str(old),'rate':str(div),'result_CNY':money(cash)},{'item':'免费新增','base_old_shares':str(old),'rate':str(bonus),'new_shares':str(free)},{'item':'付费新增','base_old_shares':str(old),'rate':str(rights),'new_shares':str(paid),'cash_out_CNY':money(external)}], 'reconciliation':{'beginning_reference_value_CNY':money(old*pre),'ending_shares':str(final),'stock_reference_value_CNY':money(stock),'dividend_CNY':money(cash),'external_cash_CNY':money(external),'stock_plus_cash_CNY':money(stock+cash),'net_after_external_CNY':money(stock+cash-external),'reference_price_exact':str(reference),'reference_price_display_CNY':str((stock/final).quantize(D('.000001'),rounding=ROUND_HALF_UP))}, 'wrong_base':{'after_free_shares':str(old+free),'wrong_paid_shares':str((old+free)*rights),'excess_paid_shares':str(free*rights),'excess_payment_CNY':money(free*rights*sub)}, 'zero_paid_rate':{'ending_shares':str(old+free),'external_CNY':'0.00','reference_price_exact':str(Fraction(old*pre-cash)/Fraction(old+free))}}, {'external_cash_removed_before_comparison':stock+cash-external==old*pre,'fraction_reference_conserves_value':reference*Fraction(final)==Fraction(stock),'zero_all_rates_same_price':Fraction(old*pre)/Fraction(old)==Fraction(pre)}) initial_old,old_per_new,new_price=10000,5,D('15.00') old_batches,temporary_batches=[6000,4000],[1600,400] old_balance,temporary_balance,formal_balance=initial_old,0,0 mapping=[];vouchers=[] def snapshot(state): equivalent=Fraction(old_balance,old_per_new)+temporary_balance+formal_balance assert equivalent.denominator==1 mapping.append({'state':state,'old_shares':old_balance,'temporary_new_shares':temporary_balance,'formal_new_shares':formal_balance,'equivalent_new_shares':int(equivalent)}) snapshot('转换前') for i,quantity in enumerate(old_batches): assert quantity<=old_balance and quantity%old_per_new==0 addition=quantity//old_per_new old_balance-=quantity;temporary_balance+=addition vouchers.append({'from':'OLD','decrease':quantity,'to':'TEMP','increase':addition}) snapshot(['首笔旧股转换','余下旧股转换'][i]) for i,quantity in enumerate(temporary_batches): assert quantity<=temporary_balance temporary_balance-=quantity;formal_balance+=quantity vouchers.append({'from':'TEMP','decrease':quantity,'to':'NEW','increase':quantity}) snapshot(['首笔临时转正式','转换完成'][i]) final_new=initial_old//old_per_new duplicate_new=final_new+initial_old//old_per_new out['DJ3-010']=case( {'old_per_new':old_per_new,'initial_old_shares':initial_old,'new_unit_reference_HKD':str(new_price),'old_to_temp_batches':old_batches,'temp_to_formal_batches':temporary_batches}, ['所有代码均为文字占位OLD/TEMP/NEW而非真实证券代码;按凭证分批转换,不设真实交易日程','临时栏已是新股单位,不能再除5;旧历史影子行不是有效余额','渠道日期与交易服务须另核,模型不是港股通账户界面或统一流程;金额只作固定参考估值'], {'mapping':mapping,'conversion_vouchers':vouchers, 'reference_value_HKD':money(final_new*new_price),'duplicate_shadow':{'stale_old_shares':initial_old,'valid_new_shares':final_new,'wrong_equivalent_new_shares':duplicate_new,'wrong_reference_value_HKD':money(duplicate_new*new_price)}, 'zero_conversion':{'old_shares':initial_old,'new_shares':0,'equivalent_new_shares':final_new}}, {'every_state_preserves_economic_quantity':all(Fraction(r['old_shares'],old_per_new)+r['temporary_new_shares']+r['formal_new_shares']==final_new for r in mapping),'first_conversion_consistent':old_batches[0]//old_per_new==mapping[1]['temporary_new_shares'],'all_old_converted':sum(old_batches)==initial_old,'formal_reconciliation':sum(temporary_batches)==final_new}) pool_q,price,sub,fee,denom,threshold=D(500000),D('2.35'),D('2.10'),D(25000),D(500000),D(50) gross=pool_q*(price-sub); net=max(D(0),gross-fee); per=net/denom distributions=[] for q in [D(100),D(250),D(251),D(1000)]: share=(q*per).quantize(D('.01'),rounding=ROUND_DOWN) distributions.append({'unsubscribed_rights':str(q),'raw_allocation_HKD':money(share),'paid_HKD':money(share if share>threshold else 0),'retained_HKD':money(share if share<=threshold else 0)}) out['DJ3-011']=case( {'placed_shares':str(pool_q),'placing_price_HKD':str(price),'subscription_price_HKD':str(sub),'pool_expenses_HKD':money(fee),'allocation_denominator_rights':str(denom),'constructed_payment_threshold_HKD':money(threshold),'threshold_rule':'超过50全额支付;50或以下不支付'}, ['全量配售与分母均构造;无自行卖权成交;只分配已实现净池,不保证出现溢价','50港元门槛为教学自定,不是NIU公告100港元或全港标准','按权利份额分摊,现金向下截到仙;净池以零为下限,不向股东倒收负数'], {'pool':{'placing_receipts_HKD':money(pool_q*price),'subscription_component_HKD':money(pool_q*sub),'gross_premium_HKD':money(gross),'expenses_HKD':money(fee),'distributable_net_HKD':money(net),'per_right_HKD':str(per)}, 'distributions':distributions,'price_sensitivity':[{'placing_price_HKD':str(p),'gross_premium_HKD':money(pool_q*(p-sub)),'net_before_floor_HKD':money(pool_q*(p-sub)-fee),'distributable_net_HKD':money(max(D(0),pool_q*(p-sub)-fee))} for p in [price,D('2.15'),sub]], 'wrong_above_only':{'rights':distributions[2]['unsubscribed_rights'],'correct_paid_HKD':distributions[2]['paid_HKD'],'wrong_deduct_threshold_HKD':money(D(distributions[2]['raw_allocation_HKD'])-threshold)}}, {'pool_arithmetic':pool_q*price-pool_q*sub-fee==net,'threshold_equal_no_payment':distributions[1]['paid_HKD']=='0.00','threshold_above_full_payment':distributions[2]['paid_HKD']=='50.20','zero_premium_zero_net':max(D(0),pool_q*(sub-sub)-fee)==0}) assert len(out)==11 and set(out)=={f'DJ3-{n:03d}' for n in range(1,12)} return out def main(): target=Path(__file__).resolve().with_suffix('.json') target.write_text(json.dumps(calculate(),ensure_ascii=False,indent=2)+'\n',encoding='utf-8',newline='\n') print(target.name) if __name__=='__main__': main()