etreschenkov 2026-02-03 08:39:59 +03:00
parent 92b3705423
commit d06ec59dea
10 changed files with 489 additions and 9 deletions

View file

@ -3,6 +3,7 @@ package ru.spcex.clearing.gatewayapi.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import ru.spcex.clearing.gatewayapi.enums.CurrencySpecificationFormat;
import ru.spcex.clearing.platform.messaging.config.element.KafkaConsumerSettings;
import ru.spcex.clearing.platform.messaging.config.element.KafkaProducerSettings;
import ru.spcex.platform.imdg.iml.hazelcast.config.HazelcastClientParams;
@ -17,6 +18,7 @@ public class GatewayApiSettings {
private InboundServerSettings inboundServer;
private InboundExternalServerSettings inboundExternalServer;
private String clearingSystem;
private CurrencySpecificationFormat loadCurrencySpecificationFormat;
public HazelcastClientParams getHazelcast() {
return hazelcast;
@ -65,4 +67,12 @@ public class GatewayApiSettings {
public void setClearingSystem(String clearingSystem) {
this.clearingSystem = clearingSystem;
}
public CurrencySpecificationFormat getLoadCurrencySpecificationFormat() {
return loadCurrencySpecificationFormat;
}
public void setLoadCurrencySpecificationFormat(CurrencySpecificationFormat loadCurrencySpecificationFormat) {
this.loadCurrencySpecificationFormat = loadCurrencySpecificationFormat;
}
}

View file

@ -0,0 +1,21 @@
package ru.spcex.clearing.gatewayapi.config.deserializers;
import java.util.Arrays;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import ru.spcex.clearing.gatewayapi.enums.CurrencySpecificationFormat;
@Component
@ConfigurationPropertiesBinding
public class CurrencySpecificationFormatConverter implements Converter<String, CurrencySpecificationFormat> {
@Override
public CurrencySpecificationFormat convert(String source) {
String normalized = source.trim();
return Arrays.stream(CurrencySpecificationFormat.values())
.filter(v -> v.name().equalsIgnoreCase(normalized))
.findFirst()
.orElse(CurrencySpecificationFormat.Massive);
}
}

View file

@ -1,5 +1,8 @@
package ru.spcex.clearing.gatewayapi.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
@ -20,9 +23,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import ru.spcex.clearing.gatewayapi.config.GatewayApiSettings;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.CommonRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.company.CompaniesRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.limit.LimitRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrListingsMassiveRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrListingsRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.fond.FondDigitalListingsRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.fond.FondListingsRequest;
@ -30,11 +35,13 @@ import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.mkr.MMLis
import ru.spcex.clearing.gatewayapi.controller.inbound.request.operations.OperationsRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.tkr.request.TkrRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.response.CommonResponse;
import ru.spcex.clearing.gatewayapi.enums.CurrencySpecificationFormat;
import ru.spcex.clearing.gatewayapi.exception.GatewayException;
import ru.spcex.clearing.gatewayapi.service.NotificationService;
import ru.spcex.clearing.gatewayapi.service.OperationService;
import ru.spcex.clearing.gatewayapi.service.TkrService;
import ru.spcex.clearing.gatewayapi.service.processor.CompanyProcessor;
import ru.spcex.clearing.gatewayapi.service.processor.ListingCurrMassiveProcessor;
import ru.spcex.clearing.gatewayapi.service.processor.ListingCurrProcessor;
import ru.spcex.clearing.gatewayapi.service.processor.ListingDigitalFondProcessor;
import ru.spcex.clearing.gatewayapi.service.processor.ListingFondProcessor;
@ -54,10 +61,13 @@ public class GatewayController {
private final ListingDigitalFondProcessor listingDigitalFondProcessor;
private final ListingMMProcessor listingMMProcessor;
private final ListingCurrProcessor listingCurrProcessor;
private final ListingCurrMassiveProcessor listingCurrMassiveProcessor;
private final ExecutorService executor;
private final OperationService operationService;
private final NotificationService notificationService;
private final TkrService tkrService;
private final GatewayApiSettings gatewayApiSettings;
private final ObjectMapper objectMapper;
private List<String> validTypes = Arrays.asList("DAY_START", "ON_DEMAND");
@ -67,20 +77,26 @@ public class GatewayController {
ListingDigitalFondProcessor listingDigitalFondProcessor,
ListingMMProcessor listingMMProcessor,
ListingCurrProcessor listingCurrProcessor,
ListingCurrMassiveProcessor listingCurrMassiveProcessor,
@Qualifier("gatewayExecutor") ExecutorService executor,
OperationService operationService,
NotificationService notificationService,
TkrService tkrService) {
TkrService tkrService,
GatewayApiSettings gatewayApiSettings,
ObjectMapper objectMapper) {
this.messageResolver = messageResolver;
this.companiesProcessor = companiesProcessor;
this.listingFondProcessor = listingFondProcessor;
this.listingDigitalFondProcessor = listingDigitalFondProcessor;
this.listingMMProcessor = listingMMProcessor;
this.listingCurrProcessor = listingCurrProcessor;
this.listingCurrMassiveProcessor = listingCurrMassiveProcessor;
this.executor = executor;
this.operationService = operationService;
this.notificationService = notificationService;
this.tkrService = tkrService;
this.gatewayApiSettings = gatewayApiSettings;
this.objectMapper = objectMapper;
}
@ -168,11 +184,19 @@ public class GatewayController {
produces = MediaType.APPLICATION_JSON_VALUE
)
@ResponseBody
public CommonResponse listingsCurr(@RequestBody CurrListingsRequest request) {
// request.validate(validTypes, List.of(Section.CURR));
public CommonResponse listingsCurr(@RequestBody JsonNode jsonRequest) throws JsonProcessingException {
CommonRequest request;
Runnable task;
if (CurrencySpecificationFormat.Massive == gatewayApiSettings.getLoadCurrencySpecificationFormat()) {
request = objectMapper.treeToValue(jsonRequest, CurrListingsMassiveRequest.class);
task = () -> listingCurrMassiveProcessor.process((CurrListingsMassiveRequest) request);
} else {
request = objectMapper.treeToValue(jsonRequest, CurrListingsRequest.class);
task = () -> listingCurrProcessor.process((CurrListingsRequest) request);
}
executor.submit(() -> {
try {
listingCurrProcessor.process(request);
task.run();
} catch (Throwable e) {
log.error("At process /listing_curr has error: {}", ExceptionUtils.getStackTrace(e));
}

View file

@ -0,0 +1,33 @@
package ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import java.util.ArrayList;
import java.util.List;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.CommonRequest;
public class CurrListingsMassiveRequest extends CommonRequest {
@JsonProperty("currency_specification")
@ApiModelProperty(value = "Список инструментов")
private List<CurrencyInstrument> currencyInstrumentList = new ArrayList<>();
@JsonProperty("trading_mode")
@ApiModelProperty(value = "Список инструментов")
private List<TradingMode> tradingModeList = new ArrayList<>();
public List<CurrencyInstrument> getCurrencyInstrumentList() {
return currencyInstrumentList;
}
public void setCurrencyInstrumentList(List<CurrencyInstrument> currencyInstrumentList) {
this.currencyInstrumentList = currencyInstrumentList;
}
public List<TradingMode> getTradingModeList() {
return tradingModeList;
}
public void setTradingModeList(List<TradingMode> tradingModeList) {
this.tradingModeList = tradingModeList;
}
}

View file

@ -0,0 +1,260 @@
package ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.UUID;
import ru.spcex.clearing.gatewayapi.config.deserializers.LocalDateDeserializer;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.WithMapId;
public class TradingMode extends WithMapId {
@JsonProperty("id")
@ApiModelProperty(
value = "id",
example = "28406b73-7eab-4e21-9295-2ae8ee5c4eb5"
)
private UUID id;
@JsonProperty("currency_spec_id")
@ApiModelProperty(
value = "currency_spec_id",
example = "28406b73-7eab-4e21-9295-2ae8ee5c4eb5"
)
private UUID currencySpecId;
@JsonProperty("symbol_code")
@ApiModelProperty(
value = "symbol_code",
example = "CNYRUB_TOD_N"
)
private String symbolCode;
@JsonProperty("trading_code")
@ApiModelProperty(
value = "symbol_code",
example = "NVAD"
)
private String tradingCode;
@JsonProperty("trading_name")
@ApiModelProperty(
value = "trading_name",
example = "Режим адресных сделок без клиринга"
)
private String tradingName;
@JsonProperty("lot_currency_letter_code")
@ApiModelProperty(
value = "Значение поля «Валюта лота»",
example = "CNY"
)
private String lotCurrencyLetterCode;
@JsonProperty("matched_currency_letter_code")
@ApiModelProperty(
value = "Значение поля «Сопряженная валюта»",
example = "RUB"
)
private String matchedCurrencyLetterCode;
@JsonProperty("symbol_trading_mode")
@ApiModelProperty(
value = """
Тип заявок (адресные/безадресные) - данные из системного справочника.
Y адресные
N безадресные
""",
example = "N"
)
private String symbolTradingMode;
@JsonProperty("settle_code")
@ApiModelProperty(
value = "Код (условия) расчетов: заполняется значением согласно справочнику «Код сектора в клиринговой системе»",
example = "T0"
)
private String settleCode;
@JsonProperty("min_step")
@ApiModelProperty(
value = "Шаг изменения цены, значение из listing.dir_price_min_step_precision.min_step",
example = "1"
)
private BigDecimal minStep;
@JsonProperty("precision")
@ApiModelProperty(
value = "Точность цены, значение из listing.dir_price_min_step_precision.precision, соответствующее значению из listing.dir_price_min_step_precision.min_step",
example = "2"
)
private BigDecimal precision;
@JsonProperty("lot_size")
@ApiModelProperty(
value = "Размер лота",
example = "1"
)
private BigDecimal lotSize;
@JsonProperty("number_of_lot_currency")
@ApiModelProperty(
value = "Размер лота",
example = "0"
)
private BigDecimal numberOfLotCurrency;
@JsonProperty("sector")
@ApiModelProperty(
value = "Сектор",
example = "CURR"
)
private String sector;
@JsonProperty("start_date")
@JsonDeserialize(using = LocalDateDeserializer.class)
@ApiModelProperty(
value = "Дата начала торгов инструментом",
example = "21.02.2024"
)
private LocalDate startDate;
@JsonProperty("workflow_status")
@ApiModelProperty(
value = """
«ACTV»|«BLKD»
""",
example = "ACTV"
)
private String workflowStatus;
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getCurrencySpecId() {
return currencySpecId;
}
public void setCurrencySpecId(UUID currencySpecId) {
this.currencySpecId = currencySpecId;
}
public String getSymbolCode() {
return symbolCode;
}
public void setSymbolCode(String symbolCode) {
this.symbolCode = symbolCode;
}
public String getTradingCode() {
return tradingCode;
}
public void setTradingCode(String tradingCode) {
this.tradingCode = tradingCode;
}
public String getTradingName() {
return tradingName;
}
public void setTradingName(String tradingName) {
this.tradingName = tradingName;
}
public String getLotCurrencyLetterCode() {
return lotCurrencyLetterCode;
}
public void setLotCurrencyLetterCode(String lotCurrencyLetterCode) {
this.lotCurrencyLetterCode = lotCurrencyLetterCode;
}
public String getMatchedCurrencyLetterCode() {
return matchedCurrencyLetterCode;
}
public void setMatchedCurrencyLetterCode(String matchedCurrencyLetterCode) {
this.matchedCurrencyLetterCode = matchedCurrencyLetterCode;
}
public String getSymbolTradingMode() {
return symbolTradingMode;
}
public void setSymbolTradingMode(String symbolTradingMode) {
this.symbolTradingMode = symbolTradingMode;
}
public String getSettleCode() {
return settleCode;
}
public void setSettleCode(String settleCode) {
this.settleCode = settleCode;
}
public BigDecimal getMinStep() {
return minStep;
}
public void setMinStep(BigDecimal minStep) {
this.minStep = minStep;
}
public BigDecimal getPrecision() {
return precision;
}
public void setPrecision(BigDecimal precision) {
this.precision = precision;
}
public BigDecimal getLotSize() {
return lotSize;
}
public void setLotSize(BigDecimal lotSize) {
this.lotSize = lotSize;
}
public BigDecimal getNumberOfLotCurrency() {
return numberOfLotCurrency;
}
public void setNumberOfLotCurrency(BigDecimal numberOfLotCurrency) {
this.numberOfLotCurrency = numberOfLotCurrency;
}
public String getSector() {
return sector;
}
public void setSector(String sector) {
this.sector = sector;
}
public LocalDate getStartDate() {
return startDate;
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
}
public String getWorkflowStatus() {
return workflowStatus;
}
public void setWorkflowStatus(String workflowStatus) {
this.workflowStatus = workflowStatus;
}
}

View file

@ -0,0 +1,22 @@
package ru.spcex.clearing.gatewayapi.enums;
import ru.spcex.platform.utils.enumeration.IEnumKey;
public enum CurrencySpecificationFormat implements IEnumKey {
Single("single"),
Massive("massive");
private final String name;
CurrencySpecificationFormat(final String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String getKey() {
return name;
}
}

View file

@ -2,6 +2,7 @@ package ru.spcex.clearing.gatewayapi.service.adapter;
import org.springframework.stereotype.Service;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrencyInstrument;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.TradingMode;
import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewRequest;
import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewGatewayRequest;
import ru.spcex.platform.enumeration.InstrumentType;
@ -36,4 +37,33 @@ public class CurrencyPairSecurityRequestAdapter {
// req.setUuid(currencyInstrument.getInitiatorId() != null ? currencyInstrument.getInitiatorId().toString() : null);
return req;
}
public CurrencyPairSecurityNewGatewayRequest toCurrencySecurityNewRequest(CurrencyInstrument currencyInstrument,
TradingMode tradingMode) {
CurrencyPairSecurityNewGatewayRequest req = new CurrencyPairSecurityNewGatewayRequest();
CurrencyPairSecurityNewRequest securityNewRequest = new CurrencyPairSecurityNewRequest();
securityNewRequest.setInstrumentType(InstrumentType.CRNC.getKey());
securityNewRequest.setSecuritySymbol(currencyInstrument.getTicker());
securityNewRequest.setShortName(currencyInstrument.getTicker());
securityNewRequest.setFullName(currencyInstrument.getName());
securityNewRequest.setBaseUnitSize(tradingMode.getNumberOfLotCurrency());
securityNewRequest.setSettlementType(currencyInstrument.getSettleCode());
securityNewRequest.setClearingOrganization(currencyInstrument.getClearingOrganization());
securityNewRequest.setWorkflowStatus(currencyInstrument.getWorkflowStatus());
// Для listing
securityNewRequest.setMinStep(tradingMode.getMinStep());
securityNewRequest.setPrecision(tradingMode.getPrecision());
securityNewRequest.setLotSize(tradingMode.getLotSize());
req.setTradeMode(tradingMode.getTradingCode());
// для currencyPairId
req.setLotCurrencyLetterCode(tradingMode.getLotCurrencyLetterCode());
req.setMatchedCurrencyLetterCode(tradingMode.getMatchedCurrencyLetterCode());
req.setCurrencySecurityNewRequest(securityNewRequest);
// req.setUuid(currencyInstrument.getInitiatorId() != null ? currencyInstrument.getInitiatorId().toString() : null);
return req;
}
}

View file

@ -0,0 +1,80 @@
package ru.spcex.clearing.gatewayapi.service.processor;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrListingsMassiveRequest;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.CurrencyInstrument;
import ru.spcex.clearing.gatewayapi.controller.inbound.request.listing.curr.mkr.TradingMode;
import ru.spcex.clearing.gatewayapi.service.CodeStatusComparator;
import ru.spcex.clearing.gatewayapi.service.adapter.CurrencyPairSecurityRequestAdapter;
import ru.spcex.clearing.platform.messaging.domain.Consts;
import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecurityNewGatewayRequest;
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.platform.utils.log.ExceptionUtils;
@Service
public class ListingCurrMassiveProcessor {
private final Logger log = LoggerFactory.getLogger(getClass());
private final CurrencyPairSecurityRequestAdapter currencyPairSecurityRequestAdapter;
private final KafkaSender kafkaSender;
public ListingCurrMassiveProcessor(CurrencyPairSecurityRequestAdapter currencyPairSecurityRequestAdapter,
KafkaSender kafkaSender) {
this.currencyPairSecurityRequestAdapter = currencyPairSecurityRequestAdapter;
this.kafkaSender = kafkaSender;
}
public void process(CurrListingsMassiveRequest request) {
List<CurrencyInstrument> currencyInstrumentList = request.getCurrencyInstrumentList();
CodeStatusComparator<CurrencyInstrument> comparatorByCodeAndWorkflowStatus = CodeStatusComparator.create(
CurrencyInstrument::getTicker,
CurrencyInstrument::getWorkflowStatus);
currencyInstrumentList = currencyInstrumentList.stream()
.filter(ei -> {
if (StringUtils.isEmpty(ei.getTicker())) {
log.warn("CurrencyInstrument {}, ignore 1002: required field 'ticker' was empty", ei.getId());
return false;
}
if ("EXCHANGE".equalsIgnoreCase(StringUtils.trim(ei.getExchangeOffexchange()))) {
return true;
} else {
log.trace("CurrencyInstrument {} with ticker={} ignore: not allow exchange_offexchange={}",
ei.getId(), ei.getTicker(), ei.getExchangeOffexchange());
return false;
}
})
.sorted(comparatorByCodeAndWorkflowStatus).collect(Collectors.toList());
for (CurrencyInstrument currencyInstrument : currencyInstrumentList) {
UUID currencyInstrumentId = null;
try {
currencyInstrumentId = currencyInstrument.getId();
log.debug("Process currencyInstrumentId : {}", currencyInstrumentId);
UUID finalCurrencyInstrumentId = currencyInstrumentId;
Optional<TradingMode> tradingMode = request.getTradingModeList().stream()
.filter(tm -> tm.getCurrencySpecId().equals(finalCurrencyInstrumentId))
.findFirst();
if (tradingMode.isEmpty()) {
log.warn("Trading mode not found for instrument {}", finalCurrencyInstrumentId);
continue;
}
// if (StringUtils.isEmpty(currencyInstrument.getCode()) || currencyInstrument.getCode().length() < 6) {
// log.warn("Incorrect code : {}, skip record", currencyInstrument.getCode());
// continue;
// }
CurrencyPairSecurityNewGatewayRequest currencySecurityNewRequest = currencyPairSecurityRequestAdapter
.toCurrencySecurityNewRequest(currencyInstrument, tradingMode.get());
kafkaSender.sendRequestToQueue(Consts.DESTINATION_GATEWAY_CURRENCY_PAIR_SECURITY, currencySecurityNewRequest);
} catch (Throwable e) {
log.error("currencyInstrumentId={}: {}", currencyInstrumentId, ExceptionUtils.getStackTrace(e));
}
}
log.debug("process CurrListingsRequest done");
}
}

View file

@ -1,5 +1,8 @@
package ru.spcex.clearing.gatewayapi.service.processor;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -13,10 +16,6 @@ import ru.spcex.clearing.platform.messaging.domain.cud.security.CurrencyPairSecu
import ru.spcex.clearing.platform.messaging.service.sender.KafkaSender;
import ru.spcex.platform.utils.log.ExceptionUtils;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class ListingCurrProcessor {
private final Logger log = LoggerFactory.getLogger(getClass());
@ -27,7 +26,7 @@ public class ListingCurrProcessor {
public ListingCurrProcessor(CurrencyPairSecurityRequestAdapter currencyPairSecurityRequestAdapter,
KafkaSender kafkaSender) {
this.currencyPairSecurityRequestAdapter = currencyPairSecurityRequestAdapter;
this.kafkaSender = kafkaSender;
this.kafkaSender = kafkaSender;;
}
public void process(CurrListingsRequest request) {

View file

@ -26,6 +26,7 @@ gateway-api.kafka-consumer.linger-ms=1
gateway-api.kafka-consumer.buffer-memory=33554432
gateway-api.clearing-system=LCC
gateway-api.load-currency-specification-format=Massive
gateway-api.inbound-server.enable-ssl=false
gateway-api.inbound-server.host=10.200.200.183