mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
integrate backtest report in the build flow
This commit is contained in:
parent
d299932f5f
commit
15e6e810c7
15
Makefile
15
Makefile
|
@ -17,6 +17,9 @@ OSX_APP_GUI ?= webview
|
||||||
|
|
||||||
FRONTEND_EXPORT_DIR = frontend/out
|
FRONTEND_EXPORT_DIR = frontend/out
|
||||||
|
|
||||||
|
BACKTEST_REPORT_APP_DIR = apps/backtest-report
|
||||||
|
BACKTEST_REPORT_EXPORT_DIR = apps/backtest-report/out
|
||||||
|
|
||||||
all: bbgo-linux bbgo-darwin
|
all: bbgo-linux bbgo-darwin
|
||||||
|
|
||||||
$(BIN_DIR):
|
$(BIN_DIR):
|
||||||
|
@ -229,10 +232,18 @@ frontend/out/index.html: frontend/node_modules
|
||||||
pkg/server/assets.go: frontend/out/index.html
|
pkg/server/assets.go: frontend/out/index.html
|
||||||
go run ./util/embed -package server -output $@ $(FRONTEND_EXPORT_DIR)
|
go run ./util/embed -package server -output $@ $(FRONTEND_EXPORT_DIR)
|
||||||
|
|
||||||
embed: pkg/server/assets.go
|
$(BACKTEST_REPORT_APP_DIR)/node_modules:
|
||||||
|
cd $(BACKTEST_REPORT_APP_DIR) && yarn install
|
||||||
|
|
||||||
static: frontend/out/index.html pkg/server/assets.go
|
$(BACKTEST_REPORT_APP_DIR)/out/index.html: $(BACKTEST_REPORT_APP_DIR)/node_modules
|
||||||
|
cd $(BACKTEST_REPORT_APP_DIR) && yarn build && yarn export
|
||||||
|
|
||||||
|
pkg/backtest/assets.go: $(BACKTEST_REPORT_APP_DIR)/out/index.html
|
||||||
|
go run ./util/embed -package backtest -output $@ $(BACKTEST_REPORT_EXPORT_DIR)
|
||||||
|
|
||||||
|
embed: pkg/server/assets.go pkg/backtest/assets.go
|
||||||
|
|
||||||
|
static: frontend/out/index.html pkg/server/assets.go pkg/backtest/assets.go
|
||||||
|
|
||||||
PROTOS := \
|
PROTOS := \
|
||||||
$(wildcard pkg/pb/*.proto)
|
$(wildcard pkg/pb/*.proto)
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"export": "next build && next export"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^4.2.5",
|
"@mantine/core": "^4.2.5",
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
@ -1,287 +0,0 @@
|
||||||
import { format } from "d3-format";
|
|
||||||
import { tsvParse } from "d3-dsv";
|
|
||||||
import { timeFormat } from "d3-time-format";
|
|
||||||
import { timeParse } from "d3-time-format";
|
|
||||||
import * as React from "react";
|
|
||||||
import {
|
|
||||||
elderRay,
|
|
||||||
ema,
|
|
||||||
discontinuousTimeScaleProviderBuilder,
|
|
||||||
Chart,
|
|
||||||
ChartCanvas,
|
|
||||||
CurrentCoordinate,
|
|
||||||
BarSeries,
|
|
||||||
CandlestickSeries,
|
|
||||||
ElderRaySeries,
|
|
||||||
LineSeries,
|
|
||||||
MovingAverageTooltip,
|
|
||||||
OHLCTooltip,
|
|
||||||
SingleValueTooltip,
|
|
||||||
lastVisibleItemBasedZoomAnchor,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CrossHairCursor,
|
|
||||||
EdgeIndicator,
|
|
||||||
MouseCoordinateX,
|
|
||||||
MouseCoordinateY,
|
|
||||||
ZoomButtons,
|
|
||||||
withDeviceRatio,
|
|
||||||
withSize,
|
|
||||||
} from "react-financial-charts";
|
|
||||||
|
|
||||||
interface IOHLCData {
|
|
||||||
readonly close: number;
|
|
||||||
readonly date: Date;
|
|
||||||
readonly high: number;
|
|
||||||
readonly low: number;
|
|
||||||
readonly open: number;
|
|
||||||
readonly volume: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const parseDate = timeParse("%Y-%m-%d");
|
|
||||||
|
|
||||||
const parseData = () => {
|
|
||||||
return (d: any) => {
|
|
||||||
const date = parseDate(d.date);
|
|
||||||
if (date === null) {
|
|
||||||
d.date = new Date(Number(d.date));
|
|
||||||
} else {
|
|
||||||
d.date = new Date(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in d) {
|
|
||||||
if (key !== "date" && Object.prototype.hasOwnProperty.call(d, key)) {
|
|
||||||
d[key] = +d[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d as IOHLCData;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
interface WithOHLCDataProps {
|
|
||||||
readonly data: IOHLCData[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WithOHLCState {
|
|
||||||
data?: IOHLCData[];
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function withOHLCData(interval : string = "5m") {
|
|
||||||
return <TProps extends WithOHLCDataProps>(OriginalComponent: React.ComponentClass<TProps>) => {
|
|
||||||
return class WithOHLCData extends React.Component<Omit<TProps, "data">, WithOHLCState> {
|
|
||||||
public constructor(props: Omit<TProps, "data">) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
message: `Loading price data...`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidMount() {
|
|
||||||
fetch(
|
|
||||||
`/data/klines/ETHUSDT-5m.csv`,
|
|
||||||
)
|
|
||||||
.then((response) => response.text())
|
|
||||||
.then((data) => tsvParse(data, parseData()))
|
|
||||||
.then((data) => {
|
|
||||||
this.setState({
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.setState({
|
|
||||||
message: `Failed to fetch data.`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { data, message } = this.state;
|
|
||||||
if (data === undefined) {
|
|
||||||
return <div className="center">{message}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <OriginalComponent {...(this.props as TProps)} data={data} />;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
interface StockChartProps {
|
|
||||||
readonly data: IOHLCData[];
|
|
||||||
readonly height: number;
|
|
||||||
readonly dateTimeFormat?: string;
|
|
||||||
readonly width: number;
|
|
||||||
readonly ratio: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
class StockChart extends React.Component<StockChartProps> {
|
|
||||||
private readonly margin = { left: 0, right: 48, top: 0, bottom: 24 };
|
|
||||||
private readonly pricesDisplayFormat = format(".2f");
|
|
||||||
private readonly xScaleProvider = discontinuousTimeScaleProviderBuilder().inputDateAccessor(
|
|
||||||
(d: IOHLCData) => d.date,
|
|
||||||
);
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const { data: initialData, dateTimeFormat = "%d %b", height, ratio, width } = this.props;
|
|
||||||
|
|
||||||
const ema12 = ema()
|
|
||||||
.id(1)
|
|
||||||
.options({ windowSize: 12 })
|
|
||||||
.merge((d: any, c: any) => {
|
|
||||||
d.ema12 = c;
|
|
||||||
})
|
|
||||||
.accessor((d: any) => d.ema12);
|
|
||||||
|
|
||||||
const ema26 = ema()
|
|
||||||
.id(2)
|
|
||||||
.options({ windowSize: 26 })
|
|
||||||
.merge((d: any, c: any) => {
|
|
||||||
d.ema26 = c;
|
|
||||||
})
|
|
||||||
.accessor((d: any) => d.ema26);
|
|
||||||
|
|
||||||
const elder = elderRay();
|
|
||||||
|
|
||||||
const calculatedData = elder(ema26(ema12(initialData)));
|
|
||||||
|
|
||||||
const { margin, xScaleProvider } = this;
|
|
||||||
|
|
||||||
const { data, xScale, xAccessor, displayXAccessor } = xScaleProvider(calculatedData);
|
|
||||||
|
|
||||||
const max = xAccessor(data[data.length - 1]);
|
|
||||||
const min = xAccessor(data[Math.max(0, data.length - 100)]);
|
|
||||||
const xExtents = [min, max + 5];
|
|
||||||
|
|
||||||
const gridHeight = height - margin.top - margin.bottom;
|
|
||||||
|
|
||||||
const elderRayHeight = 100;
|
|
||||||
const elderRayOrigin = (_: number, h: number) => [0, h - elderRayHeight];
|
|
||||||
const barChartHeight = gridHeight / 4;
|
|
||||||
const barChartOrigin = (_: number, h: number) => [0, h - barChartHeight - elderRayHeight];
|
|
||||||
const chartHeight = gridHeight - elderRayHeight;
|
|
||||||
|
|
||||||
const timeDisplayFormat = timeFormat(dateTimeFormat);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ChartCanvas
|
|
||||||
height={height}
|
|
||||||
ratio={ratio}
|
|
||||||
width={width}
|
|
||||||
margin={margin}
|
|
||||||
data={data}
|
|
||||||
displayXAccessor={displayXAccessor}
|
|
||||||
seriesName="Data"
|
|
||||||
xScale={xScale}
|
|
||||||
xAccessor={xAccessor}
|
|
||||||
xExtents={xExtents}
|
|
||||||
zoomAnchor={lastVisibleItemBasedZoomAnchor}
|
|
||||||
>
|
|
||||||
<Chart id={2} height={barChartHeight} origin={barChartOrigin} yExtents={this.barChartExtents}>
|
|
||||||
<BarSeries fillStyle={this.volumeColor} yAccessor={this.volumeSeries} />
|
|
||||||
</Chart>
|
|
||||||
<Chart id={3} height={chartHeight} yExtents={this.candleChartExtents}>
|
|
||||||
<XAxis showGridLines showTicks={false} showTickLabel={false} />
|
|
||||||
<YAxis showGridLines tickFormat={this.pricesDisplayFormat} />
|
|
||||||
<CandlestickSeries />
|
|
||||||
<LineSeries yAccessor={ema26.accessor()} strokeStyle={ema26.stroke()} />
|
|
||||||
<CurrentCoordinate yAccessor={ema26.accessor()} fillStyle={ema26.stroke()} />
|
|
||||||
<LineSeries yAccessor={ema12.accessor()} strokeStyle={ema12.stroke()} />
|
|
||||||
<CurrentCoordinate yAccessor={ema12.accessor()} fillStyle={ema12.stroke()} />
|
|
||||||
<MouseCoordinateY rectWidth={margin.right} displayFormat={this.pricesDisplayFormat} />
|
|
||||||
<EdgeIndicator
|
|
||||||
itemType="last"
|
|
||||||
rectWidth={margin.right}
|
|
||||||
fill={this.openCloseColor}
|
|
||||||
lineStroke={this.openCloseColor}
|
|
||||||
displayFormat={this.pricesDisplayFormat}
|
|
||||||
yAccessor={this.yEdgeIndicator}
|
|
||||||
/>
|
|
||||||
<MovingAverageTooltip
|
|
||||||
origin={[8, 24]}
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
yAccessor: ema26.accessor(),
|
|
||||||
type: "EMA",
|
|
||||||
stroke: ema26.stroke(),
|
|
||||||
windowSize: ema26.options().windowSize,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
yAccessor: ema12.accessor(),
|
|
||||||
type: "EMA",
|
|
||||||
stroke: ema12.stroke(),
|
|
||||||
windowSize: ema12.options().windowSize,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ZoomButtons />
|
|
||||||
<OHLCTooltip origin={[8, 16]} />
|
|
||||||
</Chart>
|
|
||||||
<Chart
|
|
||||||
id={4}
|
|
||||||
height={elderRayHeight}
|
|
||||||
yExtents={[0, elder.accessor()]}
|
|
||||||
origin={elderRayOrigin}
|
|
||||||
padding={{ top: 8, bottom: 8 }}
|
|
||||||
>
|
|
||||||
<XAxis showGridLines gridLinesStrokeStyle="#e0e3eb" />
|
|
||||||
<YAxis ticks={4} tickFormat={this.pricesDisplayFormat} />
|
|
||||||
|
|
||||||
<MouseCoordinateX displayFormat={timeDisplayFormat} />
|
|
||||||
<MouseCoordinateY rectWidth={margin.right} displayFormat={this.pricesDisplayFormat} />
|
|
||||||
|
|
||||||
<ElderRaySeries yAccessor={elder.accessor()} />
|
|
||||||
|
|
||||||
<SingleValueTooltip
|
|
||||||
yAccessor={elder.accessor()}
|
|
||||||
yLabel="Elder Ray"
|
|
||||||
yDisplayFormat={(d: any) =>
|
|
||||||
`${this.pricesDisplayFormat(d.bullPower)}, ${this.pricesDisplayFormat(d.bearPower)}`
|
|
||||||
}
|
|
||||||
origin={[8, 16]}
|
|
||||||
/>
|
|
||||||
</Chart>
|
|
||||||
<CrossHairCursor />
|
|
||||||
</ChartCanvas>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly barChartExtents = (data: IOHLCData) => {
|
|
||||||
return data.volume;
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly candleChartExtents = (data: IOHLCData) => {
|
|
||||||
return [data.high, data.low];
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly yEdgeIndicator = (data: IOHLCData) => {
|
|
||||||
return data.close;
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly volumeColor = (data: IOHLCData) => {
|
|
||||||
return data.close > data.open ? "rgba(38, 166, 154, 0.3)" : "rgba(239, 83, 80, 0.3)";
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly volumeSeries = (data: IOHLCData) => {
|
|
||||||
return data.volume;
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly openCloseColor = (data: IOHLCData) => {
|
|
||||||
return data.close > data.open ? "#26a69a" : "#ef5350";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withOHLCData()(withSize({ style: { minHeight: 400 } })(withDeviceRatio()(StockChart)));
|
|
||||||
|
|
||||||
export const MinutesStockChart = withOHLCData("MINUTES")(
|
|
||||||
withSize({ style: { minHeight: 400 } })(withDeviceRatio()(StockChart)),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SecondsStockChart = withOHLCData("SECONDS")(
|
|
||||||
withSize({ style: { minHeight: 400 } })(withDeviceRatio()(StockChart)),
|
|
||||||
);
|
|
Loading…
Reference in New Issue
Block a user