Compare commits
6 Commits
7d3b3758f3
...
3991effa88
| Author | SHA1 | Date | |
|---|---|---|---|
| 3991effa88 | |||
| 02508a5426 | |||
| edd18061eb | |||
| 3d428d9d0a | |||
| b4e2c81d36 | |||
| 1edd74e35d |
@@ -6,6 +6,7 @@ import com.querydsl.core.types.Predicate;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
@@ -14,10 +15,7 @@ import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
public interface DailyRepository extends SimpleRepository<Daily> {
|
||||
@Query("select distinct daily.tradeDate from Daily daily")
|
||||
List<LocalDate> findDistinctTradeDate();
|
||||
|
||||
@Query("select distinct daily.tradeDate from Daily daily where daily.stock.id = ?1")
|
||||
List<LocalDate> findDistinctTradeDateByStockId(Long stockId);
|
||||
Set<LocalDate> findDistinctTradeDate();
|
||||
|
||||
@Query("select max(daily.tradeDate) from Daily daily")
|
||||
LocalDate findMaxTradeDate();
|
||||
|
||||
@@ -4,8 +4,7 @@ import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.EntityGraph;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -17,16 +16,12 @@ import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
public interface StockRepository extends SimpleRepository<Stock> {
|
||||
@Query("select distinct stock.industry from Stock stock where stock.industry is not null")
|
||||
List<String> findDistinctIndustries();
|
||||
Set<String> findDistinctIndustries();
|
||||
|
||||
@Query("select distinct stock.code from Stock stock")
|
||||
List<String> findDistinctCodes();
|
||||
Set<String> findDistinctCodes();
|
||||
|
||||
@Modifying
|
||||
@Transactional(rollbackOn = Throwable.class)
|
||||
@Modifying
|
||||
void deleteAllByCodeIn(Collection<String> code);
|
||||
|
||||
@EntityGraph(attributePaths = {"indicators"})
|
||||
@Query("from Stock stock where size(stock.indicators) >= ?1")
|
||||
List<Stock> findAllByIndicatorsSizeGreaterThanEqual(int count);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Repository
|
||||
public interface TaskRepository extends SimpleRepository<Task> {
|
||||
@Modifying
|
||||
@Query("update Task task set task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.FAILURE where task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.RUNNING")
|
||||
@Query("""
|
||||
update Task task set
|
||||
task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.FAILURE,
|
||||
task.finishedTime = current timestamp
|
||||
where task.status = com.lanyuanxiaoyao.leopard.core.entity.Task.Status.RUNNING""")
|
||||
void updateAllRunningTaskToFailure();
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 金字塔选股
|
||||
@@ -28,6 +29,7 @@ public class PyramidStockSelector implements StockSelector<PyramidStockSelector.
|
||||
this.stockRepository = stockRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@Override
|
||||
public Set<Candidate> select(Request request) {
|
||||
// 选择至少有最近5年财报的股票
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TaskService;
|
||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -100,18 +101,21 @@ public class CommonOptionsController {
|
||||
case "stock_market" -> GlobalResponse.responseSuccess(
|
||||
Arrays.stream(Stock.Market.values())
|
||||
.map(market -> new Option(market.getChineseName(), market.name()))
|
||||
.sorted(Comparator.comparing(Option::label))
|
||||
.toList()
|
||||
);
|
||||
case "stock_industry" -> GlobalResponse.responseSuccess(
|
||||
stockRepository.findDistinctIndustries()
|
||||
.stream()
|
||||
.map(industry -> new Option(industry, industry))
|
||||
.sorted(Comparator.comparing(Option::label))
|
||||
.toList()
|
||||
);
|
||||
case "task_template_id" -> GlobalResponse.responseSuccess(
|
||||
taskService.getTemplates()
|
||||
.stream()
|
||||
.map(template -> new Option(template.name(), template.id()))
|
||||
.sorted(Comparator.comparing(Option::label))
|
||||
.toList()
|
||||
);
|
||||
default -> GlobalResponse.responseSuccess(List.of());
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||
import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -48,7 +49,10 @@ public class TaskController extends SimpleControllerSupport<Task, Void, TaskCont
|
||||
|
||||
@GetMapping("template/list")
|
||||
public GlobalResponse<Map<String, Object>> templateList() {
|
||||
var templates = taskService.getTemplates();
|
||||
var templates = taskService.getTemplates()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(TaskService.TaskTemplate::name))
|
||||
.toList();
|
||||
return GlobalResponse.responseCrudData(templates, templates.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,11 @@ import org.springframework.stereotype.Service;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class StockService extends SimpleServiceSupport<Stock> {
|
||||
private final StockRepository stockRepository;
|
||||
private final FinanceIndicatorRepository financeIndicatorRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
public StockService(StockRepository repository, FinanceIndicatorRepository financeIndicatorRepository, DailyRepository dailyRepository) {
|
||||
super(repository);
|
||||
this.stockRepository = repository;
|
||||
this.financeIndicatorRepository = financeIndicatorRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.Task;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.TaskRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.task.PyramidSelect;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.task.TaskRunner;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.task.UpdateDailyTask;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.task.UpdateFinanceIndicatorTask;
|
||||
@@ -40,7 +41,8 @@ public class TaskService extends SimpleServiceSupport<Task> {
|
||||
new TaskTemplate("更新股票信息", "更新股票信息", UpdateStockTask.class),
|
||||
new TaskTemplate("更新年线指标", "更新年线指标", UpdateYearlyTask.class),
|
||||
new TaskTemplate("更新日线数据", "更新日线数据", UpdateDailyTask.class),
|
||||
new TaskTemplate("更新财务指标", "更新财务指标", UpdateFinanceIndicatorTask.class)
|
||||
new TaskTemplate("更新财务指标", "更新财务指标", UpdateFinanceIndicatorTask.class),
|
||||
new TaskTemplate("金字塔选股", "金字塔选股", PyramidSelect.class)
|
||||
).collect(Collectors.toSet());
|
||||
private final Map<String, TaskTemplate> templateMap = templates.stream()
|
||||
.collect(Collectors.toMap(TaskTemplate::id, template -> template));
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.lanyuanxiaoyao.leopard.server.service.task;
|
||||
|
||||
import com.lanyuanxiaoyao.leopard.core.entity.StockCollection;
|
||||
import com.lanyuanxiaoyao.leopard.core.repository.StockCollectionRepository;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.selector.PyramidStockSelector;
|
||||
import com.lanyuanxiaoyao.leopard.core.service.selector.StockSelector;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 金字塔选股
|
||||
*
|
||||
* @author lanyuanxiaoyao
|
||||
* @version 20250925
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PyramidSelect extends TaskRunner {
|
||||
private final StockCollectionRepository stockCollectionRepository;
|
||||
|
||||
private final PyramidStockSelector pyramidStockSelector;
|
||||
|
||||
protected PyramidSelect(ApplicationContext context, StockCollectionRepository stockCollectionRepository, PyramidStockSelector pyramidStockSelector) {
|
||||
super(context);
|
||||
this.stockCollectionRepository = stockCollectionRepository;
|
||||
this.pyramidStockSelector = pyramidStockSelector;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) throws Exception {
|
||||
var candidates = pyramidStockSelector.select(new PyramidStockSelector.Request(LocalDate.now().getYear(), 50));
|
||||
var collection = new StockCollection();
|
||||
collection.setName("金字塔选股");
|
||||
collection.setDescription("金字塔选股");
|
||||
collection.setStocks(candidates.stream().map(StockSelector.Candidate::stock).collect(Collectors.toSet()));
|
||||
stockCollectionRepository.save(collection);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,11 @@ public abstract class TaskRunner {
|
||||
taskRepository.saveAndFlush(task);
|
||||
|
||||
try {
|
||||
var result = process(params, step -> taskRepository.updateStepById(task.getId(), step));
|
||||
var result = process(params, step -> {
|
||||
synchronized (task) {
|
||||
taskRepository.updateStepById(task.getId(), step);
|
||||
}
|
||||
});
|
||||
|
||||
task.setStatus(Task.Status.SUCCESS);
|
||||
task.setStep(1.0);
|
||||
|
||||
@@ -10,15 +10,17 @@ import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
||||
import com.lanyuanxiaoyao.leopard.server.helper.NumberHelper;
|
||||
import com.lanyuanxiaoyao.leopard.server.service.TuShareService;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
/**
|
||||
* 更新日线数据
|
||||
@@ -32,16 +34,18 @@ public class UpdateDailyTask extends TaskRunner {
|
||||
private final StockRepository stockRepository;
|
||||
private final DailyRepository dailyRepository;
|
||||
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
private final TuShareService tuShareService;
|
||||
|
||||
protected UpdateDailyTask(ApplicationContext context, StockRepository stockRepository, DailyRepository dailyRepository, TuShareService tuShareService) {
|
||||
protected UpdateDailyTask(ApplicationContext context, StockRepository stockRepository, DailyRepository dailyRepository, TransactionTemplate transactionTemplate, TuShareService tuShareService) {
|
||||
super(context);
|
||||
this.stockRepository = stockRepository;
|
||||
this.dailyRepository = dailyRepository;
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) throws Exception {
|
||||
var tradeDates = new HashSet<LocalDate>();
|
||||
@@ -60,39 +64,44 @@ public class UpdateDailyTask extends TaskRunner {
|
||||
.filter(date -> date.isBefore(nowDate) || date.isEqual(nowDate))
|
||||
.filter(date -> !existsTradeDates.contains(date))
|
||||
.toList();
|
||||
for (int index = 0, total = targetTradeDates.size(); index < total; index++) {
|
||||
var tradeDate = targetTradeDates.get(index);
|
||||
var factorResponse = tuShareService.factorList(tradeDate);
|
||||
var factorMap = new HashMap<String, Double>();
|
||||
for (List<String> item : factorResponse.data().items()) {
|
||||
factorMap.put(item.get(0), Double.valueOf(item.get(2)));
|
||||
}
|
||||
|
||||
var response = tuShareService.dailyList(tradeDate);
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (stocksMap.containsKey(code)) {
|
||||
var stock = stocksMap.get(code);
|
||||
var factor = factorMap.get(code);
|
||||
var daily = new Daily();
|
||||
daily.setTradeDate(tradeDate);
|
||||
daily.setOpen(NumberHelper.parseDouble(item.get(2)));
|
||||
daily.setHigh(NumberUtil.parseDouble(item.get(3)));
|
||||
daily.setLow(NumberUtil.parseDouble(item.get(4)));
|
||||
daily.setClose(NumberUtil.parseDouble(item.get(5)));
|
||||
daily.setPreviousClose(NumberUtil.parseDouble(item.get(6)));
|
||||
daily.setPriceChangeAmount(NumberUtil.parseDouble(item.get(7)));
|
||||
daily.setPriceFluctuationRange(NumberUtil.parseDouble(item.get(8)));
|
||||
daily.setVolume(NumberUtil.parseDouble(item.get(9)));
|
||||
daily.setTurnover(NumberUtil.parseDouble(item.get(10)));
|
||||
daily.setFactor(factor);
|
||||
daily.setStock(stock);
|
||||
dailyRepository.save(daily);
|
||||
var total = targetTradeDates.size();
|
||||
var finished = new AtomicInteger(0);
|
||||
targetTradeDates.parallelStream()
|
||||
.forEach(tradeDate -> {
|
||||
var factorResponse = tuShareService.factorList(tradeDate);
|
||||
var factorMap = new HashMap<String, Double>();
|
||||
for (List<String> item : factorResponse.data().items()) {
|
||||
factorMap.put(item.get(0), Double.valueOf(item.get(2)));
|
||||
}
|
||||
}
|
||||
updater.update(index * 1.0 / total);
|
||||
}
|
||||
|
||||
transactionTemplate.execute(status -> {
|
||||
var response = tuShareService.dailyList(tradeDate);
|
||||
var dailies = new ArrayList<Daily>();
|
||||
for (List<String> item : response.data().items()) {
|
||||
var code = item.get(0);
|
||||
if (stocksMap.containsKey(code)) {
|
||||
var stock = stocksMap.get(code);
|
||||
var factor = factorMap.get(code);
|
||||
var daily = new Daily();
|
||||
daily.setTradeDate(tradeDate);
|
||||
daily.setOpen(NumberHelper.parseDouble(item.get(2)));
|
||||
daily.setHigh(NumberUtil.parseDouble(item.get(3)));
|
||||
daily.setLow(NumberUtil.parseDouble(item.get(4)));
|
||||
daily.setClose(NumberUtil.parseDouble(item.get(5)));
|
||||
daily.setPreviousClose(NumberUtil.parseDouble(item.get(6)));
|
||||
daily.setPriceChangeAmount(NumberUtil.parseDouble(item.get(7)));
|
||||
daily.setPriceFluctuationRange(NumberUtil.parseDouble(item.get(8)));
|
||||
daily.setVolume(NumberUtil.parseDouble(item.get(9)));
|
||||
daily.setTurnover(NumberUtil.parseDouble(item.get(10)));
|
||||
daily.setFactor(factor);
|
||||
daily.setStock(stock);
|
||||
dailies.add(daily);
|
||||
}
|
||||
}
|
||||
dailyRepository.saveAll(dailies);
|
||||
return null;
|
||||
});
|
||||
updater.update(finished.incrementAndGet() * 1.0 / total);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 更新财务指标数据
|
||||
@@ -39,6 +40,7 @@ public class UpdateFinanceIndicatorTask extends TaskRunner {
|
||||
this.tuShareService = tuShareService;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Throwable.class)
|
||||
@Override
|
||||
public String process(Map<String, Object> params, StepUpdater updater) throws JsonProcessingException {
|
||||
var stocks = stockRepository.findAll();
|
||||
|
||||
3
leopard-server/src/main/resources/application-dev.yml
Normal file
3
leopard-server/src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://127.0.0.1:6785/leopard_dev
|
||||
@@ -23,7 +23,7 @@ spring:
|
||||
job-store-type: jdbc
|
||||
jdbc:
|
||||
platform: postgres
|
||||
# initialize-schema: always
|
||||
initialize-schema: always
|
||||
properties:
|
||||
org:
|
||||
quartz:
|
||||
|
||||
@@ -28,7 +28,7 @@ function TaskDetail() {
|
||||
label: '进度',
|
||||
content: {
|
||||
type: 'tpl',
|
||||
tpl: "${step}%",
|
||||
tpl: "${ROUND(step * 100, 2)}%",
|
||||
},
|
||||
span: 2,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user