Começar pela obrigação
A aula usa uma instrução fictícia de 1500 unidades e 450001 cêntimos. O primeiro passo consiste em identificar serviço, conta, instrumento, referência da instrução e moeda. A mesma referência curta pode existir noutro âmbito; não basta procurar o primeiro registo com esse texto. Escreve numa folha o total original, o que está confirmado e o que falta explicar. Este registo serve ao L3 para delimitar o incidente e ao gestor para comunicar impacto. Nenhum campo do exercício representa uma instrução enviada a uma infraestrutura financeira real. São entradas controladas para praticar interpretação e reconciliação.
Separar parcelas e residuais
A primeira confirmação do exercício contém 900 unidades e 270000 cêntimos. Restam 600 unidades e 180001 cêntimos. O cêntimo adicional deve continuar visível, mesmo que o painel apresente euros sem casas decimais. A explicação pública do BCE sobre liquidação parcial distingue a parcela executada e o remanescente; o exercício usa os seus próprios números. Não implementa o algoritmo de seleção, janelas, mínimos ou regras de arredondamento de T2S. Recebe montantes já confirmados e verifica a conservação dos totais. Prever o residual antes de executar o código ajuda a detetar um erro de mapeamento que uma mensagem de sucesso não revelaria.
Pedido não é resultado
Enviar cancelamento não permite passar automaticamente as 600 unidades para canceladas. No fluxo bilateral consultado das GFS, uma dependência da contraparte pode manter o pedido pendente. Lê o estado da manutenção e o da instrução a que se refere, conservando as referências. No contrato local, só cancel-confirm transfere o remanescente para a parcela cancelada; as 900 unidades já liquidadas permanecem no histórico. A igualdade original = liquidado + cancelado + pendente deve continuar verdadeira. Se uma confirmação tardia entrar depois, a recusa do modelo exige reconciliação; não prova que o movimento externo seja inválido.
Interpretar dimensões de estado
O caso contém um snapshot completo com matched e dois holds. O seguinte remove PARTY, mas conserva CSD. O consumidor deve representar o hold restante. A orientação do BCE sobre múltiplos estados ajuda a perceber por que um único rótulo pronto pode esconder restrições. O programa não decide se uma infraestrutura pode liquidar: contabiliza observações e apresenta um sinal local de acompanhamento. Uma confirmação pode chegar depois de um estado que refletia outro instante. Para investigar, separa momento de origem, receção e processamento. Não elimines um movimento só porque o painel atual apresenta um hold cuja cronologia ainda não foi reconciliada.
Repetir sem duplicar efeitos
Copia o programa para uma pasta temporária e executa python3 run.py --output evidence.json com Python 3.13. O modelo reduz repetições idênticas por source/id e rejeita alterações de conteúdo com a mesma identidade. Também verifica movementRef: um novo message id não transforma um movimento antigo num segundo movimento. A função trabalha sobre cópias e deixa as entradas intactas perante falha. Esta propriedade em memória não demonstra persistência, transação distribuída ou recuperação depois de crash. Experimenta primeiro duplicar uma mensagem; depois muda apenas o seu id, mantendo a referência económica. Explica por que os dois resultados diferem.
Entregar uma conclusão verificável
Reserva quinze minutos para prever as parcelas, quinze para executar os casos e quinze para introduzir conflitos. Nos quinze finais, produz uma nota curta com obrigação original, parcelas confirmadas, residual, referências, incertezas, responsável e próxima atualização. Outra pessoa deve conseguir repetir a conta sem perguntar qual ficheiro escolheste. Confirma a versão aplicável ao ambiente: o índice consultado apresenta documentos R2026.NOV publicados em setembro e R2026.JUN como anteriores, o que não demonstra a ativação do teu ambiente. A conclusão do laboratório limita-se às regras locais e aos casos executados. A aceitação real exige contratos, sistemas e participantes autorizados.
"""Original DR post-trade observation exercise, Python 3.13.
Controlled inputs only; no T2S/DTCC messages, banking ledger or execution.
python3 run.py --output evidence.json
"""
import argparse
from copy import deepcopy
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import sys
checks=[]
def check(name,actual,expected):
assert actual==expected,(name,actual,expected)
checks.append(dict(name=name,actual=actual,expected=expected,passed=True))
def reject(fn):
try:fn()
except ValueError as e:return str(e)
raise AssertionError('Expected rejection')
def positive(value):
if type(value)is not int or value<=0:raise ValueError('positive integer required')
return value
def unique(events):
seen={};out=[]
for e in events:
key=(e['source'],e['id'])
if key in seen:
if seen[key]!=e:raise ValueError('message identity conflict')
continue
seen[key]=deepcopy(e);out.append(deepcopy(e))
return out
def settlement(instruction,events):
total=positive(instruction['quantity']);amount=positive(instruction['cashCents'])
out=dict(original=total,settled=0,cancelled=0,pending=total,cashSettled=0,cashCancelled=0,cashPending=amount,matched=False,holds=[],cancelRequested=False,cancelConfirmed=False,movements=[])
seen=set()
for e in unique(events):
if e['scope']!=instruction['scope']:raise ValueError('instruction scope mismatch')
kind=e['kind']
if kind=='status':
if type(e['matched'])is not bool or not isinstance(e['holds'],list):raise ValueError('invalid status snapshot')
out['matched']=e['matched'];out['holds']=sorted(set(e['holds']))
elif kind=='cancel-request':out['cancelRequested']=True
elif kind=='cancel-confirm':
if out['cancelConfirmed']:raise ValueError('duplicate cancellation outcome')
out['cancelConfirmed']=True;out['cancelRequested']=False
out['cancelled']=out['pending'];out['cashCancelled']=out['cashPending']
out['pending']=0;out['cashPending']=0
elif kind=='settlement':
if out['cancelConfirmed']:raise ValueError('settlement after cancellation needs reconciliation')
if e['movementRef']in seen:raise ValueError('duplicate movement reference')
qty=positive(e['quantity']);cash=positive(e['cashCents'])
if qty>out['pending'] or cash>out['cashPending']:raise ValueError('exceeds remaining obligation')
if (qty==out['pending'])!=(cash==out['cashPending']):raise ValueError('final quantity cash mismatch')
seen.add(e['movementRef']);out['movements'].append(e['movementRef'])
out['settled']+=qty;out['pending']-=qty;out['cashSettled']+=cash;out['cashPending']-=cash
else:raise ValueError('unknown settlement observation')
# Diagnostic local flag only; it does not command or permit settlement.
out['localFollowUp']=out['pending']>0 and (bool(out['holds'])or not out['matched']or out['cancelRequested'])
return out
def corporate(scope,events):
out=dict(noticeRevision=None,preAdvice={},credits={},reversed=[],adjustments={},net=0)
effect_refs=set()
for e in unique(events):
if e['scope']!=scope:raise ValueError('corporate scope mismatch')
kind=e['kind']
if kind=='notice':
revision=positive(e['revision'])
if out['noticeRevision'] is not None and revision<=out['noticeRevision']:raise ValueError('non increasing local notice revision')
out['noticeRevision']=revision
elif kind=='preadvice':out['preAdvice'][e['id']]=positive(e['amount'])
elif kind=='cancel-preadvice':
if e['ref']not in out['preAdvice']:raise ValueError('missing preadvice reference')
del out['preAdvice'][e['ref']]
elif kind in('credit','reversal','adjustment'):
if e['effectRef']in effect_refs:raise ValueError('duplicate economic reference')
if kind=='credit':
value=positive(e['amount']);out['credits'][e['effectRef']]=value;out['net']+=value
else:
parent=e['ref']
if parent not in out['credits']:raise ValueError('missing original credit')
if parent in out['reversed']:raise ValueError('original credit already reversed')
if kind=='reversal':
value=positive(e['amount'])
if value!=out['credits'][parent]:raise ValueError('reversal amount mismatch')
if any(a['ref']==parent for a in out['adjustments'].values()):raise ValueError('adjusted credit requires separate reconciliation')
out['reversed'].append(parent);out['net']-=value
else:
value=e['amount']
if type(value)is not int or value==0:raise ValueError('nonzero integer adjustment required')
if out['net']+value<0:raise ValueError('outside nonnegative fixture scope')
out['adjustments'][e['effectRef']]=dict(ref=parent,amount=value);out['net']+=value
effect_refs.add(e['effectRef'])
else:raise ValueError('unknown corporate observation')
return out
def deadline(received,limit):
a=datetime.fromisoformat(received);b=datetime.fromisoformat(limit)
if a.utcoffset()is None or b.utcoffset()is None:raise ValueError('explicit UTC offset required')
return (a.astimezone(timezone.utc)<=b.astimezone(timezone.utc))
def main():
sc=['DR-CSD','ACCOUNT-A','INSTRUMENT-X','INSTRUCTION-1','EUR']
instruction=dict(scope=sc,quantity=1500,cashCents=450001)
def se(id,kind,**extra):return dict(source='DR-OBS',id=id,scope=sc,kind=kind,**extra)
status=se('s1','status',matched=True,holds=['CSD','PARTY'])
part=se('s2','settlement',movementRef='M1',quantity=900,cashCents=270000)
req=se('s3','cancel-request')
events=[status,part,req];saved=deepcopy(events);a=settlement(instruction,events)
check('partial quantity remains',a['pending'],600)
check('partial cash remainder preserves cent',a['cashPending'],180001)
check('request preserves settled quantity',a['settled'],900)
check('request does not cancel remainder',a['cancelled'],0)
check('request remains pending',a['cancelRequested'],True)
check('matching does not erase holds',a['holds'],['CSD','PARTY'])
check('partial request needs follow up',a['localFollowUp'],True)
release=se('s4','status',matched=True,holds=['CSD'])
check('one hold release retains another',settlement(instruction,[status,release])['holds'],['CSD'])
b=settlement(instruction,events+[se('s5','cancel-confirm')])
check('confirmed cancellation retains settled quantity',b['settled'],900)
check('confirmed cancellation closes remaining quantity',b['cancelled'],600)
check('confirmed cancellation retains cash remainder separately',b['cashCancelled'],180001)
check('quantity conservation after cancellation',b['settled']+b['cancelled']+b['pending'],1500)
check('cash conservation after cancellation',b['cashSettled']+b['cashCancelled']+b['cashPending'],450001)
final=se('s6','settlement',movementRef='M2',quantity=600,cashCents=180001)
complete=settlement(instruction,[part,final])
check('final movement consumes residual cent',complete['cashSettled'],450001)
check('complete quantity has no pending remainder',complete['pending'],0)
check('identical message replay unchanged',settlement(instruction,[part,part]),settlement(instruction,[part]))
check('changed payload with same identity rejected',reject(lambda:settlement(instruction,[part,{**part,'quantity':800}])),'message identity conflict')
check('new message cannot reuse movement reference',reject(lambda:settlement(instruction,[part,{**part,'id':'new'}])),'duplicate movement reference')
check('excess quantity rejected',reject(lambda:settlement(instruction,[{**part,'quantity':1501}])),'exceeds remaining obligation')
check('final quantity with incomplete cash rejected',reject(lambda:settlement(instruction,[part,{**final,'cashCents':180000}])),'final quantity cash mismatch')
check('wrong account scope rejected',reject(lambda:settlement(instruction,[{**part,'scope':['OTHER']}])),'instruction scope mismatch')
check('late settlement after cancel held for reconciliation',reject(lambda:settlement(instruction,events+[se('s5','cancel-confirm'),final])),'settlement after cancellation needs reconciliation')
check('input history preserved',events==saved,True)
ca=['DR-CA','EVENT-1','ACCOUNT-A','INSTRUMENT-X','CASH','OPTION-1','EUR']
def ce(id,kind,**extra):return dict(source='DR-OBS',id=id,scope=ca,kind=kind,**extra)
notice=ce('a1','notice',revision=1);pre=ce('a2','preadvice',amount=7200)
credit=ce('a3','credit',effectRef='C1',amount=7200)
reversal=ce('a4','reversal',effectRef='R1',ref='C1',amount=7200)
replacement=ce('a5','credit',effectRef='C2',amount=7500)
check('announcement does not book cash',corporate(ca,[notice])['net'],0)
check('preadvice does not book cash',corporate(ca,[notice,pre])['net'],0)
check('cancel preadvice keeps net unchanged',corporate(ca,[pre,ce('a6','cancel-preadvice',ref='a2')])['net'],0)
check('confirmed credit books amount',corporate(ca,[credit])['net'],7200)
check('reversal removes original credit effect',corporate(ca,[credit,reversal])['net'],0)
corrected=corporate(ca,[notice,pre,credit,ce('a7','notice',revision=2),reversal,replacement])
check('replacement follows reversal without double count',corrected['net'],7500)
check('original credit retained in history',corrected['credits']['C1'],7200)
check('replayed credit counts once',corporate(ca,[credit,credit])['net'],7200)
check('reversal before original is held',reject(lambda:corporate(ca,[reversal])),'missing original credit')
check('mismatched reversal amount rejected',reject(lambda:corporate(ca,[credit,{**reversal,'amount':7100}])),'reversal amount mismatch')
check('second reversal with new id rejected',reject(lambda:corporate(ca,[credit,reversal,{**reversal,'id':'a8','effectRef':'R2'}])),'original credit already reversed')
check('same effect with new message rejected',reject(lambda:corporate(ca,[credit,{**credit,'id':'a9'}])),'duplicate economic reference')
adj=ce('a10','adjustment',effectRef='D1',ref='C1',amount=-300)
check('delta adjustment does not repay full value',corporate(ca,[credit,adj])['net'],6900)
check('adjustment retains original credit',corporate(ca,[credit,adj])['credits']['C1'],7200)
check('wrong corporate scope rejected',reject(lambda:corporate(ca,[{**credit,'scope':['OTHER']}])),'corporate scope mismatch')
check('old notice revision does not overwrite new',reject(lambda:corporate(ca,[ce('a11','notice',revision=2),notice])),'non increasing local notice revision')
check('offsets identify same deadline instant',deadline('2026-10-04T16:00:00+01:00','2026-10-04T15:00:00+00:00'),True)
check('one second past deadline rejected locally',deadline('2026-10-04T16:00:01+01:00','2026-10-04T15:00:00+00:00'),False)
check('naive time cannot prove deadline',reject(lambda:deadline('2026-10-04T16:00:00','2026-10-04T15:00:00+00:00')),'explicit UTC offset required')
check('equal wall clocks can be different instants',deadline('2026-10-04T16:00:00+00:00','2026-10-04T16:00:00+01:00'),False)
check('fractional local quantity rejected',reject(lambda:settlement(instruction,[{**part,'quantity':900.5}])),'positive integer required')
check('reversal after adjustment needs separate reconciliation',reject(lambda:corporate(ca,[credit,adj,reversal])),'adjusted credit requires separate reconciliation')
out=dict(scope='Original local observation reducers only; no T2S/DTCC protocol, settlement execution, bank ledger, legal calendar, actual instruction or independent acceptance.',python=sys.version.split()[0],runnerSha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),passed=len(checks),checks=checks)
parser=argparse.ArgumentParser();parser.add_argument('--output',required=True);Path(parser.parse_args().output).write_text(json.dumps(out,indent=2)+'\n');print(json.dumps({'passed':len(checks)}))
if __name__=='__main__':main()
Caso fictício: 900 unidades liquidadas, 600 pendentes e um cancelamento pedido. O painel encerra a instrução cedo demais; a reconciliação conserva as parcelas e o residual de 180001 cêntimos.
Armadilhas comuns
Somar snapshots como movimentos; cancelar parcelas já liquidadas; perder residuais; interpretar matched como liquidação; confundir rejeição local com invalidade externa.
Tópicos relacionados: Sessões FIX e estado das ordens · Pós-negociação e liquidação · Operação, mudança e preparação T+1
Conservar a obrigação original, as parcelas e a incerteza permite explicar o resultado e decidir o próximo passo sem fabricar confirmações.
Referência: T2S General Functional Specifications R2026.JUN · DR financial markets technology professional assessment2026.10