Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Добавлена поддержка брокера Финам #538

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/main/java/ru/investbook/parser/finam/FinamBrokerReport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* InvestBook
* Copyright (C) 2023 Spacious Team <spacious-team@ya.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package ru.investbook.parser.finam;

import lombok.EqualsAndHashCode;
import org.apache.poi.ss.usermodel.Workbook;
import org.spacious_team.table_wrapper.api.ReportPage;
import org.spacious_team.table_wrapper.api.TableCellAddress;
import org.spacious_team.table_wrapper.excel.ExcelSheet;
import ru.investbook.parser.AbstractExcelBrokerReport;
import ru.investbook.parser.SecurityRegistrar;

import java.io.InputStream;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

@EqualsAndHashCode(callSuper = true)
public class FinamBrokerReport extends AbstractExcelBrokerReport {

@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String UNIQ_TEXT = "Акционерное общество \u00ABИнвестиционная компания \u00ABФИНАМ\u00BB";
private static final String PORTFOLIO_MARKER = "По счету:";
private static final String REPORT_END_DATE_MARKER = "Справка о состоянии обязательств";
private static final int REPORT_END_DATE_POSITION = 9;

private final Workbook book;

public FinamBrokerReport(String excelFileName, InputStream is, SecurityRegistrar securityRegistrar) {
super(securityRegistrar);
this.book = getWorkBook(excelFileName, is);
final ReportPage reportPage = new ExcelSheet(book.getSheetAt(0));
checkReportFormat(excelFileName, reportPage);
setPath(Paths.get(excelFileName));
setReportPage(reportPage);
setPortfolio(getPortfolio(reportPage));
setReportEndDateTime(getReportEndDateTime(reportPage));
}

private String getPortfolio(ReportPage reportPage) {
try {
return String.valueOf(reportPage.getNextColumnValue(PORTFOLIO_MARKER));
} catch (Exception e) {
// TODO: Дейсствительно ли тут влетает исключение?
throw new IllegalArgumentException(
"В отчете не найден номер договора по заданному шаблону '" + PORTFOLIO_MARKER + "' XXX"
vananiev marked this conversation as resolved.
Show resolved Hide resolved
);
}
}

private Instant getReportEndDateTime(ReportPage reportPage) {
try {
final TableCellAddress address = reportPage.findByPrefix(REPORT_END_DATE_MARKER, 0, 1);
@SuppressWarnings("DataFlowIssue")
final String value = reportPage.getCell(address)
.getStringValue()
.split(" ")[REPORT_END_DATE_POSITION];
return convertToInstant(value)
.plus(LAST_TRADE_HOUR, ChronoUnit.HOURS);
} catch (Exception e) {
throw new IllegalArgumentException(
"Не найдена дата отчета по заданному шаблону '" + REPORT_END_DATE_MARKER+ " XXX'"
);
}
}

public static void checkReportFormat(String excelFileName, ReportPage reportPage) {
if (reportPage.findByPrefix(UNIQ_TEXT, 2) == TableCellAddress.NOT_FOUND) {
vananiev marked this conversation as resolved.
Show resolved Hide resolved
throw new RuntimeException("В файле " + excelFileName + " не содежится отчета брокера ФИНАМ");
}
}

@Override
public void close() throws Exception {
this.book.close();
vananiev marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* InvestBook
* Copyright (C) 2023 Spacious Team <spacious-team@ya.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package ru.investbook.parser.finam;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.spacious_team.broker.report_parser.api.AbstractBrokerReportFactory;
import org.spacious_team.broker.report_parser.api.BrokerReport;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import ru.investbook.parser.SecurityRegistrar;

import java.io.InputStream;
import java.util.Optional;
import java.util.regex.Pattern;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // TODO:why???
@Slf4j
@RequiredArgsConstructor
public class FinamBrokerReportFactory extends AbstractBrokerReportFactory {

private final SecurityRegistrar securityRegistrar;

@Getter
private final String brokerName = "ФИНАМ";

private final Pattern expectedFileNamePattern = Pattern.compile(
"^(\\p{L}+_){3}КлФ_[0-9]+_\\d{2}_\\d{2}_\\d{4}_по_\\d{2}_\\d{2}_\\d{4}\\.xls(x)?$"
);

@Override
public boolean canCreate(String excelFileName, InputStream is) {
return super.canCreate(expectedFileNamePattern, excelFileName, is);
}

@Override
public Optional<BrokerReport> create(String excelFileName, InputStream is) {
Optional<BrokerReport> brokerReport = create(excelFileName, is,
(fileName, stream) -> new FinamBrokerReport(fileName, stream, securityRegistrar));
if (brokerReport.isPresent()) {
log.info("Обнаружен отчет '{}' Финам брокера", excelFileName);
}
return brokerReport;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* InvestBook
* Copyright (C) 2023 Spacious Team <spacious-team@ya.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package ru.investbook.parser.finam;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.spacious_team.broker.pojo.PortfolioProperty;
import org.spacious_team.broker.pojo.PortfolioPropertyType;
import org.spacious_team.broker.report_parser.api.BrokerReport;
import org.spacious_team.table_wrapper.api.ConstantPositionTableColumn;
import org.spacious_team.table_wrapper.api.PatternTableColumn;
import org.spacious_team.table_wrapper.api.Table;
import org.spacious_team.table_wrapper.api.TableColumn;
import org.spacious_team.table_wrapper.api.TableHeaderColumn;
import org.spacious_team.table_wrapper.api.TableRow;
import ru.investbook.parser.SingleBrokerReport;
import ru.investbook.parser.SingleInitializableReportTable;

import java.util.Collection;
import java.util.Collections;

@Slf4j
public class FinamPortfolioPropertyTable extends SingleInitializableReportTable<PortfolioProperty> {

private static final String SUMMARY_TABLE = "1. Оценка состояния счета Клиента";
private static final String ASSETS = "ИТОГО оценка состояния счета (руб.)";

public FinamPortfolioPropertyTable(SingleBrokerReport report) {
super(report);
}

@Override
protected Collection<PortfolioProperty> parseTable() {
final Table table = getSummaryTable();
return getTotalAssets(table);
}

protected Table getSummaryTable() {
return getSummaryTable(getReport(), ASSETS);
}

public static Table getSummaryTable(BrokerReport report, String tableFooterString) {
vananiev marked this conversation as resolved.
Show resolved Hide resolved
final Table table = report.getReportPage()
.createNameless("На начало периода", tableFooterString, FinamSummaryTableHeader.class);
if (table.isEmpty()) {
throw new IllegalArgumentException("Таблица '" + SUMMARY_TABLE + "' не найдена");
}
return table;
}

protected Collection<PortfolioProperty> getTotalAssets(Table table) {
try {
final TableRow row = table.findRowByPrefix(ASSETS);
if (row == null) {
return Collections.emptyList();
}
return Collections.singletonList(PortfolioProperty.builder()
.portfolio(getReport().getPortfolio())
.property(PortfolioPropertyType.TOTAL_ASSETS_RUB)
.value(row.getBigDecimalCellValue(FinamSummaryTableHeader.PERIOD_END).toString())
.timestamp(getReport().getReportEndDateTime())
.build()
);
} catch (Exception e) {
log.info("Не могу получить стоимость активов из отчета {}", getReport());
return Collections.emptyList();
}
}

@RequiredArgsConstructor
private enum FinamSummaryTableHeader implements TableHeaderColumn {
DESCRIPTION(ConstantPositionTableColumn.of(0)),
PERIOD_BEGIN(PatternTableColumn.of(HeaderDescriptions.PERIOD_BEGIN_HEADER)),
PERIOD_END(PatternTableColumn.of(HeaderDescriptions.PERIOD_END_HEADER)),
PERIOD_CHANGE(PatternTableColumn.of(HeaderDescriptions.PERIOD_CHANGE_HEADER));

@Getter
private final TableColumn column;

private static class HeaderDescriptions {
private static final String PERIOD_BEGIN_HEADER = "На начало периода";
private static final String PERIOD_END_HEADER = "На конец периода";
private static final String PERIOD_CHANGE_HEADER = "Изменение за период";
}

}
}
77 changes: 77 additions & 0 deletions src/main/java/ru/investbook/parser/finam/FinamReportTables.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* InvestBook
* Copyright (C) 2023 Spacious Team <spacious-team@ya.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package ru.investbook.parser.finam;

import org.spacious_team.broker.pojo.EventCashFlow;
import org.spacious_team.broker.pojo.ForeignExchangeRate;
import org.spacious_team.broker.pojo.PortfolioCash;
import org.spacious_team.broker.pojo.PortfolioProperty;
import org.spacious_team.broker.pojo.Security;
import org.spacious_team.broker.pojo.SecurityEventCashFlow;
import org.spacious_team.broker.pojo.SecurityQuote;
import org.spacious_team.broker.report_parser.api.AbstractReportTables;
import org.spacious_team.broker.report_parser.api.AbstractTransaction;
import org.spacious_team.broker.report_parser.api.ReportTable;

public class FinamReportTables extends AbstractReportTables<FinamBrokerReport> {

protected FinamReportTables(FinamBrokerReport report) {
super(report);
}

@Override
public ReportTable<PortfolioProperty> getPortfolioPropertyTable() {
return new FinamPortfolioPropertyTable(report);
}

@Override
public ReportTable<PortfolioCash> getPortfolioCashTable() {
return emptyTable();
}

@Override
public ReportTable<EventCashFlow> getCashFlowTable() {
return emptyTable();
}

@Override
public ReportTable<Security> getSecuritiesTable() {
return emptyTable();
}

@Override
public ReportTable<AbstractTransaction> getTransactionTable() {
return emptyTable();
}

@Override
public ReportTable<SecurityEventCashFlow> getSecurityEventCashFlowTable() {
return emptyTable();
}

@Override
public ReportTable<SecurityQuote> getSecurityQuoteTable() {
return emptyTable();
}

@Override
public ReportTable<ForeignExchangeRate> getForeignExchangeRateTable() {
return emptyTable();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* InvestBook
* Copyright (C) 2023 Spacious Team <spacious-team@ya.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package ru.investbook.parser.finam;

import org.spacious_team.broker.report_parser.api.BrokerReport;
import org.spacious_team.broker.report_parser.api.ReportTables;
import org.spacious_team.broker.report_parser.api.ReportTablesFactory;
import org.springframework.stereotype.Component;

@Component
public class FinamReportTablesFactory implements ReportTablesFactory {


@Override
public boolean canCreate(BrokerReport brokerReport) {
return brokerReport instanceof FinamBrokerReport;
}

@Override
public ReportTables create(BrokerReport brokerReport) {
return new FinamReportTables((FinamBrokerReport) brokerReport);
}
}