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

@@ -1,95 +0,0 @@
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 + "balance_sheet")
public class BalanceSheet extends SimpleEntity {
@ManyToOne
private Stock stock;
@Comment("年报年度")
private Integer year;
@Comment("原始名称total_share描述期末总股本")
private Double endingTotalShares;
@Comment("原始名称cap_rese描述资本公积金")
private Double capitalSurplus;
@Comment("原始名称undistr_porfit描述未分配利润")
private Double undistributedProfit;
@Comment("原始名称money_cap描述货币资金")
private Double monetaryFunds;
@Comment("原始名称accounts_receiv描述应收账款")
private Double accountsReceivable;
@Comment("原始名称inventories描述存货")
private Double inventories;
@Comment("原始名称total_cur_assets描述流动资产合计")
private Double totalCurrentAssets;
@Comment("原始名称lt_eqt_invest描述长期股权投资")
private Double longTermEquityInvestments;
@Comment("原始名称lt_rec描述长期应收款")
private Double longTermReceivables;
@Comment("原始名称fix_assets描述固定资产")
private Double fixedAssets;
@Comment("原始名称r_and_d描述研发支出")
private Double researchAndDevelopmentExpenditures;
@Comment("原始名称goodwill描述商誉")
private Double goodwill;
@Comment("原始名称total_nca描述非流动资产合计")
private Double totalNonCurrentAssets;
@Comment("原始名称total_assets描述资产总计")
private Double totalAssets;
@Comment("原始名称lt_borr描述长期借款")
private Double longTermBorrowings;
@Comment("原始名称st_borr描述短期借款")
private Double shortTermBorrowings;
@Comment("原始名称acct_payable描述应付账款")
private Double accountsPayable;
@Comment("原始名称adv_receipts描述预收款项")
private Double advancesReceived;
@Comment("原始名称total_cur_liab描述流动负债合计")
private Double totalCurrentLiabilities;
@Comment("原始名称total_ncl描述非流动负债合计")
private Double totalNonCurrentLiabilities;
@Comment("原始名称total_liab描述负债合计")
private Double totalLiabilities;
@Comment("原始名称total_hldr_eqy_exc_min_int描述股东权益合计(不含少数股东权益)")
private Double totalShareholdersEquityExcludingMinorityInterest;
@Comment("原始名称total_hldr_eqy_inc_min_int描述股东权益合计(含少数股东权益)")
private Double totalShareholdersEquityIncludingMinorityInterest;
@Comment("原始名称total_liab_hldr_eqy描述负债及股东权益总计")
private Double totalLiabilitiesAndShareholdersEquity;
@Comment("原始名称acc_receivable描述应收款项")
private Double receivables;
@Comment("原始名称payables描述应付款项")
private Double payables;
@Comment("原始名称accounts_receiv_bill描述应收票据及应收账款")
private Double notesAndAccountsReceivable;
@Comment("原始名称accounts_pay描述应付票据及应付账款")
private Double notesAndAccountsPayable;
@Comment("原始名称oth_rcv_total描述其他应收款(合计)(元)")
private Double otherReceivablesTotal;
@Comment("原始名称fix_assets_total描述固定资产(合计)(元)")
private Double fixedAssetsTotal;
}

View File

@@ -1,61 +0,0 @@
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 + "cash_flow")
public class CashFlow extends SimpleEntity {
@ManyToOne
private Stock stock;
@Comment("年报年度")
private Integer year;
@Comment("原始名称net_profit描述净利润")
private Double netProfit;
@Comment("原始名称finan_exp描述财务费用")
private Double financialExpense;
@Comment("原始名称c_fr_sale_sg描述销售商品、提供劳务收到的现金")
private Double cashReceivedFromSalesAndServices;
@Comment("原始名称c_inf_fr_operate_a描述经营活动现金流入小计")
private Double subtotalOfCashInflowsFromOperatingActivities;
@Comment("原始名称c_paid_to_for_empl描述支付给职工以及为职工支付的现金")
private Double cashPaidToAndForEmployees;
@Comment("原始名称c_paid_for_taxes描述支付的各项税费")
private Double cashPaidForVariousTaxes;
@Comment("原始名称n_cashflow_act描述经营活动产生的现金流量净额")
private Double netCashFlowFromOperatingActivities;
@Comment("原始名称stot_inflows_inv_act描述投资活动现金流入小计")
private Double subtotalOfCashInflowsFromInvestingActivities;
@Comment("原始名称c_pay_acq_const_fiolta描述购置固定资产、无形资产和其他长期资产支付的现金")
private Double cashPaidForLongTermAssets;
@Comment("原始名称stot_out_inv_act描述投资活动现金流出小计")
private Double subtotalOfCashOutflowsFromInvestingActivities;
@Comment("原始名称stot_cashout_fnc_act描述筹资活动现金流出小计")
private Double subtotalOfCashOutflowsFromFinancingActivities;
@Comment("原始名称c_cash_equ_beg_period描述期初现金及现金等价物余额")
private Double beginningBalanceOfCashAndCashEquivalents;
@Comment("原始名称c_cash_equ_end_period描述期末现金及现金等价物余额")
private Double endingBalanceOfCashAndCashEquivalents;
}

View File

@@ -1,87 +0,0 @@
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 + "income")
public class Income extends SimpleEntity {
@ManyToOne
private Stock stock;
@Comment("年报年度")
private Integer year;
@Comment("原始名称basic_eps描述基本每股收益")
private Double basicEarningsPerShare;
@Comment("原始名称diluted_eps描述稀释每股收益")
private Double dilutedEarningsPerShare;
@Comment("原始名称total_revenue描述营业总收入")
private Double totalOperatingRevenue;
@Comment("原始名称revenue描述营业收入")
private Double operatingRevenue;
@Comment("原始名称total_cogs描述营业总成本")
private Double totalOperatingCost;
@Comment("原始名称oper_cost描述减:营业成本")
private Double operatingCost;
@Comment("原始名称sell_exp描述减:销售费用")
private Double sellingExpense;
@Comment("原始名称admin_exp描述减:管理费用")
private Double administrativeExpense;
@Comment("原始名称fin_exp描述减:财务费用")
private Double financialExpense;
@Comment("原始名称oper_exp描述营业支出")
private Double operatingExpense;
@Comment("原始名称operate_profit描述营业利润")
private Double operatingProfit;
@Comment("原始名称non_oper_income描述加:营业外收入")
private Double addNonOperatingIncome;
@Comment("原始名称non_oper_exp描述减:营业外支出")
private Double lessNonOperatingExpense;
@Comment("原始名称total_profit描述利润总额")
private Double totalProfit;
@Comment("原始名称income_tax描述所得税费用")
private Double incomeTaxExpense;
@Comment("原始名称n_income描述净利润(含少数股东损益)")
private Double netProfitIncludingMinorityInterest;
@Comment("原始名称n_income_attr_p描述净利润(不含少数股东损益)")
private Double netProfitExcludingMinorityInterest;
@Comment("原始名称compr_inc_attr_p描述归属于母公司(或股东)的综合收益总额")
private Double comprehensiveIncomeAttributableToParent;
@Comment("原始名称compr_inc_attr_m_s描述归属于少数股东的综合收益总额")
private Double comprehensiveIncomeAttributableToMinorityShareholders;
@Comment("原始名称ebit描述息税前利润")
private Double earningsBeforeInterestAndTax;
@Comment("原始名称undist_profit描述年初未分配利润")
private Double beginningUndistributedProfit;
@Comment("原始名称distable_profit描述可分配利润")
private Double distributableProfit;
@Comment("原始名称rd_exp描述研发费用")
private Double researchAndDevelopmentExpense;
@Comment("原始名称fin_exp_int_exp描述财务费用-利息费用")
private Double financialExpenseInterestExpense;
@Comment("原始名称continued_net_profit描述持续经营净利润")
private Double netProfitFromContinuingOperations;
@Comment("原始名称end_net_profit描述终止经营净利润")
private Double netProfitFromDiscontinuedOperations;
}

View File

@@ -61,18 +61,6 @@ public class Stock extends SimpleEntity {
@ToString.Exclude
private Set<Daily> dailies;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<Income> incomes;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<BalanceSheet> balanceSheets;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<CashFlow> cashFlows;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<FinanceIndicator> indicators;

View File

@@ -1,13 +0,0 @@
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.core.entity.BalanceSheet;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Repository;
/**
* @author lanyuanxiaoyao
* @version 20250911
*/
@Repository
public interface BalanceSheetRepository extends SimpleRepository<BalanceSheet> {
}

View File

@@ -1,13 +0,0 @@
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.core.entity.CashFlow;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Repository;
/**
* @author lanyuanxiaoyao
* @version 20250911
*/
@Repository
public interface CashFlowRepository extends SimpleRepository<CashFlow> {
}

View File

@@ -1,13 +0,0 @@
package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.core.entity.Income;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import org.springframework.stereotype.Repository;
/**
* @author lanyuanxiaoyao
* @version 20250911
*/
@Repository
public interface IncomeRepository extends SimpleRepository<Income> {
}

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",

View File

@@ -1,17 +1,6 @@
package com.lanyuanxiaoyao.leopard.strategy;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
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.repository.BalanceSheetRepository;
import com.lanyuanxiaoyao.leopard.core.repository.CashFlowRepository;
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
import com.lanyuanxiaoyao.leopard.core.repository.IncomeRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
@@ -30,12 +19,6 @@ public class StrategyApplication {
private StockRepository stockRepository;
@Resource
private DailyRepository dailyRepository;
@Resource
private BalanceSheetRepository balanceSheetRepository;
@Resource
private IncomeRepository incomeRepository;
@Resource
private CashFlowRepository cashFlowRepository;
public static void main(String[] args) {
SpringApplication.run(StrategyApplication.class, args);
@@ -44,85 +27,5 @@ public class StrategyApplication {
@Transactional(rollbackOn = Throwable.class)
@EventListener(ApplicationReadyEvent.class)
public void test() {
var code = "600132.SH";
for (int year = 2019; year <= 2019; year++) {
var balance = balanceSheetRepository.findOne(
QBalanceSheet.balanceSheet.stock.code.eq(code)
.and(QBalanceSheet.balanceSheet.year.eq(year))
).orElseThrow();
var income = incomeRepository.findOne(
QIncome.income.stock.code.eq(code)
.and(QIncome.income.year.eq(year))
).orElseThrow();
var cashflow = cashFlowRepository.findOne(
QCashFlow.cashFlow.stock.code.eq(code)
.and(QCashFlow.cashFlow.year.eq(year))
).orElseThrow();
log.info("{} {}", year, calculateFinanceIndicator(balance, income, cashflow));
}
}
private FinanceIndicator calculateFinanceIndicator(BalanceSheet balance, Income income, CashFlow cashflow) {
return new FinanceIndicator(
safeDiv(balance.getTotalLiabilities(), balance.getTotalAssets()),
safeDiv(balance.getTotalAssets(), balance.getTotalNonCurrentAssets()),
safeDiv(balance.getTotalCurrentAssets(), balance.getTotalCurrentLiabilities()),
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(360.0, safeDiv(income.getTotalOperatingCost(), balance.getInventories())),
safeDiv(income.getOperatingRevenue(), balance.getTotalAssets()),
safeDiv(income.getNetProfitIncludingMinorityInterest(), balance.getTotalShareholdersEquityExcludingMinorityInterest())
);
}
private Double safePlus(Double a, Double b) {
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b)) {
return null;
}
return a + b;
}
private Double safeMinus(Double a, Double b) {
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b)) {
return null;
}
return a - b;
}
private Double safeDiv(Double a, Double b) {
if (ObjectUtil.isNull(a) || ObjectUtil.isNull(b) || b == 0) {
return null;
}
return NumberUtil.div(a, b, 4);
}
public record FinanceIndicator(
Double debtToAssetRatio,
Double longTermFundsToRealEstateRatio,
Double currentRatio,
Double quickRatio,
Double accountsReceivableTurnoverRate,
Double averageCashCollectionDays,
Double inventoryTurnoverRate,
Double averageSalesDays,
Double totalAssetTurnover,
Double roe
) {
@Override
public String toString() {
return "负债占资产比率=" + debtToAssetRatio +
", 长期资金占固定资产比率=" + longTermFundsToRealEstateRatio +
", 流动比率=" + currentRatio +
", 速动比率=" + quickRatio +
", 应收账款周转率=" + accountsReceivableTurnoverRate +
", 平均现金回收天数=" + averageCashCollectionDays +
", 库存周转率=" + inventoryTurnoverRate +
", 平均销货天数=" + averageSalesDays +
", 总资产周转率=" + totalAssetTurnover +
", 股东权益报酬率(ROE)=" + roe +
';';
}
}
}

View File

@@ -2,10 +2,11 @@ import React from 'react'
import {useParams} from 'react-router'
import {amisRender, commonInfo, readOnlyDialogOptions, remoteMappings} from '../../util/amis.tsx'
import type {Schema} from 'amis'
import {isNil} from 'es-toolkit'
// 格式化财务数字显示的公共函数
const formatFinanceNumber = (value: number): string => {
if (value === null || value === undefined) {
if (isNil(value)) {
return '-'
}
@@ -24,7 +25,17 @@ const formatFinanceNumber = (value: number): string => {
return isNegative ? `-${formatted}` : formatted
}
const financePropertyLabel = (id: string | undefined, label: string, type: string, field: string): Schema => {
// 格式化百分比数字显示的公共函数
const formatPercentageNumber = (value: number): string => {
if (isNil(value)) {
return '-'
}
return `${(value * 100).toFixed(2)}%`
}
type FinanceType = 'PERCENTAGE' | 'FINANCE'
const financePropertyLabel = (id: string | undefined, label: string, type: FinanceType, field: string): Schema => {
if (!id) {
return {
type: 'tpl',
@@ -32,6 +43,7 @@ const financePropertyLabel = (id: string | undefined, label: string, type: strin
}
}
let current = new Date().getFullYear()
let formatter: (value: number) => string = type === 'PERCENTAGE' ? formatPercentageNumber : formatFinanceNumber
return {
type: 'wrapper',
size: 'none',
@@ -54,7 +66,7 @@ const financePropertyLabel = (id: string | undefined, label: string, type: strin
...readOnlyDialogOptions(),
body: {
type: 'chart',
api: `get:${commonInfo.baseUrl}/stock/finance/${id}/${type}/${field}`,
api: `get:${commonInfo.baseUrl}/stock/finance/${id}/${field}`,
height: 500,
config: {
tooltip: {
@@ -68,7 +80,7 @@ const financePropertyLabel = (id: string | undefined, label: string, type: strin
padding: [10, 15],
formatter: (params: any) => {
const item = params[0]
return `${item.name}<br/>${item.marker}${formatFinanceNumber(item.value)}`
return `${item.name}<br/>${item.marker}${formatter(item.value)}`
},
},
grid: {
@@ -116,7 +128,7 @@ const financePropertyLabel = (id: string | undefined, label: string, type: strin
color: '#999',
fontSize: 12,
formatter: (value: number) => {
return formatFinanceNumber(value)
return formatter(value)
},
},
axisTick: {
@@ -163,7 +175,7 @@ const financePropertyLabel = (id: string | undefined, label: string, type: strin
fontWeight: 'bold',
fontSize: 12,
formatter: (params: any) => {
return formatFinanceNumber(params.value)
return formatter(params.value)
},
},
},
@@ -212,30 +224,49 @@ function StockDetail() {
{
className: 'my-2',
type: 'property',
column: 4,
items: [
{
label: financePropertyLabel(id, '总资产', 'balanceSheet', 'totalAssets'),
label: financePropertyLabel(id, '总资产', 'FINANCE', 'totalAssets'),
content: '${balanceSheet.totalAssets}',
span: 4,
},
{
label: financePropertyLabel(id, '流动资产', 'balanceSheet', 'totalCurrentAssets'),
content: '${balanceSheet.totalCurrentAssets}',
label: financePropertyLabel(id, '流动资产', 'FINANCE', 'currentAssets'),
content: '${balanceSheet.currentAssets}',
},
{
label: financePropertyLabel(id, '流动资产', 'balanceSheet', 'totalNonCurrentAssets'),
content: '${balanceSheet.totalNonCurrentAssets}',
label: financePropertyLabel(id, '流动资产占比', 'PERCENTAGE', 'currentAssetsToTotalAssetsRatio'),
content: '${balanceSheet.currentAssetsRatio}',
},
{
label: financePropertyLabel(id, '总负债', 'balanceSheet', 'totalLiabilities'),
label: financePropertyLabel(id, '非流动资产', 'FINANCE', 'fixedAssets'),
content: '${balanceSheet.fixedAssets}',
},
{
label: financePropertyLabel(id, '非流动资产占比', 'PERCENTAGE', 'fixedAssetsToTotalAssetsRatio'),
content: '${balanceSheet.fixedAssetsRatio}',
},
{
label: financePropertyLabel(id, '总负债', 'FINANCE', 'totalLiabilities'),
content: '${balanceSheet.totalLiabilities}',
span: 4,
},
{
label: financePropertyLabel(id, '流动负债', 'balanceSheet', 'totalCurrentLiabilities'),
content: '${balanceSheet.totalCurrentLiabilities}',
label: financePropertyLabel(id, '流动负债', 'FINANCE', 'currentLiabilities'),
content: '${balanceSheet.currentLiabilities}',
},
{
label: financePropertyLabel(id, '流动负债', 'balanceSheet', 'totalNonCurrentLiabilities'),
content: '${balanceSheet.totalNonCurrentLiabilities}',
label: financePropertyLabel(id, '流动负债占比', 'PERCENTAGE', 'currentLiabilitiesToTotalAssetsRatio'),
content: '${balanceSheet.currentLiabilitiesRatio}',
},
{
label: financePropertyLabel(id, '非流动负债', 'FINANCE', 'longTermLiabilities'),
content: '${balanceSheet.longTermLiabilities}',
},
{
label: financePropertyLabel(id, '非流动负债占比', 'PERCENTAGE', 'longTermLiabilitiesToTotalAssetsRatio'),
content: '${balanceSheet.longTermLiabilitiesRatio}',
},
],
},
@@ -245,16 +276,16 @@ function StockDetail() {
type: 'property',
items: [
{
label: financePropertyLabel(id, '营业收入', 'income', 'totalOperatingRevenue'),
content: '${income.totalOperatingRevenue}',
label: financePropertyLabel(id, '营业收入', 'FINANCE', 'operatingRevenue'),
content: '${income.operatingRevenue}',
},
{
label: financePropertyLabel(id, '营业成本', 'income', 'totalOperatingCost'),
content: '${income.totalOperatingCost}',
label: financePropertyLabel(id, '营业成本', 'FINANCE', 'operatingCost'),
content: '${income.operatingCost}',
},
{
label: financePropertyLabel(id, '营业利润', 'income', 'totalProfit'),
content: '${income.totalProfit}',
label: financePropertyLabel(id, '营业利润', 'FINANCE', 'operatingProfit'),
content: '${income.operatingProfit}',
},
],
},
@@ -264,7 +295,7 @@ function StockDetail() {
type: 'property',
items: [
{
label: financePropertyLabel(id, '净利润', 'cashflow', 'netProfit'),
label: financePropertyLabel(id, '净利润', 'FINANCE', 'netProfit'),
content: '${cashFlow.netProfit}',
},
],