feat: 增加日线的均线显示
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
package com.lanyuanxiaoyao.leopard.core.helper;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.ta4j.core.Bar;
|
||||||
|
import org.ta4j.core.BaseBar;
|
||||||
|
import org.ta4j.core.BaseBarSeries;
|
||||||
|
import org.ta4j.core.indicators.SMAIndicator;
|
||||||
|
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
|
||||||
|
|
||||||
|
public class TaHelper {
|
||||||
|
public static <T> List<Double> sma(List<T> data, int period, Function<T, Double> closeFunction) {
|
||||||
|
var series = new BaseBarSeries();
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
var price = closeFunction.apply(data.get(i));
|
||||||
|
Bar bar = new BaseBar(
|
||||||
|
Duration.ofDays(1),
|
||||||
|
ZonedDateTime.now().plusDays(i),
|
||||||
|
price,
|
||||||
|
price,
|
||||||
|
price,
|
||||||
|
price,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
series.addBar(bar);
|
||||||
|
}
|
||||||
|
var sma = new SMAIndicator(new ClosePriceIndicator(series), period);
|
||||||
|
var result = new ArrayList<Double>(series.getBarCount());
|
||||||
|
for (int i = 0; i < series.getBarCount(); i++) {
|
||||||
|
result.add(sma.getValue(i).doubleValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,4 +33,8 @@ public interface DailyRepository extends SimpleRepository<Daily> {
|
|||||||
@EntityGraph(attributePaths = {"stock"})
|
@EntityGraph(attributePaths = {"stock"})
|
||||||
@Override
|
@Override
|
||||||
List<Daily> findAll(Predicate predicate, OrderSpecifier<?>... orders);
|
List<Daily> findAll(Predicate predicate, OrderSpecifier<?>... orders);
|
||||||
|
|
||||||
|
@EntityGraph(attributePaths = {"stock"})
|
||||||
|
@Query("from Daily daily where daily.stock.id = ?1 and daily.tradeDate <= current date order by daily.tradeDate desc limit ?2")
|
||||||
|
List<Daily> findRecent(Long stockId, int days);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,18 +55,16 @@ public class StockService extends SimpleServiceSupport<Stock> {
|
|||||||
return financeIndicatorRepository.findAll(
|
return financeIndicatorRepository.findAll(
|
||||||
QFinanceIndicator.financeIndicator.stock.id.eq(stockId)
|
QFinanceIndicator.financeIndicator.stock.id.eq(stockId)
|
||||||
.and(QFinanceIndicator.financeIndicator.year.between(current.minusYears(years).getYear(), current.getYear())),
|
.and(QFinanceIndicator.financeIndicator.year.between(current.minusYears(years).getYear(), current.getYear())),
|
||||||
QDaily.daily.tradeDate.asc()
|
QFinanceIndicator.financeIndicator.year.asc()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cacheable(value = "findDailyRecent", cacheManager = "long-cache", sync = true)
|
@Cacheable(value = "findDailyRecent", cacheManager = "long-cache", sync = true)
|
||||||
public List<Daily> findDailyRecent(Long stockId, int days) {
|
public List<Daily> findDailyRecent(Long stockId, int days) {
|
||||||
var current = LocalDate.now();
|
return dailyRepository.findRecent(stockId, days)
|
||||||
return dailyRepository.findAll(
|
.stream()
|
||||||
QDaily.daily.stock.id.eq(stockId)
|
.sorted(Comparator.comparing(Daily::getTradeDate))
|
||||||
.and(QDaily.daily.tradeDate.between(current.minusDays(days), current)),
|
.toList();
|
||||||
QDaily.daily.tradeDate.asc()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cacheable(value = "findDailyLatest", cacheManager = "long-cache", sync = true)
|
@Cacheable(value = "findDailyLatest", cacheManager = "long-cache", sync = true)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
|
|||||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
import com.lanyuanxiaoyao.leopard.core.entity.Stock;
|
||||||
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
import com.lanyuanxiaoyao.leopard.core.helper.NumberHelper;
|
||||||
|
import com.lanyuanxiaoyao.leopard.core.helper.TaHelper;
|
||||||
import com.lanyuanxiaoyao.leopard.core.service.StockService;
|
import com.lanyuanxiaoyao.leopard.core.service.StockService;
|
||||||
import com.lanyuanxiaoyao.leopard.server.entity.StockDetailVo;
|
import com.lanyuanxiaoyao.leopard.server.entity.StockDetailVo;
|
||||||
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
import com.lanyuanxiaoyao.service.template.controller.GlobalResponse;
|
||||||
@@ -123,15 +124,20 @@ public class StockController extends SimpleControllerSupport<Stock, Void, StockD
|
|||||||
|
|
||||||
@GetMapping("daily/{id}")
|
@GetMapping("daily/{id}")
|
||||||
public GlobalResponse<Map<String, Object>> dailyCharts(@PathVariable("id") Long id) {
|
public GlobalResponse<Map<String, Object>> dailyCharts(@PathVariable("id") Long id) {
|
||||||
var data = stockService.findDailyRecent(id, 100);
|
var data = stockService.findDailyRecent(id, 100 + 60);
|
||||||
|
log.info("Size: {}", data.size());
|
||||||
var xList = new ArrayList<String>();
|
var xList = new ArrayList<String>();
|
||||||
var yList = new ArrayList<List<Double>>();
|
var yList = new ArrayList<List<Double>>();
|
||||||
for (var daily : data) {
|
for (var daily : data.subList(60, data.size() - 1)) {
|
||||||
xList.add(daily.getTradeDate().toString());
|
xList.add(daily.getTradeDate().toString());
|
||||||
yList.add(List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh()));
|
yList.add(List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh()));
|
||||||
}
|
}
|
||||||
return GlobalResponse.responseMapData(Map.of(
|
return GlobalResponse.responseMapData(Map.of(
|
||||||
"xList", xList, "yList", yList
|
"xList", xList,
|
||||||
|
"yList", yList,
|
||||||
|
"sma10", TaHelper.sma(data, 10, Daily::getHfqClose).subList(60, data.size() - 1),
|
||||||
|
"sma30", TaHelper.sma(data, 30, Daily::getHfqClose).subList(60, data.size() - 1),
|
||||||
|
"sma60", TaHelper.sma(data, 60, Daily::getHfqClose).subList(60, data.size() - 1)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -563,6 +563,7 @@ const financePropertyLabel = (idField: string, label: string, type: FinanceType,
|
|||||||
|
|
||||||
const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
||||||
return {
|
return {
|
||||||
|
className: 'mt-2',
|
||||||
type: 'chart',
|
type: 'chart',
|
||||||
height: 500,
|
height: 500,
|
||||||
api: api,
|
api: api,
|
||||||
@@ -638,7 +639,8 @@ const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: [
|
||||||
|
{
|
||||||
scale: true,
|
scale: true,
|
||||||
axisLine: {
|
axisLine: {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
@@ -662,6 +664,22 @@ const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
|||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scale: true,
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
type: 'inside',
|
type: 'inside',
|
||||||
@@ -680,6 +698,7 @@ const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
|||||||
{
|
{
|
||||||
type: 'candlestick',
|
type: 'candlestick',
|
||||||
data: '${yList || []}',
|
data: '${yList || []}',
|
||||||
|
yAxisIndex: 0,
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: '#eb5454',
|
color: '#eb5454',
|
||||||
color0: '#4aaa93',
|
color0: '#4aaa93',
|
||||||
@@ -687,7 +706,36 @@ const candleChart = (title: string, subtitle: string, api: Api): Schema => {
|
|||||||
borderColor0: '#4aaa93',
|
borderColor0: '#4aaa93',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: '${sma10 || []}',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(25,147,51,0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: '${sma30 || []}',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(10,94,131,0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
data: '${sma60 || []}',
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'none',
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgba(231,15,130,0.5)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user