Compare commits
4 Commits
17c96e96fc
...
d2b3305ca6
| Author | SHA1 | Date | |
|---|---|---|---|
| d2b3305ca6 | |||
| 10a0e14024 | |||
| a9621a10ac | |||
| b5688bd3ab |
@@ -2,8 +2,12 @@ package com.lanyuanxiaoyao.leopard.core.repository;
|
|||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||||
|
import com.querydsl.core.types.Predicate;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@@ -14,4 +18,12 @@ public interface DailyRepository extends SimpleRepository<Daily> {
|
|||||||
|
|
||||||
@Query("select distinct daily.tradeDate from Daily daily where daily.stock.id = ?1")
|
@Query("select distinct daily.tradeDate from Daily daily where daily.stock.id = ?1")
|
||||||
List<LocalDate> findDistinctTradeDateByStockId(Long stockId);
|
List<LocalDate> findDistinctTradeDateByStockId(Long stockId);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = {"stock"})
|
||||||
|
@Override
|
||||||
|
Optional<Daily> findOne(Predicate predicate);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = {"stock"})
|
||||||
|
@Override
|
||||||
|
List<Daily> findAll(Predicate predicate, Sort sort);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,20 @@ package com.lanyuanxiaoyao.leopard.core.repository;
|
|||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
|
||||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||||
|
import com.querydsl.core.types.Predicate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface FinanceIndicatorRepository extends SimpleRepository<FinanceIndicator> {
|
public interface FinanceIndicatorRepository extends SimpleRepository<FinanceIndicator> {
|
||||||
|
@EntityGraph(attributePaths = {"stock"})
|
||||||
|
@Override
|
||||||
|
Optional<FinanceIndicator> findOne(Predicate predicate);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = {"stock"})
|
||||||
|
@Override
|
||||||
|
List<FinanceIndicator> findAll(Predicate predicate, Sort sort);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package com.lanyuanxiaoyao.leopard.core.repository;
|
|||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@@ -14,4 +17,11 @@ import org.springframework.stereotype.Repository;
|
|||||||
public interface StockRepository extends SimpleRepository<Stock> {
|
public interface StockRepository extends SimpleRepository<Stock> {
|
||||||
@Query("select distinct stock.industry from Stock stock where stock.industry is not null")
|
@Query("select distinct stock.industry from Stock stock where stock.industry is not null")
|
||||||
List<String> findDistinctIndustries();
|
List<String> findDistinctIndustries();
|
||||||
|
|
||||||
|
@Query("select distinct stock.code from Stock stock")
|
||||||
|
List<String> findDistinctCodes();
|
||||||
|
|
||||||
|
@Modifying
|
||||||
|
@Transactional(rollbackOn = Throwable.class)
|
||||||
|
void deleteAllByCodeIn(Collection<String> code);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,21 +8,20 @@ import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
|||||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||||
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
|
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
|
||||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
|
||||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||||
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
import com.yomahub.liteflow.annotation.LiteflowComponent;
|
||||||
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@LiteflowComponent("update_daily")
|
@LiteflowComponent("update_daily")
|
||||||
public class UpdateDailyNode extends TaskNodeComponent {
|
public class UpdateDailyNode extends NodeComponent {
|
||||||
private final StockRepository stockRepository;
|
private final StockRepository stockRepository;
|
||||||
private final DailyRepository dailyRepository;
|
private final DailyRepository dailyRepository;
|
||||||
|
|
||||||
@@ -30,8 +29,7 @@ public class UpdateDailyNode extends TaskNodeComponent {
|
|||||||
|
|
||||||
private final TransactionTemplate transactionTemplate;
|
private final TransactionTemplate transactionTemplate;
|
||||||
|
|
||||||
public UpdateDailyNode(TaskService taskService, StockRepository stockRepository, DailyRepository dailyRepository, TuShareService tuShareService, TransactionTemplate transactionTemplate) {
|
public UpdateDailyNode(StockRepository stockRepository, DailyRepository dailyRepository, TuShareService tuShareService, TransactionTemplate transactionTemplate) {
|
||||||
super(taskService);
|
|
||||||
this.stockRepository = stockRepository;
|
this.stockRepository = stockRepository;
|
||||||
this.dailyRepository = dailyRepository;
|
this.dailyRepository = dailyRepository;
|
||||||
this.tuShareService = tuShareService;
|
this.tuShareService = tuShareService;
|
||||||
@@ -51,15 +49,11 @@ public class UpdateDailyNode extends TaskNodeComponent {
|
|||||||
}
|
}
|
||||||
var existsTradeDates = dailyRepository.findDistinctTradeDate();
|
var existsTradeDates = dailyRepository.findDistinctTradeDate();
|
||||||
var nowDate = LocalDate.now();
|
var nowDate = LocalDate.now();
|
||||||
var stocks = stockRepository.findAll();
|
var stocksMap = stockRepository.findAll().stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||||
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
tradeDates.parallelStream()
|
||||||
var allTradeDates = tradeDates.stream()
|
|
||||||
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
|
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
|
||||||
.filter(date -> !existsTradeDates.contains(date))
|
.filter(date -> !existsTradeDates.contains(date))
|
||||||
.sorted()
|
.filter(date -> date.isAfter(LocalDate.of(2024, 12, 31)))
|
||||||
.toList();
|
|
||||||
var total = new AtomicInteger(allTradeDates.size());
|
|
||||||
allTradeDates.parallelStream()
|
|
||||||
.forEach(tradeDate -> {
|
.forEach(tradeDate -> {
|
||||||
var factorResponse = tuShareService.factorList(tradeDate);
|
var factorResponse = tuShareService.factorList(tradeDate);
|
||||||
var factorMap = new HashMap<String, Double>();
|
var factorMap = new HashMap<String, Double>();
|
||||||
|
|||||||
@@ -126,6 +126,13 @@ public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
|
|||||||
(existing, replacement) -> existing
|
(existing, replacement) -> existing
|
||||||
));
|
));
|
||||||
|
|
||||||
|
var financeIndicatorsMap = financeIndicatorRepository.findAll(QFinanceIndicator.financeIndicator.year.eq(year))
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
indicator -> indicator.getStock().getCode(),
|
||||||
|
indicator -> indicator
|
||||||
|
));
|
||||||
|
|
||||||
for (Stock stock : stocks) {
|
for (Stock stock : stocks) {
|
||||||
var balance = balancesMap.get(stock.getCode());
|
var balance = balancesMap.get(stock.getCode());
|
||||||
var income = incomesMap.get(stock.getCode());
|
var income = incomesMap.get(stock.getCode());
|
||||||
@@ -134,10 +141,7 @@ public class UpdateFinanceIndicatorNode extends TaskNodeComponent {
|
|||||||
if (ArrayUtil.<Object>isAllNull(balance, income, cashFlow, finaIndicator)) {
|
if (ArrayUtil.<Object>isAllNull(balance, income, cashFlow, finaIndicator)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var indicator = financeIndicatorRepository.findOne(
|
var indicator = financeIndicatorsMap.getOrDefault(stock.getCode(), new FinanceIndicator());
|
||||||
QFinanceIndicator.financeIndicator.stock.id.eq(stock.getId())
|
|
||||||
.and(QFinanceIndicator.financeIndicator.year.eq(year))
|
|
||||||
).orElse(new FinanceIndicator());
|
|
||||||
indicator.setStock(stock);
|
indicator.setStock(stock);
|
||||||
indicator.setYear(year);
|
indicator.setYear(year);
|
||||||
if (ObjectUtil.isNotNull(balance)) {
|
if (ObjectUtil.isNotNull(balance)) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.yomahub.liteflow.annotation.LiteflowComponent;
|
|||||||
import com.yomahub.liteflow.core.NodeComponent;
|
import com.yomahub.liteflow.core.NodeComponent;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@LiteflowComponent("update_stock")
|
@LiteflowComponent("update_stock")
|
||||||
@@ -25,44 +24,32 @@ public class UpdateStockNode extends NodeComponent {
|
|||||||
@Transactional(rollbackOn = Throwable.class)
|
@Transactional(rollbackOn = Throwable.class)
|
||||||
@Override
|
@Override
|
||||||
public void process() {
|
public void process() {
|
||||||
var stocks = stockRepository.findAll();
|
var existsStockMap = stockRepository.findAll().stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
||||||
var stocksMap = stocks.stream().collect(Collectors.toMap(Stock::getCode, stock -> stock));
|
var stocks = tuShareService.stockList()
|
||||||
var targetCodes = new HashSet<String>();
|
|
||||||
tuShareService.stockList()
|
|
||||||
.data()
|
.data()
|
||||||
.items()
|
.items()
|
||||||
.forEach(item -> {
|
.stream()
|
||||||
|
.map(item -> {
|
||||||
var code = item.get(0);
|
var code = item.get(0);
|
||||||
var name = item.get(1);
|
var name = item.get(1);
|
||||||
var fullname = item.get(2);
|
var fullname = item.get(2);
|
||||||
var market = EnumUtil.fromString(Stock.Market.class, item.get(3));
|
var market = EnumUtil.fromString(Stock.Market.class, item.get(3));
|
||||||
var industry = item.get(4);
|
var industry = item.get(4);
|
||||||
var listedDate = LocalDate.parse(item.get(5), TuShareService.TRADE_FORMAT);
|
var listedDate = LocalDate.parse(item.get(5), TuShareService.TRADE_FORMAT);
|
||||||
if (stocksMap.containsKey(code)) {
|
var stock = existsStockMap.getOrDefault(code, new Stock());
|
||||||
var stock = stocksMap.get(code);
|
stock.setCode(code);
|
||||||
stock.setName(name);
|
stock.setName(name);
|
||||||
stock.setFullname(fullname);
|
stock.setFullname(fullname);
|
||||||
stock.setMarket(market);
|
stock.setMarket(market);
|
||||||
stock.setIndustry(industry);
|
stock.setIndustry(industry);
|
||||||
stock.setListedDate(listedDate);
|
stock.setListedDate(listedDate);
|
||||||
} else {
|
return stock;
|
||||||
var stock = new Stock();
|
})
|
||||||
stock.setCode(code);
|
|
||||||
stock.setName(name);
|
|
||||||
stock.setFullname(fullname);
|
|
||||||
stock.setMarket(market);
|
|
||||||
stock.setIndustry(industry);
|
|
||||||
stock.setListedDate(listedDate);
|
|
||||||
stocks.add(stock);
|
|
||||||
}
|
|
||||||
targetCodes.add(code);
|
|
||||||
});
|
|
||||||
var deleteStocks = stocks.stream()
|
|
||||||
.filter(stock -> !targetCodes.contains(stock.getCode()))
|
|
||||||
.map(Stock::getId)
|
|
||||||
.toList();
|
.toList();
|
||||||
stockRepository.deleteByIds(deleteStocks);
|
var currentCodes = stocks.stream().map(Stock::getCode).toList();
|
||||||
|
var existsCodes = stockRepository.findDistinctCodes();
|
||||||
|
var deleteCodes = existsCodes.stream().filter(code -> !currentCodes.contains(code)).toList();
|
||||||
|
stockRepository.deleteAllByCodeIn(deleteCodes);
|
||||||
stockRepository.saveAll(stocks);
|
stockRepository.saveAll(stocks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {useParams} from 'react-router'
|
|||||||
import {amisRender, commonInfo, readOnlyDialogOptions, remoteMappings} from '../../util/amis.tsx'
|
import {amisRender, commonInfo, readOnlyDialogOptions, remoteMappings} from '../../util/amis.tsx'
|
||||||
import type {Schema} from 'amis'
|
import type {Schema} from 'amis'
|
||||||
import {isNil} from 'es-toolkit'
|
import {isNil} from 'es-toolkit'
|
||||||
|
import {toNumber} from 'es-toolkit/compat'
|
||||||
|
|
||||||
const formatFinanceNumber = (value: number): string => {
|
const formatFinanceNumber = (value: number): string => {
|
||||||
if (isNil(value)) {
|
if (isNil(value)) {
|
||||||
@@ -280,8 +281,8 @@ function StockDetail() {
|
|||||||
content: '${balanceSheet.currentAssets}',
|
content: '${balanceSheet.currentAssets}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: financePropertyLabel(id, '非流动资产占比', 'PERCENTAGE', 'fixedAssetsToTotalAssetsRatio'),
|
label: financePropertyLabel(id, '流动资产占比', 'PERCENTAGE', 'currentAssetsToTotalAssetsRatio'),
|
||||||
content: '${balanceSheet.fixedAssetsRatio}',
|
content: '${balanceSheet.currentAssetsRatio}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: financePropertyLabel(id, '流动负债', 'FINANCE', 'currentLiabilities'),
|
label: financePropertyLabel(id, '流动负债', 'FINANCE', 'currentLiabilities'),
|
||||||
@@ -291,14 +292,14 @@ function StockDetail() {
|
|||||||
label: financePropertyLabel(id, '流动负债占比', 'PERCENTAGE', 'currentLiabilitiesToTotalAssetsRatio'),
|
label: financePropertyLabel(id, '流动负债占比', 'PERCENTAGE', 'currentLiabilitiesToTotalAssetsRatio'),
|
||||||
content: '${balanceSheet.currentLiabilitiesRatio}',
|
content: '${balanceSheet.currentLiabilitiesRatio}',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: financePropertyLabel(id, '流动资产占比', 'PERCENTAGE', 'currentAssetsToTotalAssetsRatio'),
|
|
||||||
content: '${balanceSheet.currentAssetsRatio}',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: financePropertyLabel(id, '非流动资产', 'FINANCE', 'fixedAssets'),
|
label: financePropertyLabel(id, '非流动资产', 'FINANCE', 'fixedAssets'),
|
||||||
content: '${balanceSheet.fixedAssets}',
|
content: '${balanceSheet.fixedAssets}',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: financePropertyLabel(id, '非流动资产占比', 'PERCENTAGE', 'fixedAssetsToTotalAssetsRatio'),
|
||||||
|
content: '${balanceSheet.fixedAssetsRatio}',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: financePropertyLabel(id, '非流动负债', 'FINANCE', 'longTermLiabilities'),
|
label: financePropertyLabel(id, '非流动负债', 'FINANCE', 'longTermLiabilities'),
|
||||||
content: '${balanceSheet.longTermLiabilities}',
|
content: '${balanceSheet.longTermLiabilities}',
|
||||||
@@ -409,10 +410,10 @@ function StockDetail() {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{type: 'divider'},
|
{type: 'divider'},
|
||||||
"100天日线数据",
|
"100日线数据",
|
||||||
{
|
{
|
||||||
type: 'chart',
|
type: 'chart',
|
||||||
height: 800,
|
height: 500,
|
||||||
api: `get:${commonInfo.baseUrl}/stock/daily/${id}`,
|
api: `get:${commonInfo.baseUrl}/stock/daily/${id}`,
|
||||||
config: {
|
config: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
@@ -433,30 +434,28 @@ function StockDetail() {
|
|||||||
padding: 12,
|
padding: 12,
|
||||||
formatter: function (params: any) {
|
formatter: function (params: any) {
|
||||||
const param = params[0]
|
const param = params[0]
|
||||||
const open = param.data[0]
|
const open = toNumber(param.data[1]).toFixed(2)
|
||||||
const close = param.data[1]
|
const close = toNumber(param.data[2]).toFixed(2)
|
||||||
const lowest = param.data[2]
|
const lowest = toNumber(param.data[3]).toFixed(2)
|
||||||
const highest = param.data[3]
|
const highest = toNumber(param.data[4]).toFixed(2)
|
||||||
|
|
||||||
return [
|
return `<div class="text-center font-bold mb-2">${param.name}</div>
|
||||||
`<div style="font-weight: bold; margin-bottom: 4px;">${param.name}</div>`,
|
<div class="text-center">
|
||||||
`<div style="display: flex; justify-content: space-between; margin: 2px 0;">`,
|
<span>开盘:</span>
|
||||||
`<span>开盘:</span>`,
|
<span class="font-bold ml-4">${open}</span>
|
||||||
`<span style="margin-left: 12px; font-weight: bold;">${open}</span>`,
|
</div>
|
||||||
`</div>`,
|
<div class="text-center">
|
||||||
`<div style="display: flex; justify-content: space-between; margin: 2px 0;">`,
|
<span>收盘:</span>
|
||||||
`<span>收盘:</span>`,
|
<span class="font-bold ml-4">${close}</span>
|
||||||
`<span style="margin-left: 12px; font-weight: bold;">${close}</span>`,
|
</div>
|
||||||
`</div>`,
|
<div class="text-center">
|
||||||
`<div style="display: flex; justify-content: space-between; margin: 2px 0;">`,
|
<span>最低:</span>
|
||||||
`<span>最低:</span>`,
|
<span class="font-bold ml-4">${lowest}</span>
|
||||||
`<span style="margin-left: 12px; font-weight: bold;">${lowest}</span>`,
|
</div>
|
||||||
`</div>`,
|
<div class="text-center">
|
||||||
`<div style="display: flex; justify-content: space-between; margin: 2px 0;">`,
|
<span>最高:</span>
|
||||||
`<span>最高:</span>`,
|
<span class="font-bold ml-4">${highest}</span>
|
||||||
`<span style="margin-left: 12px; font-weight: bold;">${highest}</span>`,
|
</div>`
|
||||||
`</div>`,
|
|
||||||
].join('')
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
@@ -495,7 +494,7 @@ function StockDetail() {
|
|||||||
color: '#666',
|
color: '#666',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
formatter: function (value: number) {
|
formatter: function (value: number) {
|
||||||
return value.toFixed(0)
|
return value.toFixed(2)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
@@ -538,6 +537,7 @@ function StockDetail() {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"12月线数据",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user