feat: 建立批处理也能输出图表的套路
This commit is contained in:
@@ -34,6 +34,15 @@
|
|||||||
<artifactId>commonmark-ext-gfm-tables</artifactId>
|
<artifactId>commonmark-ext-gfm-tables</artifactId>
|
||||||
<version>0.26.0</version>
|
<version>0.26.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.thymeleaf</groupId>
|
||||||
|
<artifactId>thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
package com.lanyuanxiaoyao.leopard.strategy;
|
package com.lanyuanxiaoyao.leopard.strategy;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.lang.Dict;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.extra.template.TemplateConfig;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import cn.hutool.extra.template.TemplateEngine;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.Daily;
|
import cn.hutool.extra.template.TemplateUtil;
|
||||||
import com.lanyuanxiaoyao.leopard.core.entity.QStock;
|
import com.lanyuanxiaoyao.leopard.core.entity.QDaily;
|
||||||
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.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 com.lanyuanxiaoyao.leopard.core.strategy.TradeEngine;
|
|
||||||
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.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Comparator;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -32,296 +26,38 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
@SpringBootApplication(scanBasePackages = "com.lanyuanxiaoyao.leopard")
|
@SpringBootApplication(scanBasePackages = "com.lanyuanxiaoyao.leopard")
|
||||||
@EnableJpaAuditing
|
@EnableJpaAuditing
|
||||||
public class StrategyApplication {
|
public class StrategyApplication {
|
||||||
private static final ObjectMapper mapper = new ObjectMapper();
|
private static final TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH));
|
||||||
@Resource
|
|
||||||
private PyramidStockSelector pyramidStockSelector;
|
|
||||||
@Resource
|
|
||||||
private AssessmentService assessmentService;
|
|
||||||
@Resource
|
|
||||||
private TuShareService tuShareService;
|
|
||||||
@Resource
|
@Resource
|
||||||
private DailyRepository dailyRepository;
|
private DailyRepository dailyRepository;
|
||||||
@Resource
|
|
||||||
private StockRepository stockRepository;
|
|
||||||
@Resource
|
|
||||||
private StockService stockService;
|
|
||||||
@Resource
|
|
||||||
private TradeEngine tradeEngine;
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(StrategyApplication.class, args);
|
SpringApplication.run(StrategyApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void render(Map<String, Object> data) throws IOException {
|
|
||||||
Files.writeString(
|
|
||||||
Path.of("result.html"),
|
|
||||||
StrUtil.format(
|
|
||||||
// language=HTML
|
|
||||||
"""
|
|
||||||
<html lang='zh'>
|
|
||||||
<head>
|
|
||||||
<meta charset='utf-8'/>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
|
|
||||||
<title>Strategy</title>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/antd.min.css"/>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/helper.min.css"/>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/iconfont.min.css"/>
|
|
||||||
<style>
|
|
||||||
html, body, #root {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id='root'/>
|
|
||||||
</body>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/sdk.min.js"></script>
|
|
||||||
<script type='text/javascript'>
|
|
||||||
(function () {
|
|
||||||
const amis = amisRequire('amis/embed')
|
|
||||||
const amisJson = {
|
|
||||||
type: 'page',
|
|
||||||
title: 'Strategy',
|
|
||||||
data: {},
|
|
||||||
body: [
|
|
||||||
/*{
|
|
||||||
type: 'table2',
|
|
||||||
source: '${items}',
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'date',
|
|
||||||
label: 'Date',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'code',
|
|
||||||
label: 'Code',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sma5',
|
|
||||||
label: 'SMA-5',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sma10',
|
|
||||||
label: 'SMA-10',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},*/
|
|
||||||
{
|
|
||||||
type: 'chart',
|
|
||||||
height: 800,
|
|
||||||
config: {
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
animation: true,
|
|
||||||
animationDuration: 1000,
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
},
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
||||||
borderColor: '#333',
|
|
||||||
borderWidth: 1,
|
|
||||||
textStyle: {
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
padding: 12,
|
|
||||||
formatter: function (params) {
|
|
||||||
const param = params[0]
|
|
||||||
const open = param.data[1].toFixed(2)
|
|
||||||
const close = param.data[2].toFixed(2)
|
|
||||||
const lowest = param.data[3].toFixed(2)
|
|
||||||
const highest = param.data[4].toFixed(2)
|
|
||||||
|
|
||||||
return `<div class="text-center font-bold mb-2">${param.name}</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<span>开盘:</span>
|
|
||||||
<span class="font-bold ml-4">${open}</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<span>收盘:</span>
|
|
||||||
<span class="font-bold ml-4">${close}</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<span>最低:</span>
|
|
||||||
<span class="font-bold ml-4">${lowest}</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
|
||||||
<span>最高:</span>
|
|
||||||
<span class="font-bold ml-4">${highest}</span>
|
|
||||||
</div>`
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: '2%',
|
|
||||||
right: '2%',
|
|
||||||
top: '5%',
|
|
||||||
bottom: '12%',
|
|
||||||
containLabel: true,
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
data: '${xList || []}',
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e0e0e0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#666',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
scale: true,
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e0e0e0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#666',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
formatter: function (value) {
|
|
||||||
return value.toFixed(2)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'dashed',
|
|
||||||
color: '#f0f0f0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
type: 'inside',
|
|
||||||
start: 0,
|
|
||||||
end: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
show: true,
|
|
||||||
type: 'slider',
|
|
||||||
top: '90%',
|
|
||||||
start: 0,
|
|
||||||
end: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'candlestick',
|
|
||||||
data: '${yList || []}',
|
|
||||||
itemStyle: {
|
|
||||||
color: '#eb5454',
|
|
||||||
color0: '#4aaa93',
|
|
||||||
borderColor: '#eb5454',
|
|
||||||
borderColor0: '#4aaa93',
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
data: '${sma10 || []}',
|
|
||||||
smooth: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#dcb38a',
|
|
||||||
},
|
|
||||||
symbol: 'none',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
data: '${sma60 || []}',
|
|
||||||
smooth: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#6ce3c6',
|
|
||||||
},
|
|
||||||
symbol: 'none',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
data: '${sma120 || []}',
|
|
||||||
smooth: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#6cd5e3',
|
|
||||||
},
|
|
||||||
symbol: 'none',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
amis.embed('#root', amisJson, {}, {theme: 'antd'})
|
|
||||||
})()
|
|
||||||
</script>
|
|
||||||
</html>
|
|
||||||
""",
|
|
||||||
mapper.writeValueAsString(data)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@EventListener(ApplicationReadyEvent.class)
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
public void test() {
|
public void test() throws IOException {
|
||||||
var stock = stockRepository.findOne(QStock.stock.code.eq("000001.SZ")).orElseThrow();
|
var dailies = dailyRepository.findAll(
|
||||||
var asset = tradeEngine.backtest(
|
QDaily.daily.stock.code.eq("000001.SZ")
|
||||||
List.of(stock.getId()),
|
.and(QDaily.daily.tradeDate.after(LocalDate.of(2025, 1, 1))),
|
||||||
(now, currentAsset, dailies) -> {
|
QDaily.daily.tradeDate.asc()
|
||||||
return dailies.entrySet()
|
|
||||||
.stream()
|
|
||||||
.map(entry -> {
|
|
||||||
var stockId = entry.getKey();
|
|
||||||
var stockDailies = entry.getValue()
|
|
||||||
.stream()
|
|
||||||
.sorted(Comparator.comparing(Daily::getTradeDate))
|
|
||||||
.toList();
|
|
||||||
var yesterday = stockDailies.getLast();
|
|
||||||
if (yesterday.getHfqClose() > yesterday.getHfqOpen()) {
|
|
||||||
log.info("{} Buy for price {} {}", now, yesterday.getHfqOpen(), yesterday.getHfqClose());
|
|
||||||
return new TradeEngine.Trade(now, stockId, 100);
|
|
||||||
} else if (yesterday.getHfqClose() < yesterday.getHfqOpen()) {
|
|
||||||
var hold = currentAsset.getStocks().getOrDefault(stockId, 0);
|
|
||||||
if (hold > 0) {
|
|
||||||
log.info("{} Sell for price {} {}", now, yesterday.getHfqOpen(), yesterday.getHfqClose());
|
|
||||||
return new TradeEngine.Trade(now, stockId, -1 * hold);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.info("{} Hold for price {} {}", now, yesterday.getHfqOpen(), yesterday.getHfqClose());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(ObjectUtil::isNotNull)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
LocalDate.of(2024, 12, 1),
|
|
||||||
LocalDate.of(2024, 12, 31)
|
|
||||||
);
|
);
|
||||||
log.info("Final Cash: {}", asset.getCash());
|
|
||||||
for (var history : asset.getHistories()) {
|
var xList = new ArrayList<String>();
|
||||||
log.info("Date: {} Cash: {} Trade: {}", history.date(), history.cash(), history.trades().values());
|
var yList = new ArrayList<List<Double>>();
|
||||||
|
for (var daily : dailies) {
|
||||||
|
xList.add(daily.getTradeDate().toString());
|
||||||
|
yList.add(List.of(daily.getHfqOpen(), daily.getHfqClose(), daily.getHfqLow(), daily.getHfqHigh()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var template = engine.getTemplate("report.html");
|
||||||
|
Files.writeString(Path.of("report.html"), template.render(
|
||||||
|
Dict.create().set(
|
||||||
|
"charts",
|
||||||
|
Dict.create()
|
||||||
|
.set("日线数据", Map.of("xList", xList, "yList", yList))
|
||||||
|
)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
191
leopard-strategy/src/main/resources/templates/report.html
Normal file
191
leopard-strategy/src/main/resources/templates/report.html
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<html lang='zh'>
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'/>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
|
||||||
|
<title>Strategy</title>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/antd.min.css"/>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/helper.min.css"/>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/iconfont.min.css"/>
|
||||||
|
<style>
|
||||||
|
html, body, #root {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='root'></div>
|
||||||
|
</body>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/sdk.min.js"></script>
|
||||||
|
<script type='text/javascript' th:inline="javascript">
|
||||||
|
function candleChart(title, data) {
|
||||||
|
return {
|
||||||
|
type: 'service',
|
||||||
|
data: data,
|
||||||
|
body: {
|
||||||
|
className: 'mt-2',
|
||||||
|
type: 'chart',
|
||||||
|
height: 500,
|
||||||
|
config: {
|
||||||
|
title: {
|
||||||
|
text: title,
|
||||||
|
},
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
animation: true,
|
||||||
|
animationDuration: 1000,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'cross',
|
||||||
|
},
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||||
|
borderColor: '#333',
|
||||||
|
borderWidth: 1,
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
padding: 12,
|
||||||
|
formatter: function (params) {
|
||||||
|
const param = params[0]
|
||||||
|
const open = parseFloat(param.data[1]).toFixed(2)
|
||||||
|
const close = parseFloat(param.data[2]).toFixed(2)
|
||||||
|
const lowest = parseFloat(param.data[3]).toFixed(2)
|
||||||
|
const highest = parseFloat(param.data[4]).toFixed(2)
|
||||||
|
|
||||||
|
return `<div class="text-center font-bold mb-2">${param.name}</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span>开盘:</span>
|
||||||
|
<span class="font-bold ml-4">${open}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span>收盘:</span>
|
||||||
|
<span class="font-bold ml-4">${close}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span>最低:</span>
|
||||||
|
<span class="font-bold ml-4">${lowest}</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<span>最高:</span>
|
||||||
|
<span class="font-bold ml-4">${highest}</span>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '2%',
|
||||||
|
right: '2%',
|
||||||
|
top: '15%',
|
||||||
|
bottom: '15%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
data: '${xList || []}',
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e0e0e0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
scale: true,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e0e0e0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
formatter: function (value) {
|
||||||
|
return value.toFixed(2)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed',
|
||||||
|
color: '#f0f0f0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: true,
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataZoom: [
|
||||||
|
{
|
||||||
|
type: 'inside',
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
show: true,
|
||||||
|
type: 'slider',
|
||||||
|
top: '90%',
|
||||||
|
start: 0,
|
||||||
|
end: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'candlestick',
|
||||||
|
data: '${yList || []}',
|
||||||
|
yAxisIndex: 0,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#eb5454',
|
||||||
|
color0: '#4aaa93',
|
||||||
|
borderColor: '#eb5454',
|
||||||
|
borderColor0: '#4aaa93',
|
||||||
|
borderWidth: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = /*[[${charts}]]*/ {};
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
const amis = amisRequire('amis/embed')
|
||||||
|
const amisJson = {
|
||||||
|
type: 'page',
|
||||||
|
title: 'Strategy',
|
||||||
|
body: Object.keys(data)
|
||||||
|
.map(key => candleChart(key, data[key])),
|
||||||
|
}
|
||||||
|
amis.embed('#root', amisJson, {}, {theme: 'antd'})
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
5
pom.xml
5
pom.xml
@@ -63,6 +63,11 @@
|
|||||||
<artifactId>hutool-http</artifactId>
|
<artifactId>hutool-http</artifactId>
|
||||||
<version>${hutool.version}</version>
|
<version>${hutool.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-extra</artifactId>
|
||||||
|
<version>${hutool.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.yomahub</groupId>
|
<groupId>com.yomahub</groupId>
|
||||||
|
|||||||
Reference in New Issue
Block a user