1
0

feat: 增加年线和周线的计算

This commit is contained in:
2025-10-14 23:26:35 +08:00
parent a075adf4b6
commit 8c4e3baacb
9 changed files with 267 additions and 21 deletions

View File

@@ -0,0 +1,15 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="NullableProblems" enabled="false" level="WARNING" enabled_by_default="false">
<option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE" value="true" />
<option name="REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL" value="true" />
<option name="REPORT_NOT_ANNOTATED_GETTER" value="true" />
<option name="REPORT_NOT_ANNOTATED_SETTER_PARAMETER" value="true" />
<option name="REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS" value="true" />
<option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.leopard.core.entity;
import cn.hutool.core.util.ObjectUtil;
import com.lanyuanxiaoyao.leopard.core.Constants;
import com.lanyuanxiaoyao.service.template.entity.SimpleEntity;
import jakarta.persistence.Column;
@@ -58,18 +59,18 @@ public class Daily extends SimpleEntity {
private Stock stock;
public Double getHfqOpen() {
return open * factor;
return open * ObjectUtil.defaultIfNull(factor, 1.0);
}
public Double getHfqClose() {
return close * factor;
return close * ObjectUtil.defaultIfNull(factor, 1.0);
}
public Double getHfqHigh() {
return high * factor;
return high * ObjectUtil.defaultIfNull(factor, 1.0);
}
public Double getHfqLow() {
return low * factor;
return low * ObjectUtil.defaultIfNull(factor, 1.0);
}
}

View File

@@ -61,10 +61,6 @@ public class Stock extends SimpleEntity {
@ToString.Exclude
private Set<Daily> dailies;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<Yearly> yearlies;
@OneToMany(mappedBy = "stock", cascade = CascadeType.REMOVE)
@ToString.Exclude
private Set<FinanceIndicator> indicators;

View File

@@ -0,0 +1,18 @@
package com.lanyuanxiaoyao.leopard.core.entity.dto;
import java.time.LocalDate;
public record Monthly(
LocalDate tradeDate,
int year,
int month,
Double open,
Double high,
Double low,
Double close,
Double priceChangeAmount,
Double priceFluctuationRange,
Double volume,
Double turnover
) {
}

View File

@@ -0,0 +1,18 @@
package com.lanyuanxiaoyao.leopard.core.entity.dto;
import java.time.LocalDate;
public record Weekly(
LocalDate tradeDate,
int year,
int week,
Double open,
Double high,
Double low,
Double close,
Double priceChangeAmount,
Double priceFluctuationRange,
Double volume,
Double turnover
) {
}

View File

@@ -0,0 +1,17 @@
package com.lanyuanxiaoyao.leopard.core.entity.dto;
import java.time.LocalDate;
public record Yearly(
LocalDate tradeDate,
int year,
Double open,
Double high,
Double low,
Double close,
Double priceChangeAmount,
Double priceFluctuationRange,
Double volume,
Double turnover
) {
}

View File

@@ -2,12 +2,12 @@ package com.lanyuanxiaoyao.leopard.core.repository;
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
import com.querydsl.core.types.OrderSpecifier;
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;
import org.springframework.stereotype.Repository;
@@ -32,5 +32,5 @@ public interface DailyRepository extends SimpleRepository<Daily> {
@EntityGraph(attributePaths = {"stock"})
@Override
List<Daily> findAll(Predicate predicate, Sort sort);
List<Daily> findAll(Predicate predicate, OrderSpecifier<?>... orders);
}

View File

@@ -1,22 +1,28 @@
package com.lanyuanxiaoyao.leopard.core.service;
import cn.hutool.core.util.ObjectUtil;
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
import com.lanyuanxiaoyao.leopard.core.entity.Daily_;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.FinanceIndicator_;
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
import com.lanyuanxiaoyao.leopard.core.entity.QFinanceIndicator;
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
import com.lanyuanxiaoyao.leopard.core.entity.dto.Monthly;
import com.lanyuanxiaoyao.leopard.core.entity.dto.Weekly;
import com.lanyuanxiaoyao.leopard.core.entity.dto.Yearly;
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
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;
import java.time.temporal.ChronoField;
import java.time.temporal.WeekFields;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
/**
@@ -49,7 +55,7 @@ public class StockService extends SimpleServiceSupport<Stock> {
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)
QDaily.daily.tradeDate.asc()
);
}
@@ -59,7 +65,7 @@ public class StockService extends SimpleServiceSupport<Stock> {
return dailyRepository.findAll(
QDaily.daily.stock.id.eq(stockId)
.and(QDaily.daily.tradeDate.between(current.minusDays(days), current)),
Sort.by(Sort.Direction.ASC, Daily_.TRADE_DATE)
QDaily.daily.tradeDate.asc()
);
}
@@ -67,4 +73,174 @@ public class StockService extends SimpleServiceSupport<Stock> {
public Optional<Daily> findDailyLatest(Long stockId) {
return dailyRepository.findLatest(stockId);
}
@Cacheable(value = "long-cache", sync = true)
public List<Yearly> findYearlyRecent(Long stockId, int years) {
var current = LocalDate.now().withMonth(1).withDayOfMonth(1);
var start = current.minusYears(years).getYear();
var end = current.getYear();
return dailyRepository
.findAll(
QDaily.daily.stock.id.eq(stockId)
.and(QDaily.daily.tradeDate.year().gt(start))
.and(QDaily.daily.tradeDate.year().loe(end)),
QDaily.daily.tradeDate.asc()
)
.stream()
.collect(Collectors.groupingBy(daily -> daily.getTradeDate().getYear()))
.entrySet()
.stream()
.map(entry -> {
var year = entry.getKey();
var dailies = entry.getValue();
var open = dailies.getFirst().getHfqOpen();
var close = dailies.getLast().getHfqClose();
return new Yearly(
LocalDate.of(year, 1, 1),
year,
open,
maxFromDaily(dailies, Daily::getHfqHigh),
minFromDaily(dailies, Daily::getHfqLow),
close,
close - open,
(close - open) / open * 100,
sumFromDaily(dailies, Daily::getVolume),
sumFromDaily(dailies, Daily::getTurnover)
);
})
.sorted(Comparator.comparingInt(Yearly::year))
.toList();
}
@Cacheable(value = "long-cache", sync = true)
public List<Monthly> findMonthlyRecent(Long stockId, int months) {
var end = LocalDate.now().withDayOfMonth(1);
var start = end.minusMonths(months);
return dailyRepository
.findAll(
QDaily.daily.stock.id.eq(stockId)
.and(
QDaily.daily.tradeDate.year().gt(start.getYear())
.or(
QDaily.daily.tradeDate.year().eq(start.getYear())
.and(QDaily.daily.tradeDate.month().gt(start.getMonthValue()))
)
)
.and(
QDaily.daily.tradeDate.year().lt(end.getYear())
.or(
QDaily.daily.tradeDate.year().eq(end.getYear())
.and(QDaily.daily.tradeDate.month().loe(end.getMonthValue()))
)
),
QDaily.daily.tradeDate.asc()
)
.stream()
.collect(Collectors.groupingBy(daily -> new YearAndMonth(daily.getTradeDate().getYear(), daily.getTradeDate().getMonthValue())))
.entrySet()
.stream()
.map(entry -> {
var yearAndMonth = entry.getKey();
var dailies = entry.getValue();
var open = dailies.getFirst().getHfqOpen();
var close = dailies.getLast().getHfqClose();
return new Monthly(
LocalDate.of(yearAndMonth.year, yearAndMonth.month, 1),
yearAndMonth.year,
yearAndMonth.month,
open,
maxFromDaily(dailies, Daily::getHfqHigh),
minFromDaily(dailies, Daily::getHfqLow),
close,
close - open,
(close - open) / open * 100,
sumFromDaily(dailies, Daily::getVolume),
sumFromDaily(dailies, Daily::getTurnover)
);
})
.sorted(Comparator.comparingInt(monthly -> monthly.year() * 100 + monthly.month()))
.toList();
}
@Cacheable(value = "long-cache", sync = true)
public List<Weekly> findWeeklyRecent(Long stockId, int weeks) {
var end = LocalDate.now().with(ChronoField.DAY_OF_WEEK, 1);
var start = end.minusWeeks(weeks);
return dailyRepository
.findAll(
QDaily.daily.stock.id.eq(stockId)
.and(
QDaily.daily.tradeDate.year().gt(start.getYear())
.or(
QDaily.daily.tradeDate.year().eq(start.getYear())
.and(QDaily.daily.tradeDate.week().gt(start.get(WeekFields.ISO.weekOfYear())))
)
)
.and(
QDaily.daily.tradeDate.year().lt(end.getYear())
.or(
QDaily.daily.tradeDate.year().eq(end.getYear())
.and(QDaily.daily.tradeDate.month().loe(end.get(WeekFields.ISO.weekOfYear())))
)
),
QDaily.daily.tradeDate.asc()
)
.stream()
.collect(Collectors.groupingBy(daily -> new YearAndWeek(daily.getTradeDate().getYear(), daily.getTradeDate().get(WeekFields.ISO.weekOfYear()))))
.entrySet()
.stream()
.map(entry -> {
var yearAndWeek = entry.getKey();
var dailies = entry.getValue();
var open = dailies.getFirst().getHfqOpen();
var close = dailies.getLast().getHfqClose();
return new Weekly(
LocalDate.of(yearAndWeek.year, 1, 1).with(WeekFields.ISO.weekOfYear(), yearAndWeek.week),
yearAndWeek.year,
yearAndWeek.week,
open,
maxFromDaily(dailies, Daily::getHfqHigh),
minFromDaily(dailies, Daily::getHfqLow),
close,
close - open,
(close - open) / open * 100,
sumFromDaily(dailies, Daily::getVolume),
sumFromDaily(dailies, Daily::getTurnover)
);
})
.sorted(Comparator.comparingInt(weekly -> weekly.year() * 100 + weekly.week()))
.toList();
}
private Double maxFromDaily(List<Daily> dailies, Function<Daily, Double> function) {
return dailies.stream()
.map(function)
.filter(ObjectUtil::isNotNull)
.mapToDouble(Double::doubleValue)
.max()
.orElse(0);
}
private Double minFromDaily(List<Daily> dailies, Function<Daily, Double> function) {
return dailies.stream()
.map(function)
.filter(ObjectUtil::isNotNull)
.mapToDouble(Double::doubleValue)
.min()
.orElse(0);
}
private Double sumFromDaily(List<Daily> dailies, Function<Daily, Double> function) {
return dailies.stream()
.map(function)
.filter(ObjectUtil::isNotNull)
.mapToDouble(Double::doubleValue)
.sum();
}
private record YearAndMonth(int year, int month) {
}
private record YearAndWeek(int year, int week) {
}
}

View File

@@ -2,9 +2,11 @@ package com.lanyuanxiaoyao.leopard.strategy;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
import com.lanyuanxiaoyao.leopard.core.entity.QStock;
import com.lanyuanxiaoyao.leopard.core.repository.DailyRepository;
import com.lanyuanxiaoyao.leopard.core.repository.StockRepository;
import com.lanyuanxiaoyao.leopard.core.service.AssessmentService;
import com.lanyuanxiaoyao.leopard.core.service.StockService;
import com.lanyuanxiaoyao.leopard.core.service.TuShareService;
import com.lanyuanxiaoyao.leopard.core.service.selector.PyramidStockSelector;
import jakarta.annotation.Resource;
@@ -33,6 +35,10 @@ public class StrategyApplication {
private TuShareService tuShareService;
@Resource
private DailyRepository dailyRepository;
@Resource
private StockRepository stockRepository;
@Resource
private StockService stockService;
public static void main(String[] args) {
SpringApplication.run(StrategyApplication.class, args);
@@ -272,11 +278,10 @@ public class StrategyApplication {
@Transactional(readOnly = true)
@EventListener(ApplicationReadyEvent.class)
public void test() {
var dailies = dailyRepository.findAll(QDaily.daily.factor.isNull(), QDaily.daily.tradeDate.asc());
for (var daily : dailies) {
log.info("{} {} {}", daily.getStock().getCode(), daily.getStock().getName(), daily.getTradeDate());
// var response = tuShareService.factorList(daily.getTradeDate(), daily.getStock().getCode());
// var factor = response.data().items().getFirst().get(2);
var stock = stockRepository.findOne(QStock.stock.code.eq("000001.SZ")).orElseThrow();
var weeklies = stockService.findWeeklyRecent(stock.getId(), 2);
for (var weekly : weeklies) {
log.info("{}", weekly);
}
}
}