1
0

perf: 优化财务数据的采集和显示

This commit is contained in:
2025-09-15 13:56:07 +08:00
parent 4cc7d2344f
commit 9f781ce794
16 changed files with 229 additions and 865 deletions

View File

@@ -7,7 +7,6 @@ import com.lanyuanxiaoyao.leopard.server.service.StockService;
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.springframework.web.bind.annotation.GetMapping;
@@ -38,38 +37,42 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
public GlobalResponse<FinanceItem> finance(@PathVariable("id") Long id) {
// 财报默认是上一年的
var year = LocalDate.now().minusYears(1).getYear();
var balanceSheet = stockService.findBalanceSheet(id, year);
var income = stockService.findIncome(id, year);
var cashFlow = stockService.findCashFlow(id, year);
var financeIndicator = stockService.findFinanceIndicator(id, year);
return GlobalResponse.responseSuccess(new FinanceItem(
id,
year,
balanceSheet
.map(bs -> new BalanceSheetItem(
NumberHelper.formatFinanceDouble(bs.getTotalAssets()),
NumberHelper.formatFinanceDouble(bs.getTotalCurrentAssets()),
NumberHelper.formatFinanceDouble(bs.getTotalNonCurrentAssets()),
NumberHelper.formatFinanceDouble(bs.getTotalLiabilities()),
NumberHelper.formatFinanceDouble(bs.getTotalCurrentLiabilities()),
NumberHelper.formatFinanceDouble(bs.getTotalNonCurrentLiabilities())
financeIndicator
.map(fi -> new BalanceSheetItem(
NumberHelper.formatFinanceDouble(fi.getTotalAssets()),
NumberHelper.formatFinanceDouble(fi.getCurrentAssets()),
NumberHelper.formatPercentageDouble(fi.getCurrentAssetsToTotalAssetsRatio()),
NumberHelper.formatFinanceDouble(fi.getFixedAssets()),
NumberHelper.formatPercentageDouble(fi.getFixedAssetsToTotalAssetsRatio()),
NumberHelper.formatFinanceDouble(fi.getTotalLiabilities()),
NumberHelper.formatFinanceDouble(fi.getCurrentLiabilities()),
NumberHelper.formatPercentageDouble(fi.getCurrentLiabilitiesToTotalLiabilitiesRatio()),
NumberHelper.formatFinanceDouble(fi.getLongTermLiabilities()),
NumberHelper.formatPercentageDouble(fi.getLongTermLiabilitiesToTotalLiabilitiesRatio())
))
.orElse(new BalanceSheetItem()),
income
.map(ic -> new IncomeItem(
NumberHelper.formatFinanceDouble(ic.getTotalOperatingRevenue()),
NumberHelper.formatFinanceDouble(ic.getTotalOperatingCost()),
NumberHelper.formatFinanceDouble(ic.getTotalProfit())
financeIndicator
.map(fi -> new IncomeItem(
NumberHelper.formatFinanceDouble(fi.getOperatingRevenue()),
NumberHelper.formatFinanceDouble(fi.getOperatingCost()),
NumberHelper.formatFinanceDouble(fi.getOperatingProfit())
))
.orElse(new IncomeItem()),
cashFlow
.map(cf -> new CashFlowItem(
NumberHelper.formatFinanceDouble(cf.getNetProfit())
financeIndicator
.map(fi -> new CashFlowItem(
NumberHelper.formatFinanceDouble(fi.getNetProfit())
))
.orElse(new CashFlowItem())
));
}
private GlobalResponse<Map<String, Object>> convertFinanceChartData(List<?> data, String field) {
@GetMapping("finance/{id}/{field}")
public GlobalResponse<Map<String, Object>> financeCharts(@PathVariable("id") Long id, @PathVariable("field") String field) {
var data = stockService.findFinanceIndicatorRecent(id, 5);
return GlobalResponse.responseDetailData(
data.stream()
.map(item -> BeanUtil.getFieldValue(item, field))
@@ -77,16 +80,6 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
);
}
@GetMapping("finance/{id}/{type}/{field}")
public GlobalResponse<Map<String, Object>> financeCharts(@PathVariable("id") Long id, @PathVariable("type") String type, @PathVariable("field") String field) {
return switch (type) {
case "balanceSheet" -> convertFinanceChartData(stockService.findBalanceSheetRecent(id, 5), field);
case "income" -> convertFinanceChartData(stockService.findIncomeRecent(id, 5), field);
case "cashflow" -> convertFinanceChartData(stockService.findCashFlowRecent(id, 5), field);
default -> throw new IllegalStateException("Unexpected value: " + type);
};
}
@Override
protected Function<Void, Stock> saveItemMapper() {
throw new UnsupportedOperationException();
@@ -136,14 +129,22 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
public record BalanceSheetItem(
String totalAssets,
String totalCurrentAssets,
String totalNonCurrentAssets,
String currentAssets,
String currentAssetsRatio,
String fixedAssets,
String fixedAssetsRatio,
String totalLiabilities,
String totalCurrentLiabilities,
String totalNonCurrentLiabilities
String currentLiabilities,
String currentLiabilitiesRatio,
String longTermLiabilities,
String longTermLiabilitiesRatio
) {
public BalanceSheetItem() {
this(
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
NumberHelper.FINANCE_NULL_DOUBLE,
@@ -155,9 +156,9 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockC
}
public record IncomeItem(
String totalOperatingRevenue,
String totalOperatingCost,
String totalProfit
String operatingRevenue,
String operatingCost,
String operatingProfit
) {
public IncomeItem() {
this(

View File

@@ -16,7 +16,26 @@ public class NumberHelper {
if (ObjectUtil.isNull(value)) {
return FINANCE_NULL_DOUBLE;
}
return NumberUtil.decimalFormat("#.##", value);
var builder = new StringBuilder();
if (value > 100000000) {
builder.append(value.longValue() / 100000000).append("亿");
value = value % 100000000;
}
if (value > 10000) {
builder.append(value.longValue() / 10000).append("");
value = value % 10000;
}
if (value > 0) {
builder.append(NumberUtil.decimalFormat("#.##", value));
}
return builder.toString();
}
public static String formatPercentageDouble(Double value) {
if (ObjectUtil.isNull(value)) {
return null;
}
return NumberUtil.decimalFormat("0.00%", value);
}
public static Double parseDouble(String value) {

View File

@@ -1,18 +1,10 @@
package com.lanyuanxiaoyao.leopard.server.service;
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet_;
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow_;
import com.lanyuanxiaoyao.leopard.core.entity.Income;
import com.lanyuanxiaoyao.leopard.core.entity.Income_;
import com.lanyuanxiaoyao.leopard.core.entity.QBalanceSheet;
import com.lanyuanxiaoyao.leopard.core.entity.QCashFlow;
import com.lanyuanxiaoyao.leopard.core.entity.QIncome;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator_;
import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.repository.BalanceSheetRepository;
import com.lanyuanxiaoyao.leopard.core.repository.CashFlowRepository;
import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository;
import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
import java.time.LocalDate;
@@ -30,63 +22,28 @@ import org.springframework.stereotype.Service;
@Service
public class StockService extends SimpleServiceSupport<Stock> {
private final StockRepository stockRepository;
private final BalanceSheetRepository balanceSheetRepository;
private final IncomeRepository incomeRepository;
private final CashFlowRepository cashFlowRepository;
private final FinanceIndicatorRepository financeIndicatorRepository;
public StockService(StockRepository repository, BalanceSheetRepository balanceSheetRepository, IncomeRepository incomeRepository, CashFlowRepository cashFlowRepository) {
public StockService(StockRepository repository, FinanceIndicatorRepository financeIndicatorRepository) {
super(repository);
this.stockRepository = repository;
this.balanceSheetRepository = balanceSheetRepository;
this.incomeRepository = incomeRepository;
this.cashFlowRepository = cashFlowRepository;
this.financeIndicatorRepository = financeIndicatorRepository;
}
public Optional<BalanceSheet> findBalanceSheet(Long stockId, Integer year) {
return balanceSheetRepository.findOne(
QBalanceSheet.balanceSheet.year.eq(year)
.and(QBalanceSheet.balanceSheet.stock.id.eq(stockId))
public Optional<FinanceIndicator> findFinanceIndicator(Long stockId, Integer year) {
return financeIndicatorRepository.findOne(
QFinanceIndicator.financeIndicator.year.eq(year)
.and(QFinanceIndicator.financeIndicator.stock.id.eq(stockId))
);
}
public Optional<Income> findIncome(Long stockId, Integer year) {
return incomeRepository.findOne(
QIncome.income.year.eq(year)
.and(QIncome.income.stock.id.eq(stockId))
);
}
public Optional<CashFlow> findCashFlow(Long stockId, Integer year) {
return cashFlowRepository.findOne(
QCashFlow.cashFlow.year.eq(year)
.and(QCashFlow.cashFlow.stock.id.eq(stockId))
);
}
public List<BalanceSheet> findBalanceSheetRecent(Long stockId, int years) {
public List<FinanceIndicator> findFinanceIndicatorRecent(Long stockId, int years) {
var current = LocalDate.now();
return balanceSheetRepository.findAll(
QBalanceSheet.balanceSheet.stock.id.eq(stockId)
.and(QBalanceSheet.balanceSheet.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, BalanceSheet_.YEAR)
);
}
public List<Income> findIncomeRecent(Long stockId, int years) {
var current = LocalDate.now();
return incomeRepository.findAll(
QIncome.income.stock.id.eq(stockId)
.and(QIncome.income.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, Income_.YEAR)
);
}
public List<CashFlow> findCashFlowRecent(Long stockId, int years) {
var current = LocalDate.now();
return cashFlowRepository.findAll(
QCashFlow.cashFlow.stock.id.eq(stockId)
.and(QCashFlow.cashFlow.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, CashFlow_.YEAR)
return financeIndicatorRepository.findAll(
QFinanceIndicator.financeIndicator.stock.id.eq(stockId)
.and(QFinanceIndicator.financeIndicator.year.between(current.minusYears(years).getYear(), current.getYear())),
Sort.by(Sort.Direction.ASC, FinanceIndicator_.YEAR)
);
}
}

View File

@@ -109,123 +109,6 @@ public class TuShareService {
return tuShareResponse;
}
@SneakyThrows
public TuShareResponse incomeList(int year) {
var response = HttpUtil.post(API_URL, buildRequest(
"income_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TRADE_FORMAT)),
List.of(
"ts_code",
"basic_eps",
"diluted_eps",
"total_revenue",
"revenue",
"total_cogs",
"oper_cost",
"sell_exp",
"admin_exp",
"fin_exp",
"oper_exp",
"operate_profit",
"non_oper_income",
"non_oper_exp",
"total_profit",
"income_tax",
"n_income",
"n_income_attr_p",
"compr_inc_attr_p",
"compr_inc_attr_m_s",
"ebit",
"undist_profit",
"distable_profit",
"rd_exp",
"fin_exp_int_exp",
"continued_net_profit",
"end_net_profit"
)
));
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
if (tuShareResponse.code != 0) {
throw new RuntimeException(tuShareResponse.message);
}
return tuShareResponse;
}
@SneakyThrows
public TuShareResponse balanceList(int year) {
var response = HttpUtil.post(API_URL, buildRequest(
"balancesheet_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TRADE_FORMAT)),
List.of(
"ts_code",
"total_share",
"cap_rese",
"undistr_porfit",
"money_cap",
"accounts_receiv",
"inventories",
"total_cur_assets",
"lt_eqt_invest",
"lt_rec",
"fix_assets",
"r_and_d",
"goodwill",
"total_nca",
"total_assets",
"lt_borr",
"st_borr",
"acct_payable",
"adv_receipts",
"total_cur_liab",
"total_ncl",
"total_liab",
"total_hldr_eqy_exc_min_int",
"total_hldr_eqy_inc_min_int",
"total_liab_hldr_eqy",
"acc_receivable",
"payables",
"accounts_receiv_bill",
"accounts_pay",
"oth_rcv_total",
"fix_assets_total"
)
));
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
if (tuShareResponse.code != 0) {
throw new RuntimeException(tuShareResponse.message);
}
return tuShareResponse;
}
@SneakyThrows
public TuShareResponse cashFlowList(int year) {
var response = HttpUtil.post(API_URL, buildRequest(
"cashflow_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TRADE_FORMAT)),
List.of(
"ts_code",
"net_profit",
"finan_exp",
"c_fr_sale_sg",
"c_inf_fr_operate_a",
"c_paid_to_for_empl",
"c_paid_for_taxes",
"n_cashflow_act",
"stot_inflows_inv_act",
"c_pay_acq_const_fiolta",
"stot_out_inv_act",
"stot_cashout_fnc_act",
"c_cash_equ_beg_period",
"c_cash_equ_end_period"
)
));
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
if (tuShareResponse.code != 0) {
throw new RuntimeException(tuShareResponse.message);
}
return tuShareResponse;
}
public List<Map<String, String>> request(String api, Map<String, Object> params, List<String> fields) throws JsonProcessingException {
var response = HttpUtil.post(API_URL, buildRequest(api, params, fields));
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);

View File

@@ -1,6 +1,9 @@
package com.lanyuanxiaoyao.leopard.server.service.task;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
@@ -15,7 +18,7 @@ import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("update_finance_indicators")
@LiteflowComponent("update_finance")
public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
private final FinanceIndicatorRepository financeIndicatorRepository;
private final StockRepository stockRepository;
@@ -57,7 +60,11 @@ public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
"total_assets"
)
);
var balancesMap = balances.stream().collect(Collectors.toMap(balance -> balance.get("ts_code"), balance -> balance));
var balancesMap = balances.stream().collect(Collectors.toMap(
map -> map.get("ts_code"),
map -> map,
(existing, replacement) -> existing
));
var incomes = tuShareService.request(
"income_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
@@ -70,21 +77,31 @@ public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
"n_income"
)
);
var incomesMap = incomes.stream().collect(Collectors.toMap(income -> income.get("ts_code"), income -> income));
var incomesMap = incomes.stream().collect(Collectors.toMap(
map -> map.get("ts_code"),
map -> map,
(existing, replacement) -> existing
));
var cashFlows = tuShareService.request(
"cashflow_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
List.of(
"ts_code",
"n_cashflow_act",
"n_cashflow_inv_act",
"n_cash_flows_fnc_act"
)
);
var cashFlowsMap = cashFlows.stream().collect(Collectors.toMap(cashFlow -> cashFlow.get("ts_code"), cashFlow -> cashFlow));
var cashFlowsMap = cashFlows.stream().collect(Collectors.toMap(
map -> map.get("ts_code"),
map -> map,
(existing, replacement) -> existing
));
var finaIndicators = tuShareService.request(
"fina_indicator_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
List.of(
"ts_code",
"ca_to_assets",
"nca_to_assets",
"currentdebt_to_debt",
@@ -103,72 +120,92 @@ public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
"total_revenue_ps"
)
);
var finaIndicatorsMap = finaIndicators.stream().collect(Collectors.toMap(finaIndicator -> finaIndicator.get("ts_code"), finaIndicator -> finaIndicator));
var finaIndicatorsMap = finaIndicators.stream().collect(Collectors.toMap(
map -> map.get("ts_code"),
map -> map,
(existing, replacement) -> existing
));
for (Stock stock : stocks) {
var balance = balancesMap.get(stock.getCode());
var income = incomesMap.get(stock.getCode());
var cashFlow = cashFlowsMap.get(stock.getCode());
var finaIndicator = finaIndicatorsMap.get(stock.getCode());
var indicator = new FinanceIndicator();
if (ArrayUtil.<Object>isAllNull(balance, income, cashFlow, finaIndicator)) {
continue;
}
var indicator = financeIndicatorRepository.findOne(
QFinanceIndicator.financeIndicator.stock.id.eq(stock.getId())
.and(QFinanceIndicator.financeIndicator.year.eq(year))
).orElse(new FinanceIndicator());
indicator.setStock(stock);
indicator.setYear(year);
indicator.setTotalAssets(NumberHelper.parseDouble(balance.get("total_assets")));
indicator.setTotalShareCapital(NumberHelper.parseDouble(balance.get("total_share")));
indicator.setCapitalSurplus(NumberHelper.parseDouble(balance.get("cap_rese")));
indicator.setSurplusReserve(NumberHelper.parseDouble(balance.get("surplus_rese")));
indicator.setUndistributedProfit(NumberHelper.parseDouble(balance.get("undistr_porfit")));
indicator.setCashAndCashEquivalents(NumberHelper.parseDouble(balance.get("cash_reser_cb")));
indicator.setCashAndCashEquivalentsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCashAndCashEquivalents(), indicator.getTotalAssets()));
indicator.setAccountsReceivable(NumberHelper.parseDouble(balance.get("accounts_receiv_bill")));
indicator.setAccountsReceivableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsReceivable(), indicator.getTotalAssets()));
indicator.setAccountsPayable(NumberHelper.parseDouble(balance.get("accounts_pay")));
indicator.setAccountsPayableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsPayable(), indicator.getTotalAssets()));
indicator.setInventory(NumberHelper.parseDouble(balance.get("inventories")));
indicator.setInventoryToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getInventory(), indicator.getTotalAssets()));
indicator.setGoodwill(NumberHelper.parseDouble(balance.get("goodwill")));
indicator.setGoodwillToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getGoodwill(), indicator.getTotalAssets()));
indicator.setCurrentAssets(NumberHelper.parseDouble(balance.get("total_cur_assets")));
indicator.setCurrentAssetsToTotalAssetsRatio(NumberHelper.parseDouble(finaIndicator.get("ca_to_assets")));
indicator.setFixedAssets(NumberHelper.parseDouble(balance.get("total_nca")));
indicator.setFixedAssetsToTotalAssetsRatio(NumberHelper.parseDouble(finaIndicator.get("nca_to_assets")));
indicator.setTotalLiabilities(NumberHelper.parseDouble(balance.get("total_liab")));
indicator.setCurrentLiabilities(NumberHelper.parseDouble(balance.get("total_cur_liab")));
indicator.setCurrentLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalAssets()));
indicator.setCurrentLiabilitiesToTotalLiabilitiesRatio(NumberHelper.parseDouble(finaIndicator.get("currentdebt_to_debt")));
indicator.setLongTermLiabilities(NumberHelper.parseDouble(balance.get("total_ncl")));
indicator.setLongTermLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalAssets()));
indicator.setLongTermLiabilitiesToTotalLiabilitiesRatio(NumberHelper.parseDouble(finaIndicator.get("longdeb_to_debt")));
indicator.setLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getTotalLiabilities(), indicator.getTotalAssets()));
indicator.setShareholdersEquity(NumberHelper.parseDouble(balance.get("total_hldr_eqy_inc_min_int")));
indicator.setShareholdersEquityToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getShareholdersEquity(), indicator.getTotalAssets()));
indicator.setOperatingRevenue(NumberHelper.parseDouble(income.get("total_revenue")));
indicator.setOperatingCost(NumberHelper.parseDouble(income.get("total_cogs")));
indicator.setOperatingProfit(NumberHelper.parseDouble(income.get("operate_profit")));
indicator.setOperatingExpenses(NumberHelper.parseDouble(income.get("oper_exp")));
indicator.setNetProfit(NumberHelper.parseDouble(income.get("n_income")));
indicator.setCashFlowFromOperatingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_act")));
indicator.setCashFlowFromInvestingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_inv_act")));
indicator.setCashFlowFromFinancingActivities(NumberHelper.parseDouble(cashFlow.get("n_cash_flows_fnc_act")));
indicator.setCurrentRatio(NumberHelper.parseDouble(finaIndicator.get("current_ratio")));
indicator.setQuickRatio(NumberHelper.parseDouble(finaIndicator.get("quick_ratio")));
indicator.setAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("ar_turn")));
indicator.setDaysAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("arturn_days")));
indicator.setInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("inv_turn")));
indicator.setDaysInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("invturn_days")));
indicator.setFixedAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("fa_turn")));
indicator.setDaysFixedAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getFixedAssetsTurnover()));
indicator.setTotalAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("assets_turn")));
indicator.setDaysTotalAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getTotalAssetsTurnover()));
indicator.setReturnOnEquity(NumberHelper.parseDouble(finaIndicator.get("roe_dt")));
indicator.setReturnOnAssets(NumberHelper.parseDouble(finaIndicator.get("roa")));
indicator.setOperatingGrossProfitMargin(NumberHelper.safeDiv(NumberHelper.safeMinus(indicator.getOperatingRevenue(), indicator.getOperatingCost()), indicator.getOperatingRevenue()));
indicator.setOperatingProfitMargin(NumberHelper.safeDiv(indicator.getOperatingProfit(), indicator.getOperatingRevenue()));
indicator.setOperatingSafetyMarginRatio(NumberHelper.safeDiv(indicator.getOperatingProfitMargin(), indicator.getOperatingGrossProfitMargin()));
indicator.setNetProfitMargin(NumberHelper.parseDouble(finaIndicator.get("roa_dp")));
indicator.setEarningsPerShare(NumberHelper.parseDouble(finaIndicator.get("total_revenue_ps")));
if (ObjectUtil.isNotNull(balance)) {
indicator.setTotalAssets(NumberHelper.parseDouble(balance.get("total_assets")));
indicator.setTotalShareCapital(NumberHelper.parseDouble(balance.get("total_share")));
indicator.setCapitalSurplus(NumberHelper.parseDouble(balance.get("cap_rese")));
indicator.setSurplusReserve(NumberHelper.parseDouble(balance.get("surplus_rese")));
indicator.setUndistributedProfit(NumberHelper.parseDouble(balance.get("undistr_porfit")));
indicator.setCashAndCashEquivalents(NumberHelper.parseDouble(balance.get("cash_reser_cb")));
indicator.setCashAndCashEquivalentsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCashAndCashEquivalents(), indicator.getTotalAssets()));
indicator.setAccountsReceivable(NumberHelper.parseDouble(balance.get("accounts_receiv_bill")));
indicator.setAccountsReceivableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsReceivable(), indicator.getTotalAssets()));
indicator.setAccountsPayable(NumberHelper.parseDouble(balance.get("accounts_pay")));
indicator.setAccountsPayableToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getAccountsPayable(), indicator.getTotalAssets()));
indicator.setInventory(NumberHelper.parseDouble(balance.get("inventories")));
indicator.setInventoryToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getInventory(), indicator.getTotalAssets()));
indicator.setGoodwill(NumberHelper.parseDouble(balance.get("goodwill")));
indicator.setGoodwillToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getGoodwill(), indicator.getTotalAssets()));
indicator.setCurrentAssets(NumberHelper.parseDouble(balance.get("total_cur_assets")));
indicator.setCurrentAssetsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentAssets(), indicator.getTotalAssets()));
indicator.setFixedAssets(NumberHelper.parseDouble(balance.get("total_nca")));
indicator.setFixedAssetsToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getFixedAssets(), indicator.getTotalAssets()));
indicator.setTotalLiabilities(NumberHelper.parseDouble(balance.get("total_liab")));
indicator.setCurrentLiabilities(NumberHelper.parseDouble(balance.get("total_cur_liab")));
indicator.setCurrentLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalAssets()));
indicator.setCurrentLiabilitiesToTotalLiabilitiesRatio(NumberHelper.safeDiv(indicator.getCurrentLiabilities(), indicator.getTotalLiabilities()));
indicator.setLongTermLiabilities(NumberHelper.parseDouble(balance.get("total_ncl")));
indicator.setLongTermLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalAssets()));
indicator.setLongTermLiabilitiesToTotalLiabilitiesRatio(NumberHelper.safeDiv(indicator.getLongTermLiabilities(), indicator.getTotalLiabilities()));
indicator.setLiabilitiesToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getTotalLiabilities(), indicator.getTotalAssets()));
indicator.setShareholdersEquity(NumberHelper.parseDouble(balance.get("total_hldr_eqy_inc_min_int")));
indicator.setShareholdersEquityToTotalAssetsRatio(NumberHelper.safeDiv(indicator.getShareholdersEquity(), indicator.getTotalAssets()));
}
if (ObjectUtil.isNotNull(income)) {
indicator.setOperatingRevenue(NumberHelper.parseDouble(income.get("total_revenue")));
indicator.setOperatingCost(NumberHelper.parseDouble(income.get("total_cogs")));
indicator.setOperatingProfit(NumberHelper.parseDouble(income.get("operate_profit")));
indicator.setOperatingExpenses(NumberHelper.parseDouble(income.get("oper_exp")));
indicator.setNetProfit(NumberHelper.parseDouble(income.get("n_income")));
}
if (ObjectUtil.isNotNull(cashFlow)) {
indicator.setCashFlowFromOperatingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_act")));
indicator.setCashFlowFromInvestingActivities(NumberHelper.parseDouble(cashFlow.get("n_cashflow_inv_act")));
indicator.setCashFlowFromFinancingActivities(NumberHelper.parseDouble(cashFlow.get("n_cash_flows_fnc_act")));
}
if (ObjectUtil.isNotNull(finaIndicator)) {
indicator.setCurrentRatio(NumberHelper.parseDouble(finaIndicator.get("current_ratio")));
indicator.setQuickRatio(NumberHelper.parseDouble(finaIndicator.get("quick_ratio")));
indicator.setAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("ar_turn")));
indicator.setDaysAccountsReceivableTurnover(NumberHelper.parseDouble(finaIndicator.get("arturn_days")));
indicator.setInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("inv_turn")));
indicator.setDaysInventoryTurnover(NumberHelper.parseDouble(finaIndicator.get("invturn_days")));
indicator.setFixedAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("fa_turn")));
indicator.setDaysFixedAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getFixedAssetsTurnover()));
indicator.setTotalAssetsTurnover(NumberHelper.parseDouble(finaIndicator.get("assets_turn")));
indicator.setDaysTotalAssetsTurnover(NumberHelper.safeDiv(360.0, indicator.getTotalAssetsTurnover()));
indicator.setReturnOnEquity(NumberHelper.parseDouble(finaIndicator.get("roe_dt")));
indicator.setReturnOnAssets(NumberHelper.parseDouble(finaIndicator.get("roa")));
indicator.setOperatingGrossProfitMargin(NumberHelper.safeDiv(NumberHelper.safeMinus(indicator.getOperatingRevenue(), indicator.getOperatingCost()), indicator.getOperatingRevenue()));
indicator.setOperatingProfitMargin(NumberHelper.safeDiv(indicator.getOperatingProfit(), indicator.getOperatingRevenue()));
indicator.setOperatingSafetyMarginRatio(NumberHelper.safeDiv(indicator.getOperatingProfitMargin(), indicator.getOperatingGrossProfitMargin()));
indicator.setNetProfitMargin(NumberHelper.parseDouble(finaIndicator.get("roa_dp")));
indicator.setEarningsPerShare(NumberHelper.parseDouble(finaIndicator.get("total_revenue_ps")));
}
financeIndicatorRepository.save(indicator);
}
setStep((year - 1990) * 100 / (currentYear - 1990));
}
}
}

View File

@@ -1,174 +0,0 @@
package com.lanyuanxiaoyao.leopard.server.service.task;
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
import com.lanyuanxiaoyao.leopard.core.entity.Income;
import com.lanyuanxiaoyao.leopard.core.entity.QBalanceSheet;
import com.lanyuanxiaoyao.leopard.core.entity.QCashFlow;
import com.lanyuanxiaoyao.leopard.core.entity.QIncome;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.repository.BalanceSheetRepository;
import com.lanyuanxiaoyao.leopard.core.repository.CashFlowRepository;
import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
* 更新财务数据
*
* @author lanyuanxiaoyao
* @version 20250911
*/
@Slf4j
@LiteflowComponent("update_finance")
public class UpdateFinanceNode extends TaskNodeComponent {
private final StockRepository stockRepository;
private final IncomeRepository incomeRepository;
private final BalanceSheetRepository balanceSheetRepository;
private final CashFlowRepository cashFlowRepository;
private final TuShareService tuShareService;
public UpdateFinanceNode(TaskService taskService, StockRepository stockRepository, IncomeRepository incomeRepository, BalanceSheetRepository balanceSheetRepository, CashFlowRepository cashFlowRepository, TuShareService tuShareService) {
super(taskService);
this.stockRepository = stockRepository;
this.incomeRepository = incomeRepository;
this.balanceSheetRepository = balanceSheetRepository;
this.cashFlowRepository = cashFlowRepository;
this.tuShareService = tuShareService;
}
@Override
public void process() {
var stocks = stockRepository.findAll();
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
var currentYear = LocalDate.now().getYear();
for (int year = 1990; year < currentYear; year++) {
var response = tuShareService.incomeList(year);
for (List<String> item : response.data().items()) {
var code = item.get(0);
if (!stocksMap.containsKey(code)) {
continue;
}
var stock = stocksMap.get(code);
var income = incomeRepository.findOne(
QIncome.income.year.eq(year)
.and(QIncome.income.stock.code.eq(stock.getCode()))
).orElse(new Income());
income.setStock(stock);
income.setYear(year);
income.setBasicEarningsPerShare(NumberHelper.parseDouble(item.get(1)));
income.setDilutedEarningsPerShare(NumberHelper.parseDouble(item.get(2)));
income.setTotalOperatingRevenue(NumberHelper.parseDouble(item.get(3)));
income.setOperatingRevenue(NumberHelper.parseDouble(item.get(4)));
income.setTotalOperatingCost(NumberHelper.parseDouble(item.get(5)));
income.setOperatingCost(NumberHelper.parseDouble(item.get(6)));
income.setSellingExpense(NumberHelper.parseDouble(item.get(7)));
income.setAdministrativeExpense(NumberHelper.parseDouble(item.get(8)));
income.setFinancialExpense(NumberHelper.parseDouble(item.get(9)));
income.setOperatingExpense(NumberHelper.parseDouble(item.get(10)));
income.setOperatingProfit(NumberHelper.parseDouble(item.get(11)));
income.setAddNonOperatingIncome(NumberHelper.parseDouble(item.get(12)));
income.setLessNonOperatingExpense(NumberHelper.parseDouble(item.get(13)));
income.setTotalProfit(NumberHelper.parseDouble(item.get(14)));
income.setIncomeTaxExpense(NumberHelper.parseDouble(item.get(15)));
income.setNetProfitIncludingMinorityInterest(NumberHelper.parseDouble(item.get(16)));
income.setNetProfitExcludingMinorityInterest(NumberHelper.parseDouble(item.get(17)));
income.setComprehensiveIncomeAttributableToParent(NumberHelper.parseDouble(item.get(18)));
income.setComprehensiveIncomeAttributableToMinorityShareholders(NumberHelper.parseDouble(item.get(19)));
income.setEarningsBeforeInterestAndTax(NumberHelper.parseDouble(item.get(20)));
income.setBeginningUndistributedProfit(NumberHelper.parseDouble(item.get(21)));
income.setDistributableProfit(NumberHelper.parseDouble(item.get(22)));
income.setResearchAndDevelopmentExpense(NumberHelper.parseDouble(item.get(23)));
income.setFinancialExpenseInterestExpense(NumberHelper.parseDouble(item.get(24)));
income.setNetProfitFromContinuingOperations(NumberHelper.parseDouble(item.get(25)));
income.setNetProfitFromDiscontinuedOperations(NumberHelper.parseDouble(item.get(26)));
incomeRepository.save(income);
}
response = tuShareService.balanceList(year);
for (List<String> item : response.data().items()) {
var code = item.get(0);
if (!stocksMap.containsKey(code)) {
continue;
}
var stock = stocksMap.get(code);
var balanceSheet = balanceSheetRepository.findOne(
QBalanceSheet.balanceSheet.year.eq(year)
.and(QBalanceSheet.balanceSheet.stock.code.eq(stock.getCode()))
).orElse(new BalanceSheet());
balanceSheet.setStock(stock);
balanceSheet.setYear(year);
balanceSheet.setEndingTotalShares(NumberHelper.parseDouble(item.get(1)));
balanceSheet.setCapitalSurplus(NumberHelper.parseDouble(item.get(2)));
balanceSheet.setUndistributedProfit(NumberHelper.parseDouble(item.get(3)));
balanceSheet.setMonetaryFunds(NumberHelper.parseDouble(item.get(4)));
balanceSheet.setAccountsReceivable(NumberHelper.parseDouble(item.get(5)));
balanceSheet.setInventories(NumberHelper.parseDouble(item.get(6)));
balanceSheet.setTotalCurrentAssets(NumberHelper.parseDouble(item.get(7)));
balanceSheet.setLongTermEquityInvestments(NumberHelper.parseDouble(item.get(8)));
balanceSheet.setLongTermReceivables(NumberHelper.parseDouble(item.get(9)));
balanceSheet.setFixedAssets(NumberHelper.parseDouble(item.get(10)));
balanceSheet.setResearchAndDevelopmentExpenditures(NumberHelper.parseDouble(item.get(11)));
balanceSheet.setGoodwill(NumberHelper.parseDouble(item.get(12)));
balanceSheet.setTotalNonCurrentAssets(NumberHelper.parseDouble(item.get(13)));
balanceSheet.setTotalAssets(NumberHelper.parseDouble(item.get(14)));
balanceSheet.setLongTermBorrowings(NumberHelper.parseDouble(item.get(15)));
balanceSheet.setShortTermBorrowings(NumberHelper.parseDouble(item.get(16)));
balanceSheet.setAccountsPayable(NumberHelper.parseDouble(item.get(17)));
balanceSheet.setAdvancesReceived(NumberHelper.parseDouble(item.get(18)));
balanceSheet.setTotalCurrentLiabilities(NumberHelper.parseDouble(item.get(19)));
balanceSheet.setTotalNonCurrentLiabilities(NumberHelper.parseDouble(item.get(20)));
balanceSheet.setTotalLiabilities(NumberHelper.parseDouble(item.get(21)));
balanceSheet.setTotalShareholdersEquityExcludingMinorityInterest(NumberHelper.parseDouble(item.get(22)));
balanceSheet.setTotalShareholdersEquityIncludingMinorityInterest(NumberHelper.parseDouble(item.get(23)));
balanceSheet.setTotalLiabilitiesAndShareholdersEquity(NumberHelper.parseDouble(item.get(24)));
balanceSheet.setAccountsReceivable(NumberHelper.parseDouble(item.get(25)));
balanceSheet.setPayables(NumberHelper.parseDouble(item.get(26)));
balanceSheet.setNotesAndAccountsReceivable(NumberHelper.parseDouble(item.get(27)));
balanceSheet.setNotesAndAccountsPayable(NumberHelper.parseDouble(item.get(28)));
balanceSheet.setOtherReceivablesTotal(NumberHelper.parseDouble(item.get(29)));
balanceSheet.setFixedAssetsTotal(NumberHelper.parseDouble(item.get(30)));
balanceSheetRepository.save(balanceSheet);
}
response = tuShareService.cashFlowList(year);
for (List<String> item : response.data().items()) {
var code = item.get(0);
if (!stocksMap.containsKey(code)) {
continue;
}
var stock = stocksMap.get(code);
var cashFlow = cashFlowRepository.findOne(
QCashFlow.cashFlow.year.eq(year)
.and(QCashFlow.cashFlow.stock.code.eq(stock.getCode()))
).orElse(new CashFlow());
NumberHelper.parseDouble(item.get(1));
cashFlow.setStock(stock);
cashFlow.setYear(year);
cashFlow.setNetProfit(NumberHelper.parseDouble(item.get(1)));
cashFlow.setFinancialExpense(NumberHelper.parseDouble(item.get(2)));
cashFlow.setCashReceivedFromSalesAndServices(NumberHelper.parseDouble(item.get(3)));
cashFlow.setSubtotalOfCashInflowsFromOperatingActivities(NumberHelper.parseDouble(item.get(4)));
cashFlow.setCashPaidToAndForEmployees(NumberHelper.parseDouble(item.get(5)));
cashFlow.setCashPaidForVariousTaxes(NumberHelper.parseDouble(item.get(6)));
cashFlow.setNetCashFlowFromOperatingActivities(NumberHelper.parseDouble(item.get(7)));
cashFlow.setSubtotalOfCashInflowsFromInvestingActivities(NumberHelper.parseDouble(item.get(8)));
cashFlow.setCashPaidForLongTermAssets(NumberHelper.parseDouble(item.get(9)));
cashFlow.setSubtotalOfCashOutflowsFromInvestingActivities(NumberHelper.parseDouble(item.get(10)));
cashFlow.setSubtotalOfCashOutflowsFromFinancingActivities(NumberHelper.parseDouble(item.get(11)));
cashFlow.setBeginningBalanceOfCashAndCashEquivalents(NumberHelper.parseDouble(item.get(12)));
cashFlowRepository.save(cashFlow);
}
setStep((year - 1990) * 100 / (currentYear - 1990));
}
}
}

View File

@@ -161,7 +161,7 @@ POST {{api_url}}
Content-Type: application/json
{
"api_name": "cashflow",
"api_name": "cashflow_vip",
"token": "{{api_key}}",
"params": {
"ts_code": "000002.SZ",
@@ -214,6 +214,7 @@ Content-Type: application/json
"period": "20191231"
},
"fields": [
"ts_code",
"current_ratio",
"quick_ratio",
"invturn_days",