refactor: 移除年线数据
需要用的时候现算就是了,避免维护更多的原始数据
This commit is contained in:
@@ -1,57 +0,0 @@
|
|||||||
package com.lanyuanxiaoyao.leopard.core.entity;
|
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.Constants;
|
|
||||||
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.EntityListeners;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
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 + "yearly")
|
|
||||||
public class Yearly extends SimpleEntity {
|
|
||||||
@Column(name = "`year`", nullable = false)
|
|
||||||
@Comment("年份")
|
|
||||||
private Integer year;
|
|
||||||
@Comment("开盘价")
|
|
||||||
private Double open;
|
|
||||||
@Comment("最高价")
|
|
||||||
private Double high;
|
|
||||||
@Comment("最低价")
|
|
||||||
private Double low;
|
|
||||||
@Comment("收盘价")
|
|
||||||
private Double close;
|
|
||||||
@Comment("涨跌额")
|
|
||||||
private Double priceChangeAmount;
|
|
||||||
@Comment("涨跌幅")
|
|
||||||
private Double priceFluctuationRange;
|
|
||||||
@Comment("成交量")
|
|
||||||
private Double volume;
|
|
||||||
@Comment("成交额")
|
|
||||||
private Double turnover;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(nullable = false)
|
|
||||||
@ToString.Exclude
|
|
||||||
private Stock stock;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.lanyuanxiaoyao.leopard.core.repository;
|
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Yearly;
|
|
||||||
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public interface YearlyRepository extends SimpleRepository<Yearly> {
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ import com.lanyuanxiaoyao.leopard.core.task.TaskRunner;
|
|||||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateDailyTask;
|
import com.lanyuanxiaoyao.leopard.core.task.UpdateDailyTask;
|
||||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateFinanceIndicatorTask;
|
import com.lanyuanxiaoyao.leopard.core.task.UpdateFinanceIndicatorTask;
|
||||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateStockTask;
|
import com.lanyuanxiaoyao.leopard.core.task.UpdateStockTask;
|
||||||
import com.lanyuanxiaoyao.leopard.core.task.UpdateYearlyTask;
|
|
||||||
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -35,7 +34,6 @@ public class TaskService extends SimpleServiceSupport<Task> {
|
|||||||
@Getter
|
@Getter
|
||||||
private final Set<TaskTemplate> templates = Stream.of(
|
private final Set<TaskTemplate> templates = Stream.of(
|
||||||
new TaskTemplate("b29f76a5-b07d-4182-85f8-2641c2a975c1", "更新股票信息", "更新股票信息", UpdateStockTask.class),
|
new TaskTemplate("b29f76a5-b07d-4182-85f8-2641c2a975c1", "更新股票信息", "更新股票信息", UpdateStockTask.class),
|
||||||
new TaskTemplate("e42dde60-5584-4c27-b3f7-72e4a4ff662d", "更新年线数据", "更新年线数据", UpdateYearlyTask.class),
|
|
||||||
new TaskTemplate("b9df25ce-aa55-4f73-8265-d8a724614177", "更新日线数据", "更新日线数据", UpdateDailyTask.class),
|
new TaskTemplate("b9df25ce-aa55-4f73-8265-d8a724614177", "更新日线数据", "更新日线数据", UpdateDailyTask.class),
|
||||||
new TaskTemplate("8ab30478-c81f-4bbf-94dd-7e05fa537b50", "更新财务指标", "更新财务指标", UpdateFinanceIndicatorTask.class),
|
new TaskTemplate("8ab30478-c81f-4bbf-94dd-7e05fa537b50", "更新财务指标", "更新财务指标", UpdateFinanceIndicatorTask.class),
|
||||||
new TaskTemplate("a6a7b569-a171-481b-9184-716925571639", "金字塔选股", "金字塔选股", PyramidSelect.class)
|
new TaskTemplate("a6a7b569-a171-481b-9184-716925571639", "金字塔选股", "金字塔选股", PyramidSelect.class)
|
||||||
|
|||||||
@@ -97,9 +97,19 @@ public class TuShareService {
|
|||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public TuShareResponse factorList(LocalDate tradeDate) {
|
public TuShareResponse factorList(LocalDate tradeDate) {
|
||||||
|
return factorList(tradeDate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public TuShareResponse factorList(LocalDate tradeDate, String stockCode) {
|
||||||
|
var paramsMap = new HashMap<String, Object>();
|
||||||
|
paramsMap.put("trade_date", tradeDate.format(TRADE_FORMAT));
|
||||||
|
if (StrUtil.isNotBlank(stockCode)) {
|
||||||
|
paramsMap.put("ts_code", stockCode);
|
||||||
|
}
|
||||||
var response = HttpUtil.post(API_URL, buildRequest(
|
var response = HttpUtil.post(API_URL, buildRequest(
|
||||||
"adj_factor",
|
"adj_factor",
|
||||||
Map.of("trade_date", tradeDate.format(TRADE_FORMAT)),
|
paramsMap,
|
||||||
List.of("ts_code", "trade_date", "adj_factor")
|
List.of("ts_code", "trade_date", "adj_factor")
|
||||||
));
|
));
|
||||||
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
|
var tuShareResponse = mapper.readValue(response, TuShareResponse.class);
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package com.lanyuanxiaoyao.leopard.core.task;
|
|
||||||
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.QYearly;
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Yearly;
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
|
|
||||||
import com.lanyuanxiaoyao.leopard.core.repository.YearlyRepository;
|
|
||||||
import java.util.Map;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新年线指标
|
|
||||||
*
|
|
||||||
* @author lanyuanxiaoyao
|
|
||||||
* @version 20250924
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Component
|
|
||||||
public class UpdateYearlyTask extends TaskRunner {
|
|
||||||
private final StockRepository stockRepository;
|
|
||||||
private final DailyRepository dailyRepository;
|
|
||||||
private final YearlyRepository yearlyRepository;
|
|
||||||
|
|
||||||
protected UpdateYearlyTask(ApplicationContext context, StockRepository stockRepository, DailyRepository dailyRepository, YearlyRepository yearlyRepository) {
|
|
||||||
super(context);
|
|
||||||
this.stockRepository = stockRepository;
|
|
||||||
this.dailyRepository = dailyRepository;
|
|
||||||
this.yearlyRepository = yearlyRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
|
||||||
@Override
|
|
||||||
public String process(Map<String, Object> params, StepUpdater updater) {
|
|
||||||
var startYear = dailyRepository.findMinTradeDate().getYear();
|
|
||||||
var endYear = dailyRepository.findMaxTradeDate().getYear();
|
|
||||||
var stocks = stockRepository.findAll();
|
|
||||||
for (int year = startYear, index = 0; year <= endYear; year++, index++) {
|
|
||||||
int currentYear = year;
|
|
||||||
stocks.parallelStream()
|
|
||||||
.forEach(stock -> {
|
|
||||||
if (stock.getListedDate().getYear() > currentYear) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var yearlyOptional = yearlyRepository.findOne(
|
|
||||||
QYearly.yearly.stock.eq(stock)
|
|
||||||
.and(QYearly.yearly.year.eq(currentYear))
|
|
||||||
);
|
|
||||||
if (yearlyOptional.isPresent() && currentYear != endYear) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var dailies = dailyRepository.findAll(
|
|
||||||
QDaily.daily.tradeDate.year().eq(currentYear)
|
|
||||||
.and(QDaily.daily.stock.eq(stock))
|
|
||||||
);
|
|
||||||
if (dailies.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var yearly = yearlyOptional.orElseGet(Yearly::new);
|
|
||||||
yearly.setStock(stock);
|
|
||||||
yearly.setYear(currentYear);
|
|
||||||
yearly.setClose(dailies.getLast().getHfqClose());
|
|
||||||
yearly.setOpen(dailies.getFirst().getHfqOpen());
|
|
||||||
yearly.setHigh(dailies.stream().map(Daily::getHfqHigh).max(Double::compareTo).orElse(0.0));
|
|
||||||
yearly.setLow(dailies.stream().map(Daily::getHfqLow).min(Double::compareTo).orElse(0.0));
|
|
||||||
yearly.setVolume(dailies.stream().mapToDouble(Daily::getVolume).sum());
|
|
||||||
yearly.setTurnover(dailies.stream().mapToDouble(Daily::getTurnover).sum());
|
|
||||||
yearly.setPriceChangeAmount(yearly.getClose() - yearly.getOpen());
|
|
||||||
yearly.setPriceFluctuationRange((yearly.getClose() - yearly.getOpen()) / yearly.getOpen());
|
|
||||||
yearlyRepository.save(yearly);
|
|
||||||
});
|
|
||||||
updater.update((year - startYear) * 1.0 / (endYear - startYear + 1));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -109,7 +109,8 @@ Content-Type: application/json
|
|||||||
"api_name": "adj_factor",
|
"api_name": "adj_factor",
|
||||||
"token": "{{api_key}}",
|
"token": "{{api_key}}",
|
||||||
"params": {
|
"params": {
|
||||||
"trade_date": "20241231"
|
"ts_code": "600018.SH",
|
||||||
|
"trade_date": "20060331"
|
||||||
},
|
},
|
||||||
"fields": [
|
"fields": [
|
||||||
"ts_code",
|
"ts_code",
|
||||||
@@ -126,12 +127,15 @@ Content-Type: application/json
|
|||||||
"api_name": "stk_factor_pro",
|
"api_name": "stk_factor_pro",
|
||||||
"token": "{{api_key}}",
|
"token": "{{api_key}}",
|
||||||
"params": {
|
"params": {
|
||||||
"ts_code": "000002.SZ",
|
"ts_code": "600018.SH",
|
||||||
"trade_date": "20250102"
|
"trade_date": "20060331"
|
||||||
},
|
},
|
||||||
"fields": [
|
"fields": [
|
||||||
"ts_code",
|
"ts_code",
|
||||||
"trade_date",
|
"trade_date",
|
||||||
|
"open",
|
||||||
|
"open_qfq",
|
||||||
|
"open_hfq",
|
||||||
"close",
|
"close",
|
||||||
"close_qfq",
|
"close_qfq",
|
||||||
"close_hfq"
|
"close_hfq"
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package com.lanyuanxiaoyao.leopard.strategy;
|
package com.lanyuanxiaoyao.leopard.strategy;
|
||||||
|
|
||||||
import cn.hutool.core.util.NumberUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||||
|
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
|
||||||
import com.lanyuanxiaoyao.leopard.core.service.AssessmentService;
|
import com.lanyuanxiaoyao.leopard.core.service.AssessmentService;
|
||||||
|
import com.lanyuanxiaoyao.leopard.core.service.TuShareService;
|
||||||
import com.lanyuanxiaoyao.leopard.core.service.selector.PyramidStockSelector;
|
import com.lanyuanxiaoyao.leopard.core.service.selector.PyramidStockSelector;
|
||||||
import com.lanyuanxiaoyao.leopard.core.service.selector.StockSelector;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
@@ -30,6 +29,10 @@ public class StrategyApplication {
|
|||||||
private PyramidStockSelector pyramidStockSelector;
|
private PyramidStockSelector pyramidStockSelector;
|
||||||
@Resource
|
@Resource
|
||||||
private AssessmentService assessmentService;
|
private AssessmentService assessmentService;
|
||||||
|
@Resource
|
||||||
|
private TuShareService tuShareService;
|
||||||
|
@Resource
|
||||||
|
private DailyRepository dailyRepository;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(StrategyApplication.class, args);
|
SpringApplication.run(StrategyApplication.class, args);
|
||||||
@@ -269,62 +272,11 @@ public class StrategyApplication {
|
|||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
public void test() {
|
public void test() {
|
||||||
/*var dailies = dailyRepository.findAll(
|
var dailies = dailyRepository.findAll(QDaily.daily.factor.isNull(), QDaily.daily.tradeDate.asc());
|
||||||
QDaily.daily.tradeDate.year().eq(2025),
|
for (var daily : dailies) {
|
||||||
Sort.by(Daily_.TRADE_DATE)
|
log.info("{} {} {}", daily.getStock().getCode(), daily.getStock().getName(), daily.getTradeDate());
|
||||||
);
|
// var response = tuShareService.factorList(daily.getTradeDate(), daily.getStock().getCode());
|
||||||
var data = dailies.stream()
|
// var factor = response.data().items().getFirst().get(2);
|
||||||
.collect(Collectors.groupingBy(Daily::getStock))
|
|
||||||
.entrySet()
|
|
||||||
.parallelStream()
|
|
||||||
.map(entry -> {
|
|
||||||
var stock = entry.getKey();
|
|
||||||
var dailyList = entry.getValue()
|
|
||||||
.stream()
|
|
||||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
|
||||||
.toList();
|
|
||||||
var statistics = new DescriptiveStatistics();
|
|
||||||
dailyList.stream().map(Daily::getHfqClose).forEach(statistics::addValue);
|
|
||||||
var barSeries = new BaseBarSeriesBuilder().build();
|
|
||||||
dailyList.forEach(daily -> barSeries.addBar(new BaseBar(
|
|
||||||
Duration.ofDays(1),
|
|
||||||
daily.getTradeDate().atStartOfDay().atZone(ZoneId.systemDefault()),
|
|
||||||
daily.getHfqOpen(),
|
|
||||||
daily.getHfqHigh(),
|
|
||||||
daily.getHfqLow(),
|
|
||||||
daily.getHfqClose(),
|
|
||||||
daily.getVolume()
|
|
||||||
)));
|
|
||||||
return Map.<String, String>of(
|
|
||||||
"code", stock.getCode(),
|
|
||||||
"name", stock.getName(),
|
|
||||||
"std", NumberUtil.roundStr(statistics.getStandardDeviation(), 2),
|
|
||||||
"increase", NumberUtil.roundStr((dailyList.getLast().getHfqClose() - dailyList.getFirst().getHfqClose()) * 100.0 / dailyList.getFirst().getHfqClose(), 2)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.sorted((t1, t2) -> Double.compare(Double.parseDouble(t2.get("increase")), Double.parseDouble(t1.get("increase"))))
|
|
||||||
.toList();
|
|
||||||
render(Map.of("items", data));*/
|
|
||||||
|
|
||||||
var lines = new ArrayList<String>();
|
|
||||||
for (int year = 2024; year < 2025; year++) {
|
|
||||||
var candidates = pyramidStockSelector.select(new PyramidStockSelector.Request(year));
|
|
||||||
for (StockSelector.Candidate candidate : candidates) {
|
|
||||||
log.info("{} {}", candidate.stock().getName(), candidate.score());
|
|
||||||
}
|
|
||||||
var stocks = candidates.stream()
|
|
||||||
.map(StockSelector.Candidate::stock)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
var results = assessmentService.assess(stocks, year);
|
|
||||||
int up = results.stream()
|
|
||||||
.filter(result -> result.change() > 0)
|
|
||||||
.mapToInt(result -> 1)
|
|
||||||
.sum();
|
|
||||||
lines.add(NumberUtil.roundStr(up * 100.0 / results.size(), 2));
|
|
||||||
results.forEach(result -> log.info("{} {} {} {} {}", result.stock().getCode(), result.stock().getName(), result.change(), result.std(), result.industryTop()));
|
|
||||||
}
|
|
||||||
for (int index = 0, year = 2010; index < lines.size(); index++, year++) {
|
|
||||||
log.info("胜率: {} {}", year, lines.get(index));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user