PREP session

This commit is contained in:
ialbert 2024-10-01 13:09:20 +03:00
parent fbc0836c07
commit bfb6cb7380
5 changed files with 477 additions and 3 deletions

View file

@ -0,0 +1,131 @@
package ru.spcex.clearing.session.stage;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import ru.clearing.classes.statics.data.execution.ExecutionCommon;
import ru.clearing.classes.statics.data.execution.ExecutionCurrency;
import ru.clearing.classes.statics.data.misc.Session;
import ru.spcex.clearing.imdg.IMDGDistributedNames;
import ru.spcex.clearing.platform.messaging.domain.BaseRequest;
import ru.spcex.clearing.session.stage.impl.BalanceRevise;
import ru.spcex.clearing.session.stage.impl.DealsPrepare;
import ru.spcex.clearing.session.stage.impl.ObligationAdmission;
import ru.spcex.clearing.session.stage.impl.RequirementsAndObligationCreation;
import ru.spcex.clearing.session.stage.task.DealsPreparePayload;
import ru.spcex.clearing.session.stage.task.RequirementsAndObligationCreationPayload;
import ru.spcex.platform.classes.base.interfaces.ExecutionType;
import ru.spcex.platform.enumeration.Section;
import ru.spcex.platform.enumeration.SessionStatus;
import ru.spcex.platform.enumeration.SessionType;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgProvider;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicateBuilder;
import ru.spcex.platform.utils.enumeration.IMessageResolver;
@Service
public class PrepSession extends AbstractSession implements InitializingBean {
private final Logger log = LoggerFactory.getLogger(getClass());
private final DealsPrepare dealsPrepare;
private final RequirementsAndObligationCreation requirementsAndObligationCreation;
private final ObligationAdmission obligationsAdmission;
private final Imdg<ExecutionCurrency> executionCurrencyImdg;
private final Supplier<List<String>> marketCodes;
public PrepSession(
ImdgProvider imdgProvider,
BalanceRevise balanceRevise,
DealsPrepare dealsPrepare,
RequirementsAndObligationCreation requirementsAndObligationCreation,
IMessageResolver messageResolver, ObligationAdmission obligationsAdmission,
@Qualifier("marketCodesForCurr") Supplier<List<String>> marketCodes) {
super(imdgProvider, messageResolver);
this.dealsPrepare = dealsPrepare;
this.requirementsAndObligationCreation = requirementsAndObligationCreation;
this.obligationsAdmission = obligationsAdmission;
this.executionCurrencyImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_ExecutionCurrency, ExecutionCurrency.class);
this.marketCodes = marketCodes;
}
@Override
public void afterPropertiesSet() throws Exception {
dealsPrepare.searchForExecutions(ExecutionType.ExecutionCurrency);
ImdgPredicateBuilder execFondPb = executionCurrencyImdg.predicateBuilder();
dealsPrepare.addExecutionCurrencyCondition(execFondPb.regex("settlementCode", "^T0.*$"));
dealsPrepare.addExecutionCurrencyCondition(execFondPb.in("market", marketCodes.get().toArray(new String[0])));
requirementsAndObligationCreation.setSessionType(sessionType());
imdgProvider.waitAvailable();
initSessionIfPresent();
}
@Override
public void runSession(BaseRequest<?> req) {
if (!startSession()) {
return;
}
log.info("session is running, stage {}", currStage.get());
try {
//stage 1
StageResult<List<ExecutionCommon>> dealsPreparationResult;
{
DealsPreparePayload payload = new DealsPreparePayload();
payload.setSessionId(currSession.getId());
dealsPreparationResult = runStage(TaskType.DealsPrepare, payload, dealsPrepare);
}
{
//stage 2
RequirementsAndObligationCreationPayload payload = new RequirementsAndObligationCreationPayload(
dealsPreparationResult.getStageResult(), currSession.getId()
);
runStage(TaskType.RequirementsAndObligationsCreate, payload, requirementsAndObligationCreation);
}
//stage 3
runStage(TaskType.ObligationsAdmission, currSession.getId(), obligationsAdmission);
endSession();
} catch (StageException e) {
//already logged
}
}
private boolean startSession() {
synchronized (this.currStage) {
if (this.currStage.get() != null) {
log.info("already running session.id={}", this.currSession.getId());
return false;
} else {
TaskType startStatus = TaskType.StartRevise;
Session newSession = new Session();
newSession.setSection(section().getKey());
newSession.setSessionType(sessionType().getKey());
newSession.setSessionStatus(startStatus.getKey());
newSession.setWorkflowStatus(SessionStatus.ACTV.getKey());
newSession.setClearingDate(LocalDate.now());
newSession.setCreated(Instant.now());
//todo companyId/securityId/userId передается из сообщения очереди
sessionImdg.insert(newSession);
currSession = newSession;
log.info("started new session.id={}", this.currSession.getId());
currStage.set(TaskType.StartRevise);
return true;
}
}
}
@Override
protected Section section() {
return Section.CURR;
}
@Override
protected SessionType sessionType() {
return SessionType.PREP;
}
}

View file

@ -37,6 +37,7 @@ public class SessionManager {
private final CurrencySession currencySession;
private final IntermediateMkrSession intermediateMkrSession;
private final FinalMkrSession finalMkrSession;
private final PrepSession prepSession;
private final ReturnDepositSession returnDepositSession;
private final UnitedSession unitedSession;
private final TradingTimeService time;
@ -46,7 +47,7 @@ public class SessionManager {
PrimaryAuctionBnSession primaryAuctionBnSession,
PrimaryAuctionB0Session primaryAuctionB0Session,
SecondaryAuctionT0Session secondaryAuctionT0Session, CurrencySession currencySession,
IntermediateMkrSession intermediateMkrSession, FinalMkrSession finalMkrSession, ReturnDepositSession returnDepositSession, UnitedSession unitedSession, TradingTimeService time) {
IntermediateMkrSession intermediateMkrSession, FinalMkrSession finalMkrSession, PrepSession prepSession, ReturnDepositSession returnDepositSession, UnitedSession unitedSession, TradingTimeService time) {
this.notification = notification;
this.msgs = msgs;
this.primaryAuctionT0Session = primaryAuctionT0Session;
@ -56,6 +57,7 @@ public class SessionManager {
this.currencySession = currencySession;
this.intermediateMkrSession = intermediateMkrSession;
this.finalMkrSession = finalMkrSession;
this.prepSession = prepSession;
this.returnDepositSession = returnDepositSession;
sessionImdg = imdgProvider.getImdg(IMDGDistributedNames.Map_Session, Session.class);
@ -119,6 +121,7 @@ public class SessionManager {
case CURR -> session = currencySession;
case MEDM -> session = intermediateMkrSession;
case FINL -> session = finalMkrSession;
case PREP -> session = prepSession;
case XDEP -> session = returnDepositSession;
case UNIT -> session = unitedSession;
}

View file

@ -41,4 +41,5 @@ public interface IRegistryBuilder {
default IRegistryBuilder dpAccCash(CashV2<Long, DepoAccount> dpAccCash) {return this;}//CashV2<Long, DepoAccount> dpAccCash;
default IRegistryBuilder currCash(CashV2<String, Currency> currCash) {return this;}//CashV2<String, Currency> currCash;
default IRegistryBuilder clrAccCash(CashV2<Long, ClearingAccount> clrAccCash) {return this;}//CashV2<Long, ClearingAccount> clrAccCash;
default IRegistryBuilder ckTcr(TradingClearingRegistry ckTcr) {return this;}
}

View file

@ -0,0 +1,275 @@
package ru.spcex.clearing.session.stage.impl;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Optional;
import ru.clearing.classes.statics.data.account.Account;
import ru.clearing.classes.statics.data.account.ClearingAccount;
import ru.clearing.classes.statics.data.company.Company;
import ru.clearing.classes.statics.data.execution.ExecutionCommon;
import ru.clearing.classes.statics.data.execution.ExecutionCurrency;
import ru.clearing.classes.statics.data.misc.Currency;
import ru.clearing.classes.statics.data.misc.Session;
import ru.clearing.classes.statics.data.registry.Registry;
import ru.clearing.classes.statics.data.registry.TradingClearingRegistry;
import ru.clearing.classes.statics.data.security.CurrencyPairSecurity;
import ru.clearing.platform.dictionary.CurrencyPairDictionary;
import ru.spcex.clearing.component.predicate.cash.account.AccountIdCashingPredicate;
import ru.spcex.clearing.component.predicate.cash.currency.CurrencyCodePredicate;
import ru.spcex.clearing.service.TcrSearcher;
import ru.spcex.clearing.session.stage.util.RegistryUtil;
import ru.spcex.platform.classes.base.interfaces.ExecutionType;
import ru.spcex.platform.enumeration.AccountType;
import ru.spcex.platform.enumeration.BalanceDimension;
import ru.spcex.platform.enumeration.CurrencyCode;
import ru.spcex.platform.enumeration.ISide;
import ru.spcex.platform.enumeration.MoneyFlowSide;
import ru.spcex.platform.enumeration.RegistryCapacity;
import ru.spcex.platform.enumeration.RegistryDesignation;
import ru.spcex.platform.enumeration.RegistryInstrumentType;
import ru.spcex.platform.enumeration.RegistryStatus;
import ru.spcex.platform.enumeration.RegistryUnit;
import ru.spcex.platform.enumeration.Section;
import ru.spcex.platform.enumeration.Sender;
import ru.spcex.platform.enumeration.Side;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.predicate.ImdgPredicate;
import ru.spcex.platform.imdg.iml.hazelcast.adapter.CashV2;
public class RegistryCkBuilder implements IRegistryBuilder {
private CashV2<String, Currency> currCash;
private CashV2<Long, ClearingAccount> clrAccCash;
private Imdg<Company> companyImdg;
private Imdg<TradingClearingRegistry> tradingClearingRegistryImdg;
private Imdg<Session> sessionImdg;
private Imdg<ClearingAccount> clearingAccountImdg;
private Imdg<Currency> currencyImdg;
private Imdg<CurrencyPairSecurity> currPairSecImdg;
private Imdg<CurrencyPairDictionary> currPairDictImdg;
private TcrSearcher tcrSearcher;
private ExecutionCommon exec;
private RegistryDesignation regDsgn;
private TradingClearingRegistry ckTcr;
private RegistryCkBuilder() {
}
public static RegistryCkBuilder builder() {
return new RegistryCkBuilder();
}
public IRegistryBuilder cmpImdg(Imdg<Company> cmpImdg) {
this.companyImdg = cmpImdg;
return this;
}
public IRegistryBuilder tcrImdg(Imdg<TradingClearingRegistry> tcrImdg) {
this.tradingClearingRegistryImdg = tcrImdg;
return this;
}
public IRegistryBuilder ssnImdg(Imdg<Session> ssnImdg) {
this.sessionImdg = ssnImdg;
return this;
}
public IRegistryBuilder clAccImdg(Imdg<ClearingAccount> clAccImdg) {
this.clearingAccountImdg = clAccImdg;
return this;
}
public IRegistryBuilder currPairSecImdg(Imdg<CurrencyPairSecurity> currPairSecImdg) {
this.currPairSecImdg = currPairSecImdg;
return this;
}
public IRegistryBuilder currPairDictImdg(Imdg<CurrencyPairDictionary> currPairDictImdg) {
this.currPairDictImdg = currPairDictImdg;
return this;
}
public IRegistryBuilder currImdg(Imdg<Currency> currImdg) {
this.currencyImdg = currImdg;
return this;
}
public IRegistryBuilder tcrSearcher(TcrSearcher tcrSearcher) {
this.tcrSearcher = tcrSearcher;
return this;
}
@Override
public RegistryCkBuilder exec(ExecutionCommon exec) {
this.exec = exec;
return this;
}
@Override
public RegistryCkBuilder registryDesignation(RegistryDesignation registryDesignation) {
this.regDsgn = registryDesignation;
return this;
}
@Override
public IRegistryBuilder returnDeposit(boolean returnDep) {
if (returnDep) {
throw new IllegalStateException("cannot create second leg registry for ExecutionFond");
}
return this;
}
@Override
public IRegistryBuilder ckTcr(TradingClearingRegistry ckTcr) {
this.ckTcr = ckTcr;
return this;
}
@Override
public Registry build() {
ISide side = getSide(exec);
Registry reg = new Registry();
reg.setCompanyId(Sender.CK.getId());
reg.setCounterPartyId(exec.getCompanyId());
reg.setCreated(Instant.now());
Company company = searchCompany();
reg.setTradingCode(company.getTradingCode());
reg.setClearingCode(company.getClearingCode());
reg.setShortName(company.getShortName());
reg.setFullName(company.getFullName());
TradingClearingRegistry tcr = ckTcr;
String capacityByAccount = null;
//In: execution_currency (SECURITY_SYMBOL: CNYRUB_TOM_C, securityId-->registry.contract)
// C securityId идем в currency_pair_security, чтобы найти currency_pair_id
// C currency_pair_id идем currency_pair_dictionary для базовой base_currency_id [CNY], и обменная в чем торги quote_currency_id [RUB]
//3.1 Для side=Buy
//OM_T = RUB [quote_currency_id] balance=settlement_ammount
//TM_T = CNY [base_currency_id] balance=quantity
//3.2 для side=Sell
//OM_T = CNY [base_currency_id] balance=quantity
//TM_T = RUB [quote_currency_id] balance=settlement_ammount
reg.setContract(String.valueOf(exec.getSecurityId()));
CurrencyPairSecurity curPairSec = currPairSecImdg.getSingleObjectByID(exec.getSecurityId());
CurrencyPairDictionary curPairDict = null;
if (curPairSec != null) {
curPairDict = currPairDictImdg.getSingleObjectByID(curPairSec.getCurrencyPairId());
}
assert curPairSec != null && curPairDict != null;
ExecutionCurrency execCurr = (ExecutionCurrency) this.exec;
//что покупают
String baseCurrency = curPairDict.getBaseCurrency();
//за что покупают
String quoteCurrency = curPairDict.getQuoteCurrency();
reg.setRegistryInstrumentType(RegistryInstrumentType.M.getKey());
reg.setBalanceDimension(BalanceDimension.MONY.getKey());
String curr;
Currency currency;
if ((regDsgn.equals(RegistryDesignation.O) && !side.isBuy()) || (regDsgn.equals(RegistryDesignation.T) && !side.isSell())) {
curr = quoteCurrency;
reg.setBalance(execCurr.getSettlementAmount());
} else if ((regDsgn.equals(RegistryDesignation.T) && !side.isBuy()) || (regDsgn.equals(RegistryDesignation.O) && !side.isSell())) {
curr = baseCurrency;
reg.setBalance(execCurr.getQuantity());
} else curr = CurrencyCode.RUB.getKey();
ImdgPredicate currPrdct = CurrencyCodePredicate
.getPredicate(curr)
.cashed(currencyImdg.predicateBuilder(), currCash);
currency = currencyImdg.getFirstObjectByPredicate(currPrdct);
reg.setSecurityId(currency.getId());
reg.setSecuritySymbol(currency.getCurrencyCode());
Optional<Account> acc = tcrSearcher.searchByTcr(tcr.getId(), curr);
if (acc.isPresent()) {
reg.setAccountId(acc.get().getId());
reg.setAccount(acc.get().getAccount());
reg.setAccountType(acc.get().getAccountType());
capacityByAccount = defineCapacityByAccountType(acc.get().getAccountType(), acc.get().getId());
}
reg.setRegistryDesignation(regDsgn.getKey());
if (capacityByAccount != null) {
reg.setRegistryCapacity(capacityByAccount);
}
reg.setRegistryUnit(RegistryUnit.T.getKey());
reg.setRegistryCode(RegistryUtil.clearingCode(reg));
reg.setTradingClearingRegistryId(tcr.getId());
reg.setTradingClearingRegistry(tcr.getCode());
reg.setRegistryStatus(RegistryStatus.PROC.getKey());
reg.setSettlementDate(execCurr.getSettlementDate());
reg.setValueDate(execCurr.getSettlementDate());
reg.setSettlementCode(execCurr.getSettlementCode());
reg.setTradingDate(exec.getTradingDate());
reg.setClearingDate(LocalDate.now());
reg.setPrice(exec.getPrice());
reg.setCounterPartyId(exec.getCompanyId());
reg.setGroupId(groupId());
reg.setSessionId(exec.getSessionId());
reg.setSessionType(sessionType());
reg.setSection(Section.CURR.getKey());
return reg;
}
private Long groupId() {
// Market market = marketImdg.getFirstObjectBySQL("code = '" + exec.getMarket() + "'");
return exec.getExchangeExecutionId();
}
//можно передать из стейджа
private String sessionType() {
Session session = sessionImdg.getSingleObjectByID(exec.getSessionId());
return session.getSessionType();
}
private Company searchCompany() {
return companyImdg.getSingleObjectByID(Sender.CK.getId());
}
private TradingClearingRegistry searchTradingClearingRegistry() {
return tradingClearingRegistryImdg.getSingleObjectByID(exec.getTradingClearingRegistryId());
}
private static ISide getSide(ExecutionCommon exec) {
if (exec.type().equals(ExecutionType.ExecutionDeposit)) {
return ISide.parse(MoneyFlowSide.class, exec.getSide());
} else if (exec.type().equals(ExecutionType.ExecutionFond)) {
return ISide.parse(Side.class, exec.getSide());
} else if (exec.type().equals(ExecutionType.ExecutionCurrency)) {
return ISide.parse(Side.class, exec.getSide());
} else {
throw new RuntimeException("Unknown Execution type: " + exec.type());
}
}
private String defineCapacityByAccountType(String accountType, Long moneyAccountId) {
String capacityByAccount = null;
if (AccountType.Clrn.equalsByKey(accountType)) {
ImdgPredicate prdct = AccountIdCashingPredicate
.getPredicate(moneyAccountId)
.cashed(clearingAccountImdg.predicateBuilder(), clrAccCash);
ClearingAccount clearingAccount = clearingAccountImdg.getFirstObjectByPredicate(prdct);
if (clearingAccount != null) {
capacityByAccount = clearingAccount.getClearingAccountType();
}
} else if (AccountType.Info.equalsByKey(accountType)) {
capacityByAccount = RegistryCapacity.A.getKey();
}
return capacityByAccount;
}
@Override
public IRegistryBuilder currCash(CashV2<String, Currency> currCash) {
this.currCash = currCash;
return this;
}
@Override
public IRegistryBuilder clrAccCash(CashV2<Long, ClearingAccount> clrAccCash) {
this.clrAccCash = clrAccCash;
return this;
}
}

View file

@ -51,6 +51,8 @@ import ru.spcex.platform.enumeration.RegistryDesignation;
import ru.spcex.platform.enumeration.RegistryInstrumentType;
import ru.spcex.platform.enumeration.RegistryTradingParams;
import ru.spcex.platform.enumeration.RegistryUnit;
import ru.spcex.platform.enumeration.Sender;
import ru.spcex.platform.enumeration.SessionType;
import ru.spcex.platform.enumeration.Side;
import ru.spcex.platform.imdg.api.Imdg;
import ru.spcex.platform.imdg.api.ImdgId;
@ -65,6 +67,7 @@ import ru.spcex.platform.imdg.iml.hazelcast.adapter.CashV2ById;
import ru.spcex.platform.imdg.iml.hazelcast.adapter.CashV2ByIdAndString;
import ru.spcex.platform.imdg.iml.hazelcast.adapter.CashV2ByString;
import ru.spcex.platform.utils.collection.Pair;
import ru.spcex.platform.utils.enumeration.IEnumKey;
/**
* LiabilitiesAndClaims
@ -92,7 +95,12 @@ public class RequirementsAndObligationCreation implements ISessionStage {
private final Imdg<Currency> currImdg;
private final TcrSearcher tcrSearcher;
private final SecuritySelector<Security> secSelector;
private SessionType sessionType;
private TradingClearingRegistry ckTcr;
public void setSessionType(SessionType sessionType) {
this.sessionType = sessionType;
}
@Autowired
public RequirementsAndObligationCreation(ImdgProvider imdgProvider) {
@ -193,6 +201,7 @@ public class RequirementsAndObligationCreation implements ISessionStage {
}
protected StageResult<?> createRegisters(List<ExecutionCommon> data, Long sessionId, Map<Long, Registry> rgsStorage) {
this.ckTcr = null;
data.sort(tradeTimeComparator.thenComparing(execIdComparator));
Map<Long, Registry> newRgss = rgsStorage != null ? rgsStorage : storageMap(data);
//см. описание к #matchExecutions
@ -266,6 +275,12 @@ public class RequirementsAndObligationCreation implements ISessionStage {
findAndUpdateOrCreate.accept(counterExec, RegistryDesignation.T, true);
findAndUpdateOrCreate.accept(counterExec, RegistryDesignation.O, true);
}
if (isCk()) {
createRegistryForCK(partyExec, RegistryDesignation.O, newRgss);
createRegistryForCK(partyExec, RegistryDesignation.T, newRgss);
createRegistryForCK(counterExec, RegistryDesignation.O, newRgss);
createRegistryForCK(counterExec, RegistryDesignation.T, newRgss);
}
}
if (rgsStorage == null) {
log.info("batch insert {} registries into map", newRgss.size());
@ -274,6 +289,48 @@ public class RequirementsAndObligationCreation implements ISessionStage {
return new StageResult<>(null, true);
}
private boolean isCk() {
return IEnumKey.contains(sessionType, SessionType.PREP, SessionType.PAYM);
}
private void createRegistryForCK(
ExecutionCommon exec,
RegistryDesignation rgsDsgn,
Map<Long, Registry> newRgss
) {
if (this.ckTcr == null) {
this.ckTcr = tcrImdg.getSingleObjectByPredicate(
tcrImdg.predicateBuilder().equals("companyId", Sender.CK.getId())
);
}
IRegistryBuilder registryBuilder = RegistryCkBuilder.builder();
Registry newRegister = registryBuilder
//cashes
.dpAccCash(cash.dpAccCash)
.currCash(cash.currCash)
.clrAccCash(cash.clrAccCash)
//imdg
.cmpImdg(cmpImdg)
.tcrImdg(tcrImdg)
.accImdg(accImdg)
.ssnImdg(ssnImdg)
.clAccImdg(clAccImdg)
.dpAccImdg(dpAccImdg)
.currPairSecImdg(currPairSecImdg)
.currPairDictImdg(currPairDictImdg)
.currImdg(currImdg)
.tcrSearcher(tcrSearcher)
.secSelector(secSelector)
.imdg(imdgProvider)
.ckTcr(ckTcr)
.exec(exec)
.registryDesignation(rgsDsgn)
.returnDeposit(false)
.build();
newRegister.setId(idGen.nextId());
newRgss.put(newRegister.getId(), newRegister);
log.debug("execution id {}: created rgs.id={}", exec, newRegister.getId());
}
private Optional<Registry> findReg(ExecutionCommon exec, RegistryDesignation des) {
ISide side = getSide(exec);
@ -390,9 +447,16 @@ public class RequirementsAndObligationCreation implements ISessionStage {
invalid = String.format("exchangeExecutionId %d and %d not equal", exec1.getExchangeExecutionId(), exec2.getExchangeExecutionId());
} else if (Objects.equals(getSide(exec1), getSide(exec2))) {
invalid = String.format("side %s and %s equal, must be opposite", getSide(exec1), getSide(exec1));
} else if (!Objects.equals(exec1.getCompanyId(), exec2.getCounterPartyId()) || !Objects.equals(exec1.getCounterPartyId(), exec2.getCompanyId())) {
} else if (!isCk()
&& (!Objects.equals(exec1.getCompanyId(), exec2.getCounterPartyId())
|| !Objects.equals(exec1.getCounterPartyId(), exec2.getCompanyId()))) {
invalid = String.format("Execution#id(%d)#companyId(%d)#counterPartyId(%d), Execution#id(%d)#companyId(%d)#counterPartyId(%d)",
exec1.getId(), exec1.getCompanyId(), exec1.getCounterPartyId(), exec2.getId(), exec2.getCompanyId(), exec2.getCounterPartyId());
exec1.getId(), exec1.getCompanyId(), exec1.getCounterPartyId(), exec2.getId(), exec2.getCompanyId(), exec2.getCounterPartyId());
} else if (isCk()
&& !Objects.equals(exec1.getTradingDate(), exec2.getTradingDate())) {
invalid = String.format("[Execution#id(%d)#tradingDate=%s, Execution#id(%d)#tradingDate=%s] exchangeExecutionId=%d; tradingDate must be equal",
exec1.getId(), exec1.getTradingDate(),
exec2.getId(), exec2.getTradingDate(), exec2.getExchangeExecutionId());
}
if (invalid != null) {
log.error("FATAL couldn't match Execution#id({}) with Execution#id({}) error: {}", exec1.getId(), exec2.getId(), invalid);