1
0

feat: 增加新的财务指标采集模式

This commit is contained in:
2025-09-14 23:17:04 +08:00
parent 7fa524b8d5
commit 4cc7d2344f
8 changed files with 399 additions and 12 deletions

View File

@@ -30,4 +30,25 @@ public class NumberHelper {
var result = parseDouble(value);
return ObjectUtil.isNull(result) ? null : ifSuccess.apply(result);
}
public static Double safePlus(Double a, Double b) {
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b)) {
return null;
}
return a + b;
}
public static Double safeMinus(Double a, Double b) {
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b)) {
return null;
}
return a - b;
}
public static Double safeDiv(Double a, Double b) {
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b) || b == 0) {
return null;
}
return NumberUtil.div(a, b, 4);
}
}

View File

@@ -3,9 +3,11 @@ package com.lanyuanxiaoyao.leopard.server.service;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -224,6 +226,24 @@ public class TuShareService {
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);
if (tuShareResponse.code != 0) {
throw new RuntimeException(tuShareResponse.message);
}
var data = tuShareResponse.data;
var result = new ArrayList<Map<String, String>>();
for (var item : data.items) {
var map = new HashMap<String, String>();
for (int i = 0; i < data.fields.size(); i++) {
map.put(data.fields.get(i), item.get(i));
}
result.add(map);
}
return result;
}
public record TuShareResponse(
Integer code,
@JsonProperty("msg")

View File

@@ -0,0 +1,174 @@
package com.lanyuanxiaoyao.leopard.server.service.task;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.repository.FinanceIndicatorRepository;
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.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@LiteflowComponent("update_finance_indicators")
public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
private final FinanceIndicatorRepository financeIndicatorRepository;
private final StockRepository stockRepository;
private final TuShareService tuShareService;
protected UpdateFinanceIndicatorNode(TaskService taskService, FinanceIndicatorRepository financeIndicatorRepository, StockRepository stockRepository, TuShareService tuShareService) {
super(taskService);
this.financeIndicatorRepository = financeIndicatorRepository;
this.stockRepository = stockRepository;
this.tuShareService = tuShareService;
}
@Override
public void process() throws Exception {
var stocks = stockRepository.findAll();
var currentYear = LocalDate.now().getYear();
for (int year = 1990; year < currentYear; year++) {
var balances = tuShareService.request(
"balancesheet_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
List.of(
"ts_code",
"total_share",
"cap_rese",
"surplus_rese",
"undistr_porfit",
"cash_reser_cb",
"accounts_receiv_bill",
"accounts_pay",
"inventories",
"goodwill",
"total_cur_assets",
"total_nca",
"total_cur_liab",
"total_ncl",
"total_liab",
"total_hldr_eqy_inc_min_int",
"total_assets"
)
);
var balancesMap = balances.stream().collect(Collectors.toMap(balance -> balance.get("ts_code"), balance -> balance));
var incomes = tuShareService.request(
"income_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
List.of(
"ts_code",
"total_revenue",
"total_cogs",
"operate_profit",
"oper_exp",
"n_income"
)
);
var incomesMap = incomes.stream().collect(Collectors.toMap(income -> income.get("ts_code"), income -> income));
var cashFlows = tuShareService.request(
"cashflow_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
List.of(
"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 finaIndicators = tuShareService.request(
"fina_indicator_vip",
Map.of("period", LocalDate.of(year, 12, 31).format(TuShareService.TRADE_FORMAT)),
List.of(
"ca_to_assets",
"nca_to_assets",
"currentdebt_to_debt",
"longdeb_to_debt",
"current_ratio",
"quick_ratio",
"ar_turn",
"arturn_days",
"inv_turn",
"invturn_days",
"fa_turn",
"assets_turn",
"roe_dt",
"roa",
"roa_dp",
"total_revenue_ps"
)
);
var finaIndicatorsMap = finaIndicators.stream().collect(Collectors.toMap(finaIndicator -> finaIndicator.get("ts_code"), finaIndicator -> finaIndicator));
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();
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")));
financeIndicatorRepository.save(indicator);
}
}
}
}

View File

@@ -197,10 +197,8 @@ Content-Type: application/json
"period": "20191231"
},
"fields": [
"accounts_receiv",
"accounts_receiv_bill",
"oth_rcv_total",
"acc_receivable"
"total_liab_hldr_eqy",
"total_assets"
]
}
@@ -212,13 +210,15 @@ Content-Type: application/json
"api_name": "fina_indicator",
"token": "{{api_key}}",
"params": {
"ts_code": "000002.SZ",
"period": "20231231"
"ts_code": "600132.SH",
"period": "20191231"
},
"fields": [
"ann_date",
"ocf_to_shortdebt",
"currentdebt_to_debt"
"current_ratio",
"quick_ratio",
"invturn_days",
"arturn_days",
"ar_turn"
]
}