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

@@ -0,0 +1,150 @@
package com.lanyuanxiaoyao.leopard.core.entity;
import com.lanyuanxiaoyao.leopard.core.Constants;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Setter
@Getter
@ToString(callSuper = true)
@FieldNameConstants
@Entity
@DynamicUpdate
@DynamicInsert
@EntityListeners(AuditingEntityListener.class)
@Table(name = Constants.DATABASE_PREFIX + "finance_indicator")
public class FinanceIndicator extends SimpleEntity {
@ManyToOne
private Stock stock;
@Comment("年报年度")
private Integer year;
@Comment("总股本")
private Double totalShareCapital;
@Comment("资本公积金")
private Double capitalSurplus;
@Comment("盈余公积金")
private Double surplusReserve;
@Comment("未分配利润")
private Double undistributedProfit;
@Comment("现金及现金等价物")
private Double cashAndCashEquivalents;
@Comment("现金及现金等价物占总资产比率")
private Double cashAndCashEquivalentsToTotalAssetsRatio;
@Comment("应收账款")
private Double accountsReceivable;
@Comment("应收账款占总资产比率")
private Double accountsReceivableToTotalAssetsRatio;
@Comment("应付账款")
private Double accountsPayable;
@Comment("应付账款占总资产比率")
private Double accountsPayableToTotalAssetsRatio;
@Comment("存货")
private Double inventory;
@Comment("存货占总资产比率")
private Double inventoryToTotalAssetsRatio;
@Comment("商誉")
private Double goodwill;
@Comment("商誉占总资产比率")
private Double goodwillToTotalAssetsRatio;
@Comment("流动资产")
private Double currentAssets;
@Comment("流动资产占总资产比率")
private Double currentAssetsToTotalAssetsRatio;
@Comment("固定资产")
private Double fixedAssets;
@Comment("固定资产占总资产比率")
private Double fixedAssetsToTotalAssetsRatio;
@Comment("流动负债")
private Double currentLiabilities;
@Comment("流动负债占总资产比率")
private Double currentLiabilitiesToTotalAssetsRatio;
@Comment("流动负债占总负债比率")
private Double currentLiabilitiesToTotalLiabilitiesRatio;
@Comment("长期负债")
private Double longTermLiabilities;
@Comment("长期负债占总资产比率")
private Double longTermLiabilitiesToTotalAssetsRatio;
@Comment("长期负债占总负债比率")
private Double longTermLiabilitiesToTotalLiabilitiesRatio;
@Comment("总负债")
private Double totalLiabilities;
@Comment("负债占总资产比率")
private Double liabilitiesToTotalAssetsRatio;
@Comment("股东权益")
private Double shareholdersEquity;
@Comment("股东权益占总资产比率")
private Double shareholdersEquityToTotalAssetsRatio;
@Comment("总资产")
private Double totalAssets;
@Comment("营业收入")
private Double operatingRevenue;
@Comment("营业成本")
private Double operatingCost;
@Comment("营业利润")
private Double operatingProfit;
@Comment("营业支出")
private Double operatingExpenses;
@Comment("净利润")
private Double netProfit;
@Comment("经营活动现金流净额")
private Double netCashFlowFromOperatingActivities;
@Comment("营业活动现金流量")
private Double cashFlowFromOperatingActivities;
@Comment("投资活动现金流量")
private Double cashFlowFromInvestingActivities;
@Comment("筹资活动现金流量")
private Double cashFlowFromFinancingActivities;
@Comment("流动比率")
private Double currentRatio;
@Comment("速动比率")
private Double quickRatio;
@Comment("长期资金占固定资产比率")
private Double longTermFundsToFixedAssetsRatio;
@Comment("应收账款周转率")
private Double accountsReceivableTurnover;
@Comment("应收账款周转天数(平均收现天数)")
private Double daysAccountsReceivableTurnover;
@Comment("存货周转率")
private Double inventoryTurnover;
@Comment("存货周转天数(平均销货天数)")
private Double daysInventoryTurnover;
@Comment("固定资产周转率")
private Double fixedAssetsTurnover;
@Comment("固定资产周转天数")
private Double daysFixedAssetsTurnover;
@Comment("总资产周转率")
private Double totalAssetsTurnover;
@Comment("总资产周转天数")
private Double daysTotalAssetsTurnover;
@Comment("ROE")
private Double returnOnEquity;
@Comment("ROA")
private Double returnOnAssets;
@Comment("营业毛利率")
private Double operatingGrossProfitMargin;
@Comment("营业利益率")
private Double operatingProfitMargin;
@Comment("经营安全边际率")
private Double operatingSafetyMarginRatio;
@Comment("净利率")
private Double netProfitMargin;
@Comment("每股盈余")
private Double earningsPerShare;
@Comment("现金流量比率")
private Double cashFlowRatio;
@Comment("现金流量允当比率")
private Double cashFlowAdequacyRatio;
@Comment("现金再投资比率")
private Double cashReinvestmentRatio;
}

View File

@@ -73,6 +73,10 @@ public class Stock extends SimpleEntity {
@ToString.Exclude
private Set<CashFlow> cashFlows;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<FinanceIndicator> indicators;
@Getter
@AllArgsConstructor
public enum Market implements SimpleEnum {

View File

@@ -0,0 +1,9 @@
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface FinanceIndicatorRepository extends SimpleRepository<FinanceIndicator> {
}

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"
]
}

View File

@@ -44,7 +44,7 @@ public class StrategyApplication {
@Transactional(rollbackOn = Throwable.class)
@EventListener(ApplicationReadyEvent.class)
public void test() {
var code = "000799.SZ";
var code = "600132.SH";
for (int year = 2019; year <= 2019; year++) {
var balance = balanceSheetRepository.findOne(
QBalanceSheet.balanceSheet.stock.code.eq(code)
@@ -70,7 +70,10 @@ public class StrategyApplication {
safeDiv(safeMinus(balance.getTotalCurrentAssets(), balance.getInventories()), balance.getTotalCurrentLiabilities()),
safeDiv(income.getOperatingRevenue(), balance.getNotesAndAccountsReceivable()),
safeDiv(360.0, safeDiv(income.getOperatingRevenue(), balance.getNotesAndAccountsReceivable())),
safeDiv(income.getTotalOperatingCost(), balance.getInventories())
safeDiv(income.getTotalOperatingCost(), balance.getInventories()),
safeDiv(360.0, safeDiv(income.getTotalOperatingCost(), balance.getInventories())),
safeDiv(income.getOperatingRevenue(), balance.getTotalAssets()),
safeDiv(income.getNetProfitIncludingMinorityInterest(), balance.getTotalShareholdersEquityExcludingMinorityInterest())
);
}
@@ -102,7 +105,10 @@ public class StrategyApplication {
Double quickRatio,
Double accountsReceivableTurnoverRate,
Double averageCashCollectionDays,
Double inventoryTurnoverRate
Double inventoryTurnoverRate,
Double averageSalesDays,
Double totalAssetTurnover,
Double roe
) {
@Override
public String toString() {
@@ -113,6 +119,9 @@ public class StrategyApplication {
", 应收账款周转率=" + accountsReceivableTurnoverRate +
", 平均现金回收天数=" + averageCashCollectionDays +
", 库存周转率=" + inventoryTurnoverRate +
", 平均销货天数=" + averageSalesDays +
", 总资产周转率=" + totalAssetTurnover +
", 股东权益报酬率(ROE)=" + roe +
';';
}
}