mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 00:35:15 +00:00
Merge branch 'main' into strategy/pivotshort
This commit is contained in:
commit
8119afbb44
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
/config/bbgo.yaml
|
/config/bbgo.yaml
|
||||||
|
|
||||||
|
/localconfig
|
||||||
|
|
||||||
/pkg/server/assets.go
|
/pkg/server/assets.go
|
||||||
|
|
||||||
bbgo.sqlite3
|
bbgo.sqlite3
|
||||||
|
|
|
@ -113,6 +113,19 @@ const fetchPositionHistory = (basePath: string, runID: string, filename: string)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectPositionHistory = (data: PositionHistoryEntry[], since: Date, until: Date): PositionHistoryEntry[] => {
|
||||||
|
const entries: PositionHistoryEntry[] = [];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const d = data[i];
|
||||||
|
if (d.time < since || d.time > until) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(d)
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
const fetchOrders = (basePath: string, runID: string) => {
|
const fetchOrders = (basePath: string, runID: string) => {
|
||||||
return fetch(
|
return fetch(
|
||||||
`${basePath}/${runID}/orders.tsv`,
|
`${basePath}/${runID}/orders.tsv`,
|
||||||
|
@ -124,6 +137,19 @@ const fetchOrders = (basePath: string, runID: string) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectOrders = (data: Order[], since: Date, until: Date): Order[] => {
|
||||||
|
const entries: Order[] = [];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const d = data[i];
|
||||||
|
if (d.time && (d.time < since || d.time > until)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.push(d);
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
const parseInterval = (s: string) => {
|
const parseInterval = (s: string) => {
|
||||||
switch (s) {
|
switch (s) {
|
||||||
case "1m":
|
case "1m":
|
||||||
|
@ -390,7 +416,7 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
||||||
const resizeObserver = useRef<any>();
|
const resizeObserver = useRef<any>();
|
||||||
const intervals = props.reportSummary.intervals || [];
|
const intervals = props.reportSummary.intervals || [];
|
||||||
|
|
||||||
intervals.sort((a,b) => {
|
intervals.sort((a, b) => {
|
||||||
const as = parseInterval(a)
|
const as = parseInterval(a)
|
||||||
const bs = parseInterval(b)
|
const bs = parseInterval(b)
|
||||||
if (as < bs) {
|
if (as < bs) {
|
||||||
|
@ -403,7 +429,6 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
||||||
|
|
||||||
const [currentInterval, setCurrentInterval] = useState(intervals.length > 0 ? intervals[intervals.length - 1] : '1m');
|
const [currentInterval, setCurrentInterval] = useState(intervals.length > 0 ? intervals[intervals.length - 1] : '1m');
|
||||||
const [showPositionBase, setShowPositionBase] = useState(false);
|
const [showPositionBase, setShowPositionBase] = useState(false);
|
||||||
const [showCanceledOrders, setShowCanceledOrders] = useState(false);
|
|
||||||
const [showPositionAverageCost, setShowPositionAverageCost] = useState(false);
|
const [showPositionAverageCost, setShowPositionAverageCost] = useState(false);
|
||||||
const [orders, setOrders] = useState<Order[]>([]);
|
const [orders, setOrders] = useState<Order[]>([]);
|
||||||
|
|
||||||
|
@ -412,7 +437,6 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
||||||
new Date(props.reportSummary.endTime),
|
new Date(props.reportSummary.endTime),
|
||||||
]
|
]
|
||||||
const [selectedTimeRange, setSelectedTimeRange] = useState(reportTimeRange)
|
const [selectedTimeRange, setSelectedTimeRange] = useState(reportTimeRange)
|
||||||
const [timeRange, setTimeRange] = useState(reportTimeRange);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chartContainerRef.current || chartContainerRef.current.children.length > 0) {
|
if (!chartContainerRef.current || chartContainerRef.current.children.length > 0) {
|
||||||
|
@ -423,7 +447,7 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
||||||
const fetchers = [];
|
const fetchers = [];
|
||||||
const ordersFetcher = fetchOrders(props.basePath, props.runID).then((orders: Order[] | void) => {
|
const ordersFetcher = fetchOrders(props.basePath, props.runID).then((orders: Order[] | void) => {
|
||||||
if (orders) {
|
if (orders) {
|
||||||
const markers = ordersToMarkers(currentInterval, orders);
|
const markers = ordersToMarkers(currentInterval, selectOrders(orders, selectedTimeRange[0], selectedTimeRange[1]));
|
||||||
chartData.orders = orders;
|
chartData.orders = orders;
|
||||||
chartData.markers = markers;
|
chartData.markers = markers;
|
||||||
setOrders(orders);
|
setOrders(orders);
|
||||||
|
@ -436,7 +460,8 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
||||||
const manifest = props.reportSummary?.manifests[0];
|
const manifest = props.reportSummary?.manifests[0];
|
||||||
if (manifest && manifest.type === "strategyProperty" && manifest.strategyProperty === "position") {
|
if (manifest && manifest.type === "strategyProperty" && manifest.strategyProperty === "position") {
|
||||||
const positionHistoryFetcher = fetchPositionHistory(props.basePath, props.runID, manifest.filename).then((data) => {
|
const positionHistoryFetcher = fetchPositionHistory(props.basePath, props.runID, manifest.filename).then((data) => {
|
||||||
chartData.positionHistory = data;
|
chartData.positionHistory = selectPositionHistory(data as PositionHistoryEntry[], selectedTimeRange[0], selectedTimeRange[1]);
|
||||||
|
// chartData.positionHistory = data;
|
||||||
});
|
});
|
||||||
fetchers.push(positionHistoryFetcher);
|
fetchers.push(positionHistoryFetcher);
|
||||||
}
|
}
|
||||||
|
@ -594,7 +619,7 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
||||||
|
|
||||||
<TimeRangeSlider
|
<TimeRangeSlider
|
||||||
selectedInterval={selectedTimeRange}
|
selectedInterval={selectedTimeRange}
|
||||||
timelineInterval={timeRange}
|
timelineInterval={reportTimeRange}
|
||||||
formatTick={(ms: Date) => format(new Date(ms), 'M d HH')}
|
formatTick={(ms: Date) => format(new Date(ms), 'M d HH')}
|
||||||
step={1000 * parseInterval(currentInterval)}
|
step={1000 * parseInterval(currentInterval)}
|
||||||
onChange={(tr: any) => {
|
onChange={(tr: any) => {
|
||||||
|
@ -661,12 +686,12 @@ const createLegendUpdater = (legend: HTMLDivElement, prefix: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatDate = (d : Date) : string => {
|
const formatDate = (d: Date): string => {
|
||||||
return moment(d).format("MMM Do YY hh:mm:ss A Z");
|
return moment(d).format("MMM Do YY hh:mm:ss A Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
const createOHLCLegendUpdater = (legend: HTMLDivElement, prefix: string) => {
|
const createOHLCLegendUpdater = (legend: HTMLDivElement, prefix: string) => {
|
||||||
return (param: any, time : any) => {
|
return (param: any, time: any) => {
|
||||||
if (param) {
|
if (param) {
|
||||||
const change = Math.round((param.close - param.open) * 100.0) / 100.0
|
const change = Math.round((param.close - param.open) * 100.0) / 100.0
|
||||||
const changePercentage = Math.round((param.close - param.open) / param.close * 10000.0) / 100.0;
|
const changePercentage = Math.round((param.close - param.open) / param.close * 10000.0) / 100.0;
|
||||||
|
|
|
@ -19,7 +19,7 @@ backtest:
|
||||||
# see here for more details
|
# see here for more details
|
||||||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||||
startTime: "2022-01-01"
|
startTime: "2022-01-01"
|
||||||
endTime: "2022-06-18"
|
endTime: "2022-06-30"
|
||||||
symbols:
|
symbols:
|
||||||
- BTCUSDT
|
- BTCUSDT
|
||||||
accounts:
|
accounts:
|
||||||
|
@ -36,7 +36,12 @@ exchangeStrategies:
|
||||||
symbol: BTCUSDT
|
symbol: BTCUSDT
|
||||||
|
|
||||||
# interval is how long do you want to update your order price and quantity
|
# interval is how long do you want to update your order price and quantity
|
||||||
interval: 1h
|
interval: 5m
|
||||||
|
|
||||||
|
# ATR window used by Supertrend
|
||||||
|
window: 49
|
||||||
|
# ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are
|
||||||
|
supertrendMultiplier: 4
|
||||||
|
|
||||||
# leverage is the leverage of the orders
|
# leverage is the leverage of the orders
|
||||||
leverage: 1.0
|
leverage: 1.0
|
||||||
|
@ -45,18 +50,31 @@ exchangeStrategies:
|
||||||
fastDEMAWindow: 144
|
fastDEMAWindow: 144
|
||||||
slowDEMAWindow: 169
|
slowDEMAWindow: 169
|
||||||
|
|
||||||
# Supertrend indicator parameters
|
# Use linear regression as trend confirmation
|
||||||
superTrend:
|
linearRegression:
|
||||||
# ATR window used by Supertrend
|
interval: 5m
|
||||||
averageTrueRangeWindow: 39
|
window: 80
|
||||||
# ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are
|
|
||||||
averageTrueRangeMultiplier: 3
|
|
||||||
|
|
||||||
# TP according to ATR multiple, 0 to disable this
|
# TP according to ATR multiple, 0 to disable this
|
||||||
takeProfitMultiplier: 3
|
TakeProfitAtrMultiplier: 0
|
||||||
|
|
||||||
# Set SL price to the low of the triggering Kline
|
# Set SL price to the low of the triggering Kline
|
||||||
stopLossByTriggeringK: true
|
stopLossByTriggeringK: false
|
||||||
|
|
||||||
# TP/SL by reversed signals
|
# TP/SL by reversed supertrend signal
|
||||||
tpslBySignal: true
|
stopByReversedSupertrend: false
|
||||||
|
|
||||||
|
# TP/SL by reversed DEMA signal
|
||||||
|
stopByReversedDema: false
|
||||||
|
|
||||||
|
# TP/SL by reversed linear regression signal
|
||||||
|
stopByReversedLinGre: false
|
||||||
|
|
||||||
|
exits:
|
||||||
|
# roiStopLoss is the stop loss percentage of the position ROI (currently the price change)
|
||||||
|
- roiStopLoss:
|
||||||
|
percentage: 4%
|
||||||
|
- protectiveStopLoss:
|
||||||
|
activationRatio: 3%
|
||||||
|
stopLossRatio: 2%
|
||||||
|
placeStopOrder: false
|
||||||
|
|
|
@ -58,4 +58,4 @@ bbgo [flags]
|
||||||
* [bbgo userdatastream](bbgo_userdatastream.md) - Listen to session events (orderUpdate, tradeUpdate, balanceUpdate, balanceSnapshot)
|
* [bbgo userdatastream](bbgo_userdatastream.md) - Listen to session events (orderUpdate, tradeUpdate, balanceUpdate, balanceSnapshot)
|
||||||
* [bbgo version](bbgo_version.md) - show version name
|
* [bbgo version](bbgo_version.md) - show version name
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -42,4 +42,4 @@ bbgo account [--session SESSION] [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -51,4 +51,4 @@ bbgo backtest [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -41,4 +41,4 @@ bbgo balances [--session SESSION] [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -40,4 +40,4 @@ bbgo build [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -50,4 +50,4 @@ bbgo cancel-order [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -42,4 +42,4 @@ bbgo deposits [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -49,4 +49,4 @@ bbgo execute-order --session SESSION --symbol SYMBOL --side SIDE --target-quanti
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -43,4 +43,4 @@ bbgo get-order --session SESSION --order-id ORDER_ID [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -43,4 +43,4 @@ bbgo kline [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -42,4 +42,4 @@ bbgo list-orders open|closed --session SESSION --symbol SYMBOL [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -39,4 +39,4 @@ margin related history
|
||||||
* [bbgo margin loans](bbgo_margin_loans.md) - query loans history
|
* [bbgo margin loans](bbgo_margin_loans.md) - query loans history
|
||||||
* [bbgo margin repays](bbgo_margin_repays.md) - query repay history
|
* [bbgo margin repays](bbgo_margin_repays.md) - query repay history
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -42,4 +42,4 @@ bbgo margin interests --session=SESSION_NAME --asset=ASSET [flags]
|
||||||
|
|
||||||
* [bbgo margin](bbgo_margin.md) - margin related history
|
* [bbgo margin](bbgo_margin.md) - margin related history
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -42,4 +42,4 @@ bbgo margin loans --session=SESSION_NAME --asset=ASSET [flags]
|
||||||
|
|
||||||
* [bbgo margin](bbgo_margin.md) - margin related history
|
* [bbgo margin](bbgo_margin.md) - margin related history
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -42,4 +42,4 @@ bbgo margin repays --session=SESSION_NAME --asset=ASSET [flags]
|
||||||
|
|
||||||
* [bbgo margin](bbgo_margin.md) - margin related history
|
* [bbgo margin](bbgo_margin.md) - margin related history
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -41,4 +41,4 @@ bbgo market [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -13,6 +13,7 @@ bbgo optimize [flags]
|
||||||
--json print optimizer metrics in json format
|
--json print optimizer metrics in json format
|
||||||
--optimizer-config string config file (default "optimizer.yaml")
|
--optimizer-config string config file (default "optimizer.yaml")
|
||||||
--output string backtest report output directory (default "output")
|
--output string backtest report output directory (default "output")
|
||||||
|
--tsv print optimizer metrics in csv format
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
@ -43,4 +44,4 @@ bbgo optimize [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -43,4 +43,4 @@ bbgo orderbook --session=[exchange_name] --symbol=[pair_name] [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -41,4 +41,4 @@ bbgo orderupdate [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -13,11 +13,13 @@ bbgo pnl [flags]
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
```
|
```
|
||||||
-h, --help help for pnl
|
-h, --help help for pnl
|
||||||
--include-transfer convert transfer records into trades
|
--include-transfer convert transfer records into trades
|
||||||
--limit int number of trades
|
--limit uint number of trades
|
||||||
--session string target exchange
|
--session stringArray target exchange sessions
|
||||||
--symbol string trading symbol
|
--since string query trades from a time point
|
||||||
|
--symbol string trading symbol
|
||||||
|
--sync sync before loading trades
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
@ -48,4 +50,4 @@ bbgo pnl [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -51,4 +51,4 @@ bbgo run [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -45,4 +45,4 @@ bbgo submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANT
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -43,4 +43,4 @@ bbgo sync [--session=[exchange_name]] [--symbol=[pair_name]] [[--since=yyyy/mm/d
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -43,4 +43,4 @@ bbgo trades --session=[exchange_name] --symbol=[pair_name] [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -41,4 +41,4 @@ bbgo tradeupdate --session=[exchange_name] [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -43,4 +43,4 @@ bbgo transfer-history [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -41,4 +41,4 @@ bbgo userdatastream [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
|
@ -40,4 +40,4 @@ bbgo version [flags]
|
||||||
|
|
||||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||||
|
|
||||||
###### Auto generated by spf13/cobra on 17-Jun-2022
|
###### Auto generated by spf13/cobra on 10-Jul-2022
|
||||||
|
|
83
doc/release/v1.36.0.md
Normal file
83
doc/release/v1.36.0.md
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Fixed backtest stop limit order / stop market order emulation.
|
||||||
|
- Fixed optimizer panic issue (when report is nil)
|
||||||
|
- Fixed pnl command market settings.
|
||||||
|
- Fixed MAX API endpoints.
|
||||||
|
|
||||||
|
## Improvements
|
||||||
|
|
||||||
|
- Improved supertrend strategy.
|
||||||
|
- Improved pivotshort strategy.
|
||||||
|
- Refactor reward service sync.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added tearsheet backend api
|
||||||
|
- Added seriesExtend for indicators.
|
||||||
|
- Added progressbar to optimizer.
|
||||||
|
|
||||||
|
## New API
|
||||||
|
|
||||||
|
- Added ExitMethodSet (trailing stop, roi stop loss, roi take profit, ... etc)
|
||||||
|
- Added new graceful shutdown API, persistence API, notification API, order executor API.
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/c9s/bbgo/compare/v1.35.0...main)
|
||||||
|
|
||||||
|
- [#799](https://github.com/c9s/bbgo/pull/799): Improve supertrend strategy
|
||||||
|
- [#801](https://github.com/c9s/bbgo/pull/801): feature: optimizer: support --tsv option and render tsv output
|
||||||
|
- [#800](https://github.com/c9s/bbgo/pull/800): fix: fix exit method for trailing stop
|
||||||
|
- [#798](https://github.com/c9s/bbgo/pull/798): fix: fix trailingstop and add long position test case
|
||||||
|
- [#797](https://github.com/c9s/bbgo/pull/797): feature: re-implement trailing stop and add mock test
|
||||||
|
- [#796](https://github.com/c9s/bbgo/pull/796): strategy/pivotshort: add supportTakeProfit method
|
||||||
|
- [#793](https://github.com/c9s/bbgo/pull/793): Fix pnl command
|
||||||
|
- [#795](https://github.com/c9s/bbgo/pull/795): optimizer/fix: prevent from crashing if missing SummaryReport
|
||||||
|
- [#794](https://github.com/c9s/bbgo/pull/794): strategy/pivotshort: fix resistance updater
|
||||||
|
- [#792](https://github.com/c9s/bbgo/pull/792): strategy/pivotshort: fix findNextResistancePriceAndPlaceOrders
|
||||||
|
- [#791](https://github.com/c9s/bbgo/pull/791): strategy: pivotshort: refactor breaklow logics
|
||||||
|
- [#790](https://github.com/c9s/bbgo/pull/790): fix: strategy: pivoshort: cancel order when shutdown
|
||||||
|
- [#789](https://github.com/c9s/bbgo/pull/789): strategy: pivotshort: add more improvements for margin
|
||||||
|
- [#787](https://github.com/c9s/bbgo/pull/787): strategy: pivotshort: use active orderbook to maintain the resistance orders
|
||||||
|
- [#786](https://github.com/c9s/bbgo/pull/786): strategy: pivotshort: resistance short
|
||||||
|
- [#731](https://github.com/c9s/bbgo/pull/731): add tearsheet backend api (Sharpe)
|
||||||
|
- [#784](https://github.com/c9s/bbgo/pull/784): strategy: pivotshort: fix stopEMA
|
||||||
|
- [#785](https://github.com/c9s/bbgo/pull/785): optimizer: add progressbar
|
||||||
|
- [#778](https://github.com/c9s/bbgo/pull/778): feature: add seriesExtend
|
||||||
|
- [#783](https://github.com/c9s/bbgo/pull/783): fix: pivotshort: fix kline history loading
|
||||||
|
- [#782](https://github.com/c9s/bbgo/pull/782): refactor: moving exit methods from pivotshort to the core
|
||||||
|
- [#781](https://github.com/c9s/bbgo/pull/781): strategy: pivotshort: optimize and update config
|
||||||
|
- [#775](https://github.com/c9s/bbgo/pull/775): test: backtest: add order cancel test case
|
||||||
|
- [#773](https://github.com/c9s/bbgo/pull/773): fix: fix backtest taker order execution
|
||||||
|
- [#772](https://github.com/c9s/bbgo/pull/772): fix: backtest: fix stop order backtest, add more test cases and assertions
|
||||||
|
- [#770](https://github.com/c9s/bbgo/pull/770): fix: fix backtest stop limit order matching and add test cases
|
||||||
|
- [#769](https://github.com/c9s/bbgo/pull/769): backtest-report: sort intervals
|
||||||
|
- [#768](https://github.com/c9s/bbgo/pull/768): feature: backtest: add ohlc legend
|
||||||
|
- [#766](https://github.com/c9s/bbgo/pull/766): backtest-report: add time range slider
|
||||||
|
- [#765](https://github.com/c9s/bbgo/pull/765): improve: backtest-report layout improvements, EMA indicators and fixed the clean up issue
|
||||||
|
- [#764](https://github.com/c9s/bbgo/pull/764): strategy/pivotshort: refactor exit methods and add protection stop exit method
|
||||||
|
- [#761](https://github.com/c9s/bbgo/pull/761): datasource: refactor glassnodeapi
|
||||||
|
- [#760](https://github.com/c9s/bbgo/pull/760): doc: fix link
|
||||||
|
- [#758](https://github.com/c9s/bbgo/pull/758): improve: add pnl cmd options and fix trade query
|
||||||
|
- [#757](https://github.com/c9s/bbgo/pull/757): totp-user: add default user 'bbgo'
|
||||||
|
- [#756](https://github.com/c9s/bbgo/pull/756): refactor: clean up rsmaker, xbalance, dca, pivotshort strategies
|
||||||
|
- [#755](https://github.com/c9s/bbgo/pull/755): improve: bbgo: call global persistence facade to sync data
|
||||||
|
- [#754](https://github.com/c9s/bbgo/pull/754): optimizer: refactor max num of process in optimizer configs
|
||||||
|
- [#750](https://github.com/c9s/bbgo/pull/750): refactor: persistence singleton and improve backtest cancel performance
|
||||||
|
- [#753](https://github.com/c9s/bbgo/pull/753): optimizer: add max num of thread in config
|
||||||
|
- [#752](https://github.com/c9s/bbgo/pull/752): Upgrade nextjs from 11 to 12
|
||||||
|
- [#751](https://github.com/c9s/bbgo/pull/751): fix: reformat go code
|
||||||
|
- [#746](https://github.com/c9s/bbgo/pull/746): pivotshort: add strategy controller
|
||||||
|
- [#747](https://github.com/c9s/bbgo/pull/747): strategy/supertrend: use new order executor api
|
||||||
|
- [#748](https://github.com/c9s/bbgo/pull/748): bollmaker: remove redundant code for adapting new order executor api
|
||||||
|
- [#749](https://github.com/c9s/bbgo/pull/749): improve: add parallel local process executor for optimizer
|
||||||
|
- [#639](https://github.com/c9s/bbgo/pull/639): strategy: rsmaker: initial idea prototype
|
||||||
|
- [#745](https://github.com/c9s/bbgo/pull/745): fix: depth: do not test depth buffer when race is on
|
||||||
|
- [#744](https://github.com/c9s/bbgo/pull/744): refactor: refactor and update the support strategy
|
||||||
|
- [#743](https://github.com/c9s/bbgo/pull/743): strategy/bollmaker: refactor and clean up
|
||||||
|
- [#742](https://github.com/c9s/bbgo/pull/742): refactor: clean up bbgo.Notifiability
|
||||||
|
- [#739](https://github.com/c9s/bbgo/pull/739): refactor: redesign order executor api
|
||||||
|
- [#738](https://github.com/c9s/bbgo/pull/738): feature: binance: add binance spot rebate history support
|
||||||
|
- [#736](https://github.com/c9s/bbgo/pull/736): fix: gosimple alert
|
||||||
|
- [#737](https://github.com/c9s/bbgo/pull/737): refactor: refactor reward service sync
|
||||||
|
- [#732](https://github.com/c9s/bbgo/pull/732): Refactor grid panel
|
||||||
|
- [#734](https://github.com/c9s/bbgo/pull/734): fix: apply gofmt on all files, add revive action
|
|
@ -23,12 +23,27 @@ Supertrend strategy needs margin enabled in order to submit short orders, and yo
|
||||||
- The MA window of the ATR indicator used by Supertrend.
|
- The MA window of the ATR indicator used by Supertrend.
|
||||||
- `averageTrueRangeMultiplier`
|
- `averageTrueRangeMultiplier`
|
||||||
- Multiplier for calculating upper and lower bond prices, the higher, the stronger the trends are, but also makes it less sensitive.
|
- Multiplier for calculating upper and lower bond prices, the higher, the stronger the trends are, but also makes it less sensitive.
|
||||||
- `takeProfitMultiplier`
|
- `linearRegression`
|
||||||
|
- Use linear regression as trend confirmation
|
||||||
|
- `interval`
|
||||||
|
- Time interval of linear regression
|
||||||
|
- `window`
|
||||||
|
- Window of linear regression
|
||||||
|
- `takeProfitAtrMultiplier`
|
||||||
- TP according to ATR multiple, 0 to disable this.
|
- TP according to ATR multiple, 0 to disable this.
|
||||||
- `stopLossByTriggeringK`
|
- `stopLossByTriggeringK`
|
||||||
- Set SL price to the low of the triggering Kline.
|
- Set SL price to the low/high of the triggering Kline.
|
||||||
- `tpslBySignal`
|
- `stopByReversedSupertrend`
|
||||||
- TP/SL by reversed signals.
|
- TP/SL by reversed supertrend signal.
|
||||||
|
- `stopByReversedDema`
|
||||||
|
- TP/SL by reversed DEMA signal.
|
||||||
|
- `stopByReversedLinGre`
|
||||||
|
- TP/SL by reversed linear regression signal.
|
||||||
|
- `exits`
|
||||||
|
- Exit methods to TP/SL
|
||||||
|
- `roiStopLoss`
|
||||||
|
- The stop loss percentage of the position ROI (currently the price change)
|
||||||
|
- `percentage`
|
||||||
|
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
|
|
|
@ -19,6 +19,8 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
||||||
var bidVolume = fixedpoint.Zero
|
var bidVolume = fixedpoint.Zero
|
||||||
var askVolume = fixedpoint.Zero
|
var askVolume = fixedpoint.Zero
|
||||||
var feeUSD = fixedpoint.Zero
|
var feeUSD = fixedpoint.Zero
|
||||||
|
var grossProfit = fixedpoint.Zero
|
||||||
|
var grossLoss = fixedpoint.Zero
|
||||||
|
|
||||||
if len(trades) == 0 {
|
if len(trades) == 0 {
|
||||||
return &AverageCostPnlReport{
|
return &AverageCostPnlReport{
|
||||||
|
@ -64,6 +66,12 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
||||||
totalNetProfit = totalNetProfit.Add(netProfit)
|
totalNetProfit = totalNetProfit.Add(netProfit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if profit.Sign() > 0 {
|
||||||
|
grossProfit = grossProfit.Add(profit)
|
||||||
|
} else if profit.Sign() < 0 {
|
||||||
|
grossLoss = grossLoss.Add(profit)
|
||||||
|
}
|
||||||
|
|
||||||
if trade.IsBuyer {
|
if trade.IsBuyer {
|
||||||
bidVolume = bidVolume.Add(trade.Quantity)
|
bidVolume = bidVolume.Add(trade.Quantity)
|
||||||
} else {
|
} else {
|
||||||
|
@ -96,8 +104,12 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
||||||
Profit: totalProfit,
|
Profit: totalProfit,
|
||||||
NetProfit: totalNetProfit,
|
NetProfit: totalNetProfit,
|
||||||
UnrealizedProfit: unrealizedProfit,
|
UnrealizedProfit: unrealizedProfit,
|
||||||
AverageCost: position.AverageCost,
|
|
||||||
FeeInUSD: totalProfit.Sub(totalNetProfit),
|
GrossProfit: grossProfit,
|
||||||
CurrencyFees: currencyFees,
|
GrossLoss: grossLoss,
|
||||||
|
|
||||||
|
AverageCost: position.AverageCost,
|
||||||
|
FeeInUSD: totalProfit.Sub(totalNetProfit),
|
||||||
|
CurrencyFees: currencyFees,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,14 @@ type AverageCostPnlReport struct {
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
Market types.Market `json:"market"`
|
Market types.Market `json:"market"`
|
||||||
|
|
||||||
NumTrades int `json:"numTrades"`
|
NumTrades int `json:"numTrades"`
|
||||||
Profit fixedpoint.Value `json:"profit"`
|
Profit fixedpoint.Value `json:"profit"`
|
||||||
NetProfit fixedpoint.Value `json:"netProfit"`
|
UnrealizedProfit fixedpoint.Value `json:"unrealizedProfit"`
|
||||||
UnrealizedProfit fixedpoint.Value `json:"unrealizedProfit"`
|
|
||||||
|
NetProfit fixedpoint.Value `json:"netProfit"`
|
||||||
|
GrossProfit fixedpoint.Value `json:"grossProfit"`
|
||||||
|
GrossLoss fixedpoint.Value `json:"grossLoss"`
|
||||||
|
|
||||||
AverageCost fixedpoint.Value `json:"averageCost"`
|
AverageCost fixedpoint.Value `json:"averageCost"`
|
||||||
BuyVolume fixedpoint.Value `json:"buyVolume,omitempty"`
|
BuyVolume fixedpoint.Value `json:"buyVolume,omitempty"`
|
||||||
SellVolume fixedpoint.Value `json:"sellVolume,omitempty"`
|
SellVolume fixedpoint.Value `json:"sellVolume,omitempty"`
|
||||||
|
|
|
@ -27,6 +27,7 @@ type StateRecorder struct {
|
||||||
outputDirectory string
|
outputDirectory string
|
||||||
strategies []Instance
|
strategies []Instance
|
||||||
writers map[types.CsvFormatter]*tsv.Writer
|
writers map[types.CsvFormatter]*tsv.Writer
|
||||||
|
lastLines map[types.CsvFormatter][]string
|
||||||
manifests Manifests
|
manifests Manifests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ func NewStateRecorder(outputDir string) *StateRecorder {
|
||||||
return &StateRecorder{
|
return &StateRecorder{
|
||||||
outputDirectory: outputDir,
|
outputDirectory: outputDir,
|
||||||
writers: make(map[types.CsvFormatter]*tsv.Writer),
|
writers: make(map[types.CsvFormatter]*tsv.Writer),
|
||||||
|
lastLines: make(map[types.CsvFormatter][]string),
|
||||||
manifests: make(Manifests),
|
manifests: make(Manifests),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,11 +44,18 @@ func (r *StateRecorder) Snapshot() (int, error) {
|
||||||
var c int
|
var c int
|
||||||
for obj, writer := range r.writers {
|
for obj, writer := range r.writers {
|
||||||
records := obj.CsvRecords()
|
records := obj.CsvRecords()
|
||||||
|
lastLine, hasLastLine := r.lastLines[obj]
|
||||||
|
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
|
if hasLastLine && equalStringSlice(lastLine, record) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := writer.Write(record); err != nil {
|
if err := writer.Write(record); err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
c++
|
c++
|
||||||
|
r.lastLines[obj] = record
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Flush()
|
writer.Flush()
|
||||||
|
@ -129,3 +138,19 @@ func (r *StateRecorder) Close() error {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func equalStringSlice(a, b []string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(a); i++ {
|
||||||
|
ad := a[i]
|
||||||
|
bd := b[i]
|
||||||
|
if ad != bd {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -39,10 +39,16 @@ type SummaryReport struct {
|
||||||
InitialTotalBalances types.BalanceMap `json:"initialTotalBalances"`
|
InitialTotalBalances types.BalanceMap `json:"initialTotalBalances"`
|
||||||
FinalTotalBalances types.BalanceMap `json:"finalTotalBalances"`
|
FinalTotalBalances types.BalanceMap `json:"finalTotalBalances"`
|
||||||
|
|
||||||
|
InitialEquityValue fixedpoint.Value `json:"initialEquityValue"`
|
||||||
|
FinalEquityValue fixedpoint.Value `json:"finalEquityValue"`
|
||||||
|
|
||||||
// TotalProfit is the profit aggregated from the symbol reports
|
// TotalProfit is the profit aggregated from the symbol reports
|
||||||
TotalProfit fixedpoint.Value `json:"totalProfit,omitempty"`
|
TotalProfit fixedpoint.Value `json:"totalProfit,omitempty"`
|
||||||
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit,omitempty"`
|
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit,omitempty"`
|
||||||
|
|
||||||
|
TotalGrossProfit fixedpoint.Value `json:"totalGrossProfit,omitempty"`
|
||||||
|
TotalGrossLoss fixedpoint.Value `json:"totalGrossLoss,omitempty"`
|
||||||
|
|
||||||
SymbolReports []SessionSymbolReport `json:"symbolReports,omitempty"`
|
SymbolReports []SessionSymbolReport `json:"symbolReports,omitempty"`
|
||||||
|
|
||||||
Manifests Manifests `json:"manifests,omitempty"`
|
Manifests Manifests `json:"manifests,omitempty"`
|
||||||
|
@ -75,13 +81,21 @@ type SessionSymbolReport struct {
|
||||||
Manifests Manifests `json:"manifests,omitempty"`
|
Manifests Manifests `json:"manifests,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SessionSymbolReport) InitialEquityValue() fixedpoint.Value {
|
||||||
|
return InQuoteAsset(r.InitialBalances, r.Market, r.StartPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionSymbolReport) FinalEquityValue() fixedpoint.Value {
|
||||||
|
return InQuoteAsset(r.FinalBalances, r.Market, r.StartPrice)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *SessionSymbolReport) Print(wantBaseAssetBaseline bool) {
|
func (r *SessionSymbolReport) Print(wantBaseAssetBaseline bool) {
|
||||||
color.Green("%s %s PROFIT AND LOSS REPORT", r.Exchange, r.Symbol)
|
color.Green("%s %s PROFIT AND LOSS REPORT", r.Exchange, r.Symbol)
|
||||||
color.Green("===============================================")
|
color.Green("===============================================")
|
||||||
r.PnL.Print()
|
r.PnL.Print()
|
||||||
|
|
||||||
initQuoteAsset := inQuoteAsset(r.InitialBalances, r.Market, r.StartPrice)
|
initQuoteAsset := r.InitialEquityValue()
|
||||||
finalQuoteAsset := inQuoteAsset(r.FinalBalances, r.Market, r.LastPrice)
|
finalQuoteAsset := r.FinalEquityValue()
|
||||||
color.Green("INITIAL ASSET IN %s ~= %s %s (1 %s = %v)", r.Market.QuoteCurrency, r.Market.FormatQuantity(initQuoteAsset), r.Market.QuoteCurrency, r.Market.BaseCurrency, r.StartPrice)
|
color.Green("INITIAL ASSET IN %s ~= %s %s (1 %s = %v)", r.Market.QuoteCurrency, r.Market.FormatQuantity(initQuoteAsset), r.Market.QuoteCurrency, r.Market.BaseCurrency, r.StartPrice)
|
||||||
color.Green("FINAL ASSET IN %s ~= %s %s (1 %s = %v)", r.Market.QuoteCurrency, r.Market.FormatQuantity(finalQuoteAsset), r.Market.QuoteCurrency, r.Market.BaseCurrency, r.LastPrice)
|
color.Green("FINAL ASSET IN %s ~= %s %s (1 %s = %v)", r.Market.QuoteCurrency, r.Market.FormatQuantity(finalQuoteAsset), r.Market.QuoteCurrency, r.Market.BaseCurrency, r.LastPrice)
|
||||||
|
|
||||||
|
@ -186,8 +200,8 @@ func AddReportIndexRun(outputDirectory string, run Run) error {
|
||||||
return WriteReportIndex(outputDirectory, reportIndex)
|
return WriteReportIndex(outputDirectory, reportIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// inQuoteAsset converts all balances in quote asset
|
// InQuoteAsset converts all balances in quote asset
|
||||||
func inQuoteAsset(balances types.BalanceMap, market types.Market, price fixedpoint.Value) fixedpoint.Value {
|
func InQuoteAsset(balances types.BalanceMap, market types.Market, price fixedpoint.Value) fixedpoint.Value {
|
||||||
quote := balances[market.QuoteCurrency]
|
quote := balances[market.QuoteCurrency]
|
||||||
base := balances[market.BaseCurrency]
|
base := balances[market.BaseCurrency]
|
||||||
return base.Total().Mul(price).Add(quote.Total())
|
return base.Total().Mul(price).Add(quote.Total())
|
||||||
|
|
|
@ -66,6 +66,8 @@ func (e *GeneralOrderExecutor) BindProfitStats(profitStats *types.ProfitStats) {
|
||||||
}
|
}
|
||||||
|
|
||||||
profitStats.AddProfit(*profit)
|
profitStats.AddProfit(*profit)
|
||||||
|
|
||||||
|
Notify(profit)
|
||||||
Notify(profitStats)
|
Notify(profitStats)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,9 +340,6 @@ var BacktestCmd = &cobra.Command{
|
||||||
})
|
})
|
||||||
|
|
||||||
dumper := backtest.NewKLineDumper(kLineDataDir)
|
dumper := backtest.NewKLineDumper(kLineDataDir)
|
||||||
defer func() {
|
|
||||||
_ = dumper.Close()
|
|
||||||
}()
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := dumper.Close(); err != nil {
|
if err := dumper.Close(); err != nil {
|
||||||
log.WithError(err).Errorf("kline dumper can not close files")
|
log.WithError(err).Errorf("kline dumper can not close files")
|
||||||
|
@ -496,7 +493,6 @@ var BacktestCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, session := range environ.Sessions() {
|
for _, session := range environ.Sessions() {
|
||||||
|
|
||||||
for symbol, trades := range session.Trades {
|
for symbol, trades := range session.Trades {
|
||||||
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades)
|
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -507,6 +503,10 @@ var BacktestCmd = &cobra.Command{
|
||||||
summaryReport.SymbolReports = append(summaryReport.SymbolReports, *symbolReport)
|
summaryReport.SymbolReports = append(summaryReport.SymbolReports, *symbolReport)
|
||||||
summaryReport.TotalProfit = symbolReport.PnL.Profit
|
summaryReport.TotalProfit = symbolReport.PnL.Profit
|
||||||
summaryReport.TotalUnrealizedProfit = symbolReport.PnL.UnrealizedProfit
|
summaryReport.TotalUnrealizedProfit = symbolReport.PnL.UnrealizedProfit
|
||||||
|
summaryReport.InitialEquityValue = summaryReport.InitialEquityValue.Add(symbolReport.InitialEquityValue())
|
||||||
|
summaryReport.FinalEquityValue = summaryReport.FinalEquityValue.Add(symbolReport.FinalEquityValue())
|
||||||
|
summaryReport.TotalGrossProfit.Add(symbolReport.PnL.GrossProfit)
|
||||||
|
summaryReport.TotalGrossLoss.Add(symbolReport.PnL.GrossLoss)
|
||||||
|
|
||||||
// write report to a file
|
// write report to a file
|
||||||
if generatingReport {
|
if generatingReport {
|
||||||
|
|
|
@ -4,12 +4,16 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/data/tsv"
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
"github.com/c9s/bbgo/pkg/optimizer"
|
"github.com/c9s/bbgo/pkg/optimizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +21,7 @@ func init() {
|
||||||
optimizeCmd.Flags().String("optimizer-config", "optimizer.yaml", "config file")
|
optimizeCmd.Flags().String("optimizer-config", "optimizer.yaml", "config file")
|
||||||
optimizeCmd.Flags().String("output", "output", "backtest report output directory")
|
optimizeCmd.Flags().String("output", "output", "backtest report output directory")
|
||||||
optimizeCmd.Flags().Bool("json", false, "print optimizer metrics in json format")
|
optimizeCmd.Flags().Bool("json", false, "print optimizer metrics in json format")
|
||||||
|
optimizeCmd.Flags().Bool("tsv", false, "print optimizer metrics in csv format")
|
||||||
RootCmd.AddCommand(optimizeCmd)
|
RootCmd.AddCommand(optimizeCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +48,11 @@ var optimizeCmd = &cobra.Command{
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printTsvFormat, err := cmd.Flags().GetBool("tsv")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
outputDirectory, err := cmd.Flags().GetString("output")
|
outputDirectory, err := cmd.Flags().GetString("output")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -104,6 +114,10 @@ var optimizeCmd = &cobra.Command{
|
||||||
|
|
||||||
// print metrics JSON to stdout
|
// print metrics JSON to stdout
|
||||||
fmt.Println(string(out))
|
fmt.Println(string(out))
|
||||||
|
} else if printTsvFormat {
|
||||||
|
if err := formatMetricsTsv(metrics, os.Stdout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for n, values := range metrics {
|
for n, values := range metrics {
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
|
@ -120,3 +134,95 @@ var optimizeCmd = &cobra.Command{
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transformMetricsToRows(metrics map[string][]optimizer.Metric) (headers []string, rows [][]interface{}) {
|
||||||
|
var metricsKeys []string
|
||||||
|
for k := range metrics {
|
||||||
|
metricsKeys = append(metricsKeys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numEntries int
|
||||||
|
var paramLabels []string
|
||||||
|
for _, ms := range metrics {
|
||||||
|
for _, m := range ms {
|
||||||
|
paramLabels = m.Labels
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
numEntries = len(ms)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = append(paramLabels, metricsKeys...)
|
||||||
|
rows = make([][]interface{}, numEntries)
|
||||||
|
|
||||||
|
var metricsRows = make([][]interface{}, numEntries)
|
||||||
|
|
||||||
|
// build params into the rows
|
||||||
|
for i, m := range metrics[metricsKeys[0]] {
|
||||||
|
rows[i] = m.Params
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, metricKey := range metricsKeys {
|
||||||
|
for i, ms := range metrics[metricKey] {
|
||||||
|
if len(metricsRows[i]) == 0 {
|
||||||
|
metricsRows[i] = make([]interface{}, 0, len(metricsKeys))
|
||||||
|
}
|
||||||
|
metricsRows[i] = append(metricsRows[i], ms.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge rows
|
||||||
|
for i := range rows {
|
||||||
|
rows[i] = append(rows[i], metricsRows[i]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers, rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMetricsTsv(metrics map[string][]optimizer.Metric, writer io.WriteCloser) error {
|
||||||
|
headers, rows := transformMetricsToRows(metrics)
|
||||||
|
w := tsv.NewWriter(writer)
|
||||||
|
if err := w.Write(headers); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
var cells []string
|
||||||
|
for _, o := range row {
|
||||||
|
cell, err := castCellValue(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cells = append(cells, cell)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.Write(cells); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func castCellValue(a interface{}) (string, error) {
|
||||||
|
switch tv := a.(type) {
|
||||||
|
case fixedpoint.Value:
|
||||||
|
return tv.String(), nil
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatFloat(tv, 'f', -1, 64), nil
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(tv, 10), nil
|
||||||
|
case int32:
|
||||||
|
return strconv.FormatInt(int64(tv), 10), nil
|
||||||
|
case int:
|
||||||
|
return strconv.Itoa(tv), nil
|
||||||
|
case bool:
|
||||||
|
return strconv.FormatBool(tv), nil
|
||||||
|
case string:
|
||||||
|
return tv, nil
|
||||||
|
case []byte:
|
||||||
|
return string(tv), nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported object type: %T value: %v", tv, tv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -608,7 +608,7 @@ func (e *Exchange) QueryAccountBalances(ctx context.Context) (types.BalanceMap,
|
||||||
Available: b.Balance,
|
Available: b.Balance,
|
||||||
Locked: b.Locked,
|
Locked: b.Locked,
|
||||||
NetAsset: b.Balance.Add(b.Locked).Sub(b.Debt),
|
NetAsset: b.Balance.Add(b.Locked).Sub(b.Debt),
|
||||||
Borrowed: b.Debt, // TODO: Replace this with borrow in the newer version
|
Borrowed: b.Borrowed,
|
||||||
Interest: b.Interest,
|
Interest: b.Interest,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,10 @@ type Account struct {
|
||||||
Locked fixedpoint.Value `json:"locked"`
|
Locked fixedpoint.Value `json:"locked"`
|
||||||
|
|
||||||
// v3 fields for M wallet
|
// v3 fields for M wallet
|
||||||
Debt fixedpoint.Value `json:"debt"`
|
Debt fixedpoint.Value `json:"debt"`
|
||||||
Interest fixedpoint.Value `json:"interest"`
|
Principal fixedpoint.Value `json:"principal"`
|
||||||
|
Borrowed fixedpoint.Value `json:"borrowed"`
|
||||||
|
Interest fixedpoint.Value `json:"interest"`
|
||||||
|
|
||||||
// v2 fields
|
// v2 fields
|
||||||
FiatCurrency string `json:"fiat_currency"`
|
FiatCurrency string `json:"fiat_currency"`
|
||||||
|
|
|
@ -6,7 +6,7 @@ import "github.com/c9s/requestgen"
|
||||||
//go:generate -command PostRequest requestgen -method POST
|
//go:generate -command PostRequest requestgen -method POST
|
||||||
//go:generate -command DeleteRequest requestgen -method DELETE
|
//go:generate -command DeleteRequest requestgen -method DELETE
|
||||||
|
|
||||||
//go:generate PostRequest -url "/api/v3/wallet/:walletType/orders" -type CreateWalletOrderRequest -responseType .Order
|
//go:generate PostRequest -url "/api/v3/wallet/:walletType/order" -type CreateWalletOrderRequest -responseType .Order
|
||||||
type CreateWalletOrderRequest struct {
|
type CreateWalletOrderRequest struct {
|
||||||
client requestgen.AuthenticatedAPIClient
|
client requestgen.AuthenticatedAPIClient
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Code generated by "requestgen -method POST -url /api/v3/wallet/:walletType/orders -type CreateWalletOrderRequest -responseType .Order"; DO NOT EDIT.
|
// Code generated by "requestgen -method POST -url /api/v3/wallet/:walletType/order -type CreateWalletOrderRequest -responseType .Order"; DO NOT EDIT.
|
||||||
|
|
||||||
package v3
|
package v3
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ func (c *CreateWalletOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
||||||
}
|
}
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
|
|
||||||
apiURL := "/api/v3/wallet/:walletType/orders"
|
apiURL := "/api/v3/wallet/:walletType/order"
|
||||||
slugs, err := c.GetSlugsMap()
|
slugs, err := c.GetSlugsMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -4,12 +4,12 @@ package indicator
|
||||||
|
|
||||||
import ()
|
import ()
|
||||||
|
|
||||||
func (A *ATR) OnUpdate(cb func(value float64)) {
|
func (inc *ATR) OnUpdate(cb func(value float64)) {
|
||||||
A.UpdateCallbacks = append(A.UpdateCallbacks, cb)
|
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (A *ATR) EmitUpdate(value float64) {
|
func (inc *ATR) EmitUpdate(value float64) {
|
||||||
for _, cb := range A.UpdateCallbacks {
|
for _, cb := range inc.UpdateCallbacks {
|
||||||
cb(value)
|
cb(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
pkg/indicator/atrp.go
Normal file
112
pkg/indicator/atrp.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package indicator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ATRP is the average true range percentage
|
||||||
|
// See also https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/atrp
|
||||||
|
//
|
||||||
|
// Calculation:
|
||||||
|
//
|
||||||
|
// ATRP = (Average True Range / Close) * 100
|
||||||
|
//
|
||||||
|
//go:generate callbackgen -type ATRP
|
||||||
|
type ATRP struct {
|
||||||
|
types.SeriesBase
|
||||||
|
types.IntervalWindow
|
||||||
|
PercentageVolatility types.Float64Slice
|
||||||
|
|
||||||
|
PreviousClose float64
|
||||||
|
RMA *RMA
|
||||||
|
|
||||||
|
EndTime time.Time
|
||||||
|
UpdateCallbacks []func(value float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) Update(high, low, cloze float64) {
|
||||||
|
if inc.Window <= 0 {
|
||||||
|
panic("window must be greater than 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
if inc.RMA == nil {
|
||||||
|
inc.SeriesBase.Series = inc
|
||||||
|
inc.RMA = &RMA{
|
||||||
|
IntervalWindow: types.IntervalWindow{Window: inc.Window},
|
||||||
|
Adjust: true,
|
||||||
|
}
|
||||||
|
inc.PreviousClose = cloze
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate true range
|
||||||
|
trueRange := high - low
|
||||||
|
hc := math.Abs(high - inc.PreviousClose)
|
||||||
|
lc := math.Abs(low - inc.PreviousClose)
|
||||||
|
if trueRange < hc {
|
||||||
|
trueRange = hc
|
||||||
|
}
|
||||||
|
if trueRange < lc {
|
||||||
|
trueRange = lc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this is the difference from ATR
|
||||||
|
trueRange = trueRange / inc.PreviousClose * 100.0
|
||||||
|
|
||||||
|
inc.PreviousClose = cloze
|
||||||
|
|
||||||
|
// apply rolling moving average
|
||||||
|
inc.RMA.Update(trueRange)
|
||||||
|
atr := inc.RMA.Last()
|
||||||
|
inc.PercentageVolatility.Push(atr / cloze)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) Last() float64 {
|
||||||
|
if inc.RMA == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return inc.RMA.Last()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) Index(i int) float64 {
|
||||||
|
if inc.RMA == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return inc.RMA.Index(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) Length() int {
|
||||||
|
if inc.RMA == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return inc.RMA.Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.SeriesExtend = &ATRP{}
|
||||||
|
|
||||||
|
func (inc *ATRP) CalculateAndUpdate(kLines []types.KLine) {
|
||||||
|
for _, k := range kLines {
|
||||||
|
if inc.EndTime != zeroTime && !k.EndTime.After(inc.EndTime) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inc.Update(k.High.Float64(), k.Low.Float64(), k.Close.Float64())
|
||||||
|
}
|
||||||
|
|
||||||
|
inc.EmitUpdate(inc.Last())
|
||||||
|
inc.EndTime = kLines[len(kLines)-1].EndTime.Time()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
||||||
|
if inc.Interval != interval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inc.CalculateAndUpdate(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) Bind(updater KLineWindowUpdater) {
|
||||||
|
updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate)
|
||||||
|
}
|
15
pkg/indicator/atrp_callbacks.go
Normal file
15
pkg/indicator/atrp_callbacks.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Code generated by "callbackgen -type ATRP"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package indicator
|
||||||
|
|
||||||
|
import ()
|
||||||
|
|
||||||
|
func (inc *ATRP) OnUpdate(cb func(value float64)) {
|
||||||
|
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inc *ATRP) EmitUpdate(value float64) {
|
||||||
|
for _, cb := range inc.UpdateCallbacks {
|
||||||
|
cb(value)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ func TestGetMigrationsMap(t *testing.T) {
|
||||||
|
|
||||||
func TestMergeMigrationsMap(t *testing.T) {
|
func TestMergeMigrationsMap(t *testing.T) {
|
||||||
MergeMigrationsMap(map[int64]*rockhopper.Migration{
|
MergeMigrationsMap(map[int64]*rockhopper.Migration{
|
||||||
2: {},
|
2: &rockhopper.Migration{},
|
||||||
3: {},
|
3: &rockhopper.Migration{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ func TestGetMigrationsMap(t *testing.T) {
|
||||||
|
|
||||||
func TestMergeMigrationsMap(t *testing.T) {
|
func TestMergeMigrationsMap(t *testing.T) {
|
||||||
MergeMigrationsMap(map[int64]*rockhopper.Migration{
|
MergeMigrationsMap(map[int64]*rockhopper.Migration{
|
||||||
2: {},
|
2: &rockhopper.Migration{},
|
||||||
3: {},
|
3: &rockhopper.Migration{},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,31 @@ import (
|
||||||
type MetricValueFunc func(summaryReport *backtest.SummaryReport) fixedpoint.Value
|
type MetricValueFunc func(summaryReport *backtest.SummaryReport) fixedpoint.Value
|
||||||
|
|
||||||
var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
|
var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
|
||||||
if summaryReport == nil {
|
|
||||||
return fixedpoint.Zero
|
|
||||||
}
|
|
||||||
return summaryReport.TotalProfit
|
return summaryReport.TotalProfit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var TotalVolume = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
|
||||||
|
if len(summaryReport.SymbolReports) == 0 {
|
||||||
|
return fixedpoint.Zero
|
||||||
|
}
|
||||||
|
|
||||||
|
buyVolume := summaryReport.SymbolReports[0].PnL.BuyVolume
|
||||||
|
sellVolume := summaryReport.SymbolReports[0].PnL.SellVolume
|
||||||
|
return buyVolume.Add(sellVolume)
|
||||||
|
}
|
||||||
|
|
||||||
type Metric struct {
|
type Metric struct {
|
||||||
Labels []string `json:"labels,omitempty"`
|
// Labels is the labels of the given parameters
|
||||||
Params []interface{} `json:"params,omitempty"`
|
Labels []string `json:"labels,omitempty"`
|
||||||
Value fixedpoint.Value `json:"value,omitempty"`
|
|
||||||
|
// Params is the parameters used to output the metrics result
|
||||||
|
Params []interface{} `json:"params,omitempty"`
|
||||||
|
|
||||||
|
// Key is the metric name
|
||||||
|
Key string `json:"key"`
|
||||||
|
|
||||||
|
// Value is the metric value of the metric
|
||||||
|
Value fixedpoint.Value `json:"value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyParams(params []interface{}) []interface{} {
|
func copyParams(params []interface{}) []interface{} {
|
||||||
|
@ -172,6 +187,7 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][]
|
||||||
|
|
||||||
var valueFunctions = map[string]MetricValueFunc{
|
var valueFunctions = map[string]MetricValueFunc{
|
||||||
"totalProfit": TotalProfitMetricValueFunc,
|
"totalProfit": TotalProfitMetricValueFunc,
|
||||||
|
"totalVolume": TotalVolume,
|
||||||
}
|
}
|
||||||
var metrics = map[string][]Metric{}
|
var metrics = map[string][]Metric{}
|
||||||
|
|
||||||
|
@ -220,16 +236,20 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][]
|
||||||
close(taskC) // this will shut down the executor
|
close(taskC) // this will shut down the executor
|
||||||
|
|
||||||
for result := range resultsC {
|
for result := range resultsC {
|
||||||
for metricName, metricFunc := range valueFunctions {
|
if result.Report == nil {
|
||||||
if result.Report == nil {
|
log.Errorf("no summaryReport found for params: %+v", result.Params)
|
||||||
log.Errorf("no summaryReport found for params: %+v", result.Params)
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for metricKey, metricFunc := range valueFunctions {
|
||||||
var metricValue = metricFunc(result.Report)
|
var metricValue = metricFunc(result.Report)
|
||||||
bar.Set("log", fmt.Sprintf("params: %+v => %s %+v", result.Params, metricName, metricValue))
|
bar.Set("log", fmt.Sprintf("params: %+v => %s %+v", result.Params, metricKey, metricValue))
|
||||||
bar.Increment()
|
bar.Increment()
|
||||||
metrics[metricName] = append(metrics[metricName], Metric{
|
|
||||||
|
metrics[metricKey] = append(metrics[metricKey], Metric{
|
||||||
Params: result.Params,
|
Params: result.Params,
|
||||||
Labels: result.Labels,
|
Labels: result.Labels,
|
||||||
|
Key: metricKey,
|
||||||
Value: metricValue,
|
Value: metricValue,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,8 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) {
|
||||||
minMarginLevel := s.MinMarginLevel
|
minMarginLevel := s.MinMarginLevel
|
||||||
curMarginLevel := account.MarginLevel
|
curMarginLevel := account.MarginLevel
|
||||||
|
|
||||||
log.Infof("current account margin level: %s margin ratio: %s, margin tolerance: %s",
|
bbgo.Notify("%s: current margin level: %s, margin ratio: %s, margin tolerance: %s",
|
||||||
|
s.ExchangeSession.Name,
|
||||||
account.MarginLevel.String(),
|
account.MarginLevel.String(),
|
||||||
account.MarginRatio.String(),
|
account.MarginRatio.String(),
|
||||||
account.MarginTolerance.String(),
|
account.MarginTolerance.String(),
|
||||||
|
@ -280,7 +281,8 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.ExchangeSession.GetAccount().MarginLevel.Compare(s.MinMarginLevel) > 0 {
|
account := s.ExchangeSession.GetAccount()
|
||||||
|
if account.MarginLevel.Compare(s.MinMarginLevel) > 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +293,6 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account := s.ExchangeSession.GetAccount()
|
|
||||||
minMarginLevel := s.MinMarginLevel
|
minMarginLevel := s.MinMarginLevel
|
||||||
curMarginLevel := account.MarginLevel
|
curMarginLevel := account.MarginLevel
|
||||||
|
|
||||||
|
@ -300,7 +301,11 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
toRepay := b.Available
|
toRepay := fixedpoint.Min(b.Borrowed, b.Available)
|
||||||
|
if toRepay.IsZero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
bbgo.Notify(&MarginAction{
|
bbgo.Notify(&MarginAction{
|
||||||
Exchange: s.ExchangeSession.ExchangeName,
|
Exchange: s.ExchangeSession.ExchangeName,
|
||||||
Action: "Repay",
|
Action: "Repay",
|
||||||
|
@ -309,6 +314,7 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
||||||
MarginLevel: curMarginLevel,
|
MarginLevel: curMarginLevel,
|
||||||
MinMarginLevel: minMarginLevel,
|
MinMarginLevel: minMarginLevel,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := s.marginBorrowRepay.RepayMarginAsset(context.Background(), event.Asset, toRepay); err != nil {
|
if err := s.marginBorrowRepay.RepayMarginAsset(context.Background(), event.Asset, toRepay); err != nil {
|
||||||
log.WithError(err).Errorf("margin repay error")
|
log.WithError(err).Errorf("margin repay error")
|
||||||
}
|
}
|
||||||
|
@ -366,14 +372,14 @@ func (a *MarginAction) SlackAttachment() slack.Attachment {
|
||||||
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
|
// This strategy simply spent all available quote currency to buy the symbol whenever kline gets closed
|
||||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||||
if s.MinMarginLevel.IsZero() {
|
if s.MinMarginLevel.IsZero() {
|
||||||
log.Warnf("minMarginLevel is 0, you should configure this minimal margin ratio for controlling the liquidation risk")
|
log.Warnf("%s: minMarginLevel is 0, you should configure this minimal margin ratio for controlling the liquidation risk", session.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ExchangeSession = session
|
s.ExchangeSession = session
|
||||||
|
|
||||||
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepayService)
|
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepayService)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("exchange %s does not implement types.MarginBorrowRepayService", session.ExchangeName)
|
return fmt.Errorf("exchange %s does not implement types.MarginBorrowRepayService", session.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.marginBorrowRepay = marginBorrowRepay
|
s.marginBorrowRepay = marginBorrowRepay
|
||||||
|
|
|
@ -40,6 +40,7 @@ type BreakLow struct {
|
||||||
|
|
||||||
func (s *BreakLow) Subscribe(session *bbgo.ExchangeSession) {
|
func (s *BreakLow) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) {
|
||||||
|
@ -69,7 +70,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastLow.Compare(s.lastLow) != 0 {
|
if lastLow.Compare(s.lastLow) != 0 {
|
||||||
log.Infof("new pivot low detected: %f %s", s.pivot.LastLow(), kline.EndTime.Time())
|
bbgo.Notify("%s new pivot low detected: %f %s", s.Symbol, s.pivot.LastLow(), kline.EndTime.Time().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
s.lastLow = lastLow
|
s.lastLow = lastLow
|
||||||
|
|
|
@ -54,7 +54,7 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg
|
||||||
|
|
||||||
// use the last kline from the history before we get the next closed kline
|
// use the last kline from the history before we get the next closed kline
|
||||||
if lastKLine != nil {
|
if lastKLine != nil {
|
||||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
s.updateResistanceOrders(lastKLine.Close)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||||
|
@ -63,7 +63,7 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
s.findNextResistancePriceAndPlaceOrders(kline.Close)
|
s.updateResistanceOrders(kline.Close)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +75,14 @@ func tail(arr []float64, length int) []float64 {
|
||||||
return arr[len(arr)-1-length:]
|
return arr[len(arr)-1-length:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value) bool {
|
// updateCurrentResistancePrice update the current resistance price
|
||||||
|
// we should only update the resistance price when:
|
||||||
|
// 1) the close price is already above the current resistance price by (1 + minDistance)
|
||||||
|
// 2) the next resistance price is lower than the current resistance price.
|
||||||
|
func (s *ResistanceShort) updateCurrentResistancePrice(closePrice fixedpoint.Value) bool {
|
||||||
minDistance := s.MinDistance.Float64()
|
minDistance := s.MinDistance.Float64()
|
||||||
groupDistance := s.GroupDistance.Float64()
|
groupDistance := s.GroupDistance.Float64()
|
||||||
resistancePrices := findPossibleResistancePrices(closePrice.Float64()*(1.0+minDistance), groupDistance, tail(s.resistancePivot.Lows, 6))
|
resistancePrices := findPossibleResistancePrices(closePrice.Float64()*(1.0+minDistance), groupDistance, tail(s.resistancePivot.Lows, 6))
|
||||||
|
|
||||||
if len(resistancePrices) == 0 {
|
if len(resistancePrices) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -88,9 +91,6 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value)
|
||||||
|
|
||||||
nextResistancePrice := fixedpoint.NewFromFloat(resistancePrices[0])
|
nextResistancePrice := fixedpoint.NewFromFloat(resistancePrices[0])
|
||||||
|
|
||||||
// if currentResistancePrice is not set or the close price is already higher than the current resistance price,
|
|
||||||
// we should update the resistance price
|
|
||||||
// if the detected resistance price is lower than the current one, we should also update it too
|
|
||||||
if s.currentResistancePrice.IsZero() {
|
if s.currentResistancePrice.IsZero() {
|
||||||
s.currentResistancePrice = nextResistancePrice
|
s.currentResistancePrice = nextResistancePrice
|
||||||
return true
|
return true
|
||||||
|
@ -99,9 +99,8 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value)
|
||||||
// if the current sell price is out-dated
|
// if the current sell price is out-dated
|
||||||
// or
|
// or
|
||||||
// the next resistance is lower than the current one.
|
// the next resistance is lower than the current one.
|
||||||
currentSellPrice := s.currentResistancePrice.Mul(one.Add(s.Ratio))
|
minPriceToUpdate := s.currentResistancePrice.Mul(one.Add(s.MinDistance))
|
||||||
if closePrice.Compare(currentSellPrice) > 0 ||
|
if closePrice.Compare(minPriceToUpdate) > 0 || nextResistancePrice.Compare(s.currentResistancePrice) < 0 {
|
||||||
nextResistancePrice.Compare(currentSellPrice) < 0 {
|
|
||||||
s.currentResistancePrice = nextResistancePrice
|
s.currentResistancePrice = nextResistancePrice
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -109,11 +108,11 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
func (s *ResistanceShort) updateResistanceOrders(closePrice fixedpoint.Value) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
resistanceUpdated := s.updateNextResistancePrice(closePrice)
|
resistanceUpdated := s.updateCurrentResistancePrice(closePrice)
|
||||||
if resistanceUpdated {
|
if resistanceUpdated {
|
||||||
bbgo.Notify("Found next resistance price: %f, updating resistance order...", s.currentResistancePrice.Float64())
|
bbgo.Notify("%s Found next resistance price at %f, updating resistance order...", s.Symbol, s.currentResistancePrice.Float64())
|
||||||
s.placeResistanceOrders(ctx, s.currentResistancePrice)
|
s.placeResistanceOrders(ctx, s.currentResistancePrice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,8 +150,7 @@ func (s *ResistanceShort) placeResistanceOrders(ctx context.Context, resistanceP
|
||||||
|
|
||||||
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
||||||
price := sellPriceStart.Mul(one.Add(spread))
|
price := sellPriceStart.Mul(one.Add(spread))
|
||||||
log.Infof("price = %f", price.Float64())
|
log.Infof("resistance sell price = %f", price.Float64())
|
||||||
|
|
||||||
log.Infof("placing resistance short order #%d: price = %f, quantity = %f", i, price.Float64(), quantity.Float64())
|
log.Infof("placing resistance short order #%d: price = %f, quantity = %f", i, price.Float64(), quantity.Float64())
|
||||||
|
|
||||||
orderForms = append(orderForms, types.SubmitOrder{
|
orderForms = append(orderForms, types.SubmitOrder{
|
||||||
|
|
67
pkg/strategy/supertrend/double_dema.go
Normal file
67
pkg/strategy/supertrend/double_dema.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package supertrend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/indicator"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DoubleDema struct {
|
||||||
|
Interval types.Interval `json:"interval"`
|
||||||
|
|
||||||
|
// FastDEMAWindow DEMA window for checking breakout
|
||||||
|
FastDEMAWindow int `json:"fastDEMAWindow"`
|
||||||
|
// SlowDEMAWindow DEMA window for checking breakout
|
||||||
|
SlowDEMAWindow int `json:"slowDEMAWindow"`
|
||||||
|
fastDEMA *indicator.DEMA
|
||||||
|
slowDEMA *indicator.DEMA
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDemaSignal get current DEMA signal
|
||||||
|
func (dd *DoubleDema) getDemaSignal(openPrice float64, closePrice float64) types.Direction {
|
||||||
|
var demaSignal types.Direction = types.DirectionNone
|
||||||
|
|
||||||
|
if closePrice > dd.fastDEMA.Last() && closePrice > dd.slowDEMA.Last() && !(openPrice > dd.fastDEMA.Last() && openPrice > dd.slowDEMA.Last()) {
|
||||||
|
demaSignal = types.DirectionUp
|
||||||
|
} else if closePrice < dd.fastDEMA.Last() && closePrice < dd.slowDEMA.Last() && !(openPrice < dd.fastDEMA.Last() && openPrice < dd.slowDEMA.Last()) {
|
||||||
|
demaSignal = types.DirectionDown
|
||||||
|
}
|
||||||
|
|
||||||
|
return demaSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
// preloadDema preloads DEMA indicators
|
||||||
|
func (dd *DoubleDema) preloadDema(kLineStore *bbgo.MarketDataStore) {
|
||||||
|
if klines, ok := kLineStore.KLinesOfInterval(dd.fastDEMA.Interval); ok {
|
||||||
|
for i := 0; i < len(*klines); i++ {
|
||||||
|
dd.fastDEMA.Update((*klines)[i].GetClose().Float64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if klines, ok := kLineStore.KLinesOfInterval(dd.slowDEMA.Interval); ok {
|
||||||
|
for i := 0; i < len(*klines); i++ {
|
||||||
|
dd.slowDEMA.Update((*klines)[i].GetClose().Float64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDoubleDema initializes double DEMA indicators
|
||||||
|
func newDoubleDema(kLineStore *bbgo.MarketDataStore, interval types.Interval, fastDEMAWindow int, slowDEMAWindow int) *DoubleDema {
|
||||||
|
dd := DoubleDema{Interval: interval, FastDEMAWindow: fastDEMAWindow, SlowDEMAWindow: slowDEMAWindow}
|
||||||
|
|
||||||
|
// DEMA
|
||||||
|
if dd.FastDEMAWindow == 0 {
|
||||||
|
dd.FastDEMAWindow = 144
|
||||||
|
}
|
||||||
|
dd.fastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: dd.Interval, Window: dd.FastDEMAWindow}}
|
||||||
|
dd.fastDEMA.Bind(kLineStore)
|
||||||
|
|
||||||
|
if dd.SlowDEMAWindow == 0 {
|
||||||
|
dd.SlowDEMAWindow = 169
|
||||||
|
}
|
||||||
|
dd.slowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: dd.Interval, Window: dd.SlowDEMAWindow}}
|
||||||
|
dd.slowDEMA.Bind(kLineStore)
|
||||||
|
|
||||||
|
dd.preloadDema(kLineStore)
|
||||||
|
|
||||||
|
return &dd
|
||||||
|
}
|
73
pkg/strategy/supertrend/lingre.go
Normal file
73
pkg/strategy/supertrend/lingre.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package supertrend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
|
"github.com/c9s/bbgo/pkg/indicator"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinGre is Linear Regression baseline
|
||||||
|
type LinGre struct {
|
||||||
|
types.IntervalWindow
|
||||||
|
baseLineSlope float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Linear Regression baseline slope
|
||||||
|
func (lg *LinGre) Update(klines []types.KLine) {
|
||||||
|
if len(klines) < lg.Window {
|
||||||
|
lg.baseLineSlope = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var sumX, sumY, sumXSqr, sumXY float64 = 0, 0, 0, 0
|
||||||
|
end := len(klines) - 1 // The last kline
|
||||||
|
for i := end; i >= end-lg.Window+1; i-- {
|
||||||
|
val := klines[i].GetClose().Float64()
|
||||||
|
per := float64(end - i + 1)
|
||||||
|
sumX += per
|
||||||
|
sumY += val
|
||||||
|
sumXSqr += per * per
|
||||||
|
sumXY += val * per
|
||||||
|
}
|
||||||
|
length := float64(lg.Window)
|
||||||
|
slope := (length*sumXY - sumX*sumY) / (length*sumXSqr - sumX*sumX)
|
||||||
|
average := sumY / length
|
||||||
|
endPrice := average - slope*sumX/length + slope
|
||||||
|
startPrice := endPrice + slope*(length-1)
|
||||||
|
lg.baseLineSlope = (length - 1) / (endPrice - startPrice)
|
||||||
|
|
||||||
|
log.Debugf("linear regression baseline slope: %f", lg.baseLineSlope)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lg *LinGre) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) {
|
||||||
|
if lg.Interval != interval {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lg.Update(window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lg *LinGre) Bind(updater indicator.KLineWindowUpdater) {
|
||||||
|
updater.OnKLineWindowUpdate(lg.handleKLineWindowUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignal get linear regression signal
|
||||||
|
func (lg *LinGre) GetSignal() types.Direction {
|
||||||
|
var lgSignal types.Direction = types.DirectionNone
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case lg.baseLineSlope > 0:
|
||||||
|
lgSignal = types.DirectionUp
|
||||||
|
case lg.baseLineSlope < 0:
|
||||||
|
lgSignal = types.DirectionDown
|
||||||
|
}
|
||||||
|
|
||||||
|
return lgSignal
|
||||||
|
}
|
||||||
|
|
||||||
|
// preloadLinGre preloads linear regression indicator
|
||||||
|
func (lg *LinGre) preload(kLineStore *bbgo.MarketDataStore) {
|
||||||
|
if klines, ok := kLineStore.KLinesOfInterval(lg.Interval); ok {
|
||||||
|
lg.Update((*klines)[0:])
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,9 @@ package supertrend
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/util"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
@ -18,10 +17,9 @@ import (
|
||||||
|
|
||||||
const ID = "supertrend"
|
const ID = "supertrend"
|
||||||
|
|
||||||
const stateKey = "state-v1"
|
|
||||||
|
|
||||||
var log = logrus.WithField("strategy", ID)
|
var log = logrus.WithField("strategy", ID)
|
||||||
|
|
||||||
|
// TODO: limit order for ATR TP
|
||||||
func init() {
|
func init() {
|
||||||
// Register the pointer of the strategy struct,
|
// Register the pointer of the strategy struct,
|
||||||
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
|
// so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON)
|
||||||
|
@ -30,57 +28,57 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
*bbgo.Persistence
|
|
||||||
|
|
||||||
Environment *bbgo.Environment
|
Environment *bbgo.Environment
|
||||||
session *bbgo.ExchangeSession
|
|
||||||
Market types.Market
|
Market types.Market
|
||||||
|
|
||||||
// persistence fields
|
// persistence fields
|
||||||
Position *types.Position `json:"position,omitempty" persistence:"position"`
|
Position *types.Position `persistence:"position"`
|
||||||
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"`
|
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||||
|
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||||
// Order and trade
|
|
||||||
orderExecutor *bbgo.GeneralOrderExecutor
|
|
||||||
|
|
||||||
// groupID is the group ID used for the strategy instance for canceling orders
|
|
||||||
groupID uint32
|
|
||||||
|
|
||||||
stopC chan struct{}
|
|
||||||
|
|
||||||
// Symbol is the market symbol you want to trade
|
// Symbol is the market symbol you want to trade
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
|
|
||||||
// Interval is how long do you want to update your order price and quantity
|
types.IntervalWindow
|
||||||
Interval types.Interval `json:"interval"`
|
|
||||||
|
|
||||||
|
// Double DEMA
|
||||||
|
doubleDema *DoubleDema
|
||||||
// FastDEMAWindow DEMA window for checking breakout
|
// FastDEMAWindow DEMA window for checking breakout
|
||||||
FastDEMAWindow int `json:"fastDEMAWindow"`
|
FastDEMAWindow int `json:"fastDEMAWindow"`
|
||||||
// SlowDEMAWindow DEMA window for checking breakout
|
// SlowDEMAWindow DEMA window for checking breakout
|
||||||
SlowDEMAWindow int `json:"slowDEMAWindow"`
|
SlowDEMAWindow int `json:"slowDEMAWindow"`
|
||||||
fastDEMA *indicator.DEMA
|
|
||||||
slowDEMA *indicator.DEMA
|
|
||||||
|
|
||||||
// SuperTrend indicator
|
// SuperTrend indicator
|
||||||
// SuperTrend SuperTrend `json:"superTrend"`
|
|
||||||
Supertrend *indicator.Supertrend
|
Supertrend *indicator.Supertrend
|
||||||
// SupertrendWindow ATR window for calculation of supertrend
|
|
||||||
SupertrendWindow int `json:"supertrendWindow"`
|
|
||||||
// SupertrendMultiplier ATR multiplier for calculation of supertrend
|
// SupertrendMultiplier ATR multiplier for calculation of supertrend
|
||||||
SupertrendMultiplier float64 `json:"supertrendMultiplier"`
|
SupertrendMultiplier float64 `json:"supertrendMultiplier"`
|
||||||
|
|
||||||
|
// LinearRegression Use linear regression as trend confirmation
|
||||||
|
LinearRegression *LinGre `json:"linearRegression,omitempty"`
|
||||||
|
|
||||||
// Leverage
|
// Leverage
|
||||||
Leverage float64 `json:"leverage"`
|
Leverage float64 `json:"leverage"`
|
||||||
|
|
||||||
// TakeProfitMultiplier TP according to ATR multiple, 0 to disable this
|
// TakeProfitAtrMultiplier TP according to ATR multiple, 0 to disable this
|
||||||
TakeProfitMultiplier float64 `json:"takeProfitMultiplier"`
|
TakeProfitAtrMultiplier float64 `json:"takeProfitAtrMultiplier"`
|
||||||
|
|
||||||
// StopLossByTriggeringK Set SL price to the low of the triggering Kline
|
// StopLossByTriggeringK Set SL price to the low/high of the triggering Kline
|
||||||
StopLossByTriggeringK bool `json:"stopLossByTriggeringK"`
|
StopLossByTriggeringK bool `json:"stopLossByTriggeringK"`
|
||||||
|
|
||||||
// TPSLBySignal TP/SL by reversed signals
|
// StopByReversedSupertrend TP/SL by reversed supertrend signal
|
||||||
TPSLBySignal bool `json:"tpslBySignal"`
|
StopByReversedSupertrend bool `json:"stopByReversedSupertrend"`
|
||||||
|
|
||||||
|
// StopByReversedDema TP/SL by reversed DEMA signal
|
||||||
|
StopByReversedDema bool `json:"stopByReversedDema"`
|
||||||
|
|
||||||
|
// StopByReversedLinGre TP/SL by reversed linear regression signal
|
||||||
|
StopByReversedLinGre bool `json:"stopByReversedLinGre"`
|
||||||
|
|
||||||
|
// ExitMethods Exit methods
|
||||||
|
ExitMethods bbgo.ExitMethodSet `json:"exits"`
|
||||||
|
|
||||||
|
session *bbgo.ExchangeSession
|
||||||
|
orderExecutor *bbgo.GeneralOrderExecutor
|
||||||
currentTakeProfitPrice fixedpoint.Value
|
currentTakeProfitPrice fixedpoint.Value
|
||||||
currentStopLossPrice fixedpoint.Value
|
currentStopLossPrice fixedpoint.Value
|
||||||
|
|
||||||
|
@ -105,7 +103,7 @@ func (s *Strategy) Validate() error {
|
||||||
return errors.New("interval is required")
|
return errors.New("interval is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Leverage == 0.0 {
|
if s.Leverage <= 0.0 {
|
||||||
return errors.New("leverage is required")
|
return errors.New("leverage is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +112,7 @@ func (s *Strategy) Validate() error {
|
||||||
|
|
||||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
|
||||||
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.LinearRegression.Interval})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position control
|
// Position control
|
||||||
|
@ -141,8 +140,7 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
||||||
|
|
||||||
orderForm := s.generateOrderForm(side, quantity, types.SideEffectTypeAutoRepay)
|
orderForm := s.generateOrderForm(side, quantity, types.SideEffectTypeAutoRepay)
|
||||||
|
|
||||||
log.Infof("submit close position order %v", orderForm)
|
bbgo.Notify("submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, orderForm)
|
||||||
bbgo.Notify("Submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage)
|
|
||||||
|
|
||||||
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,43 +151,88 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preloadSupertrend preloads supertrend indicator
|
||||||
|
func preloadSupertrend(supertrend *indicator.Supertrend, kLineStore *bbgo.MarketDataStore) {
|
||||||
|
if klines, ok := kLineStore.KLinesOfInterval(supertrend.Interval); ok {
|
||||||
|
for i := 0; i < len(*klines); i++ {
|
||||||
|
supertrend.Update((*klines)[i].GetHigh().Float64(), (*klines)[i].GetLow().Float64(), (*klines)[i].GetClose().Float64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// setupIndicators initializes indicators
|
// setupIndicators initializes indicators
|
||||||
func (s *Strategy) setupIndicators() {
|
func (s *Strategy) setupIndicators() {
|
||||||
if s.FastDEMAWindow == 0 {
|
// K-line store for indicators
|
||||||
s.FastDEMAWindow = 144
|
kLineStore, _ := s.session.MarketDataStore(s.Symbol)
|
||||||
}
|
|
||||||
s.fastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FastDEMAWindow}}
|
|
||||||
|
|
||||||
if s.SlowDEMAWindow == 0 {
|
// Double DEMA
|
||||||
s.SlowDEMAWindow = 169
|
s.doubleDema = newDoubleDema(kLineStore, s.Interval, s.FastDEMAWindow, s.SlowDEMAWindow)
|
||||||
}
|
|
||||||
s.slowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}}
|
|
||||||
|
|
||||||
if s.SupertrendWindow == 0 {
|
// Supertrend
|
||||||
s.SupertrendWindow = 39
|
if s.Window == 0 {
|
||||||
|
s.Window = 39
|
||||||
}
|
}
|
||||||
if s.SupertrendMultiplier == 0 {
|
if s.SupertrendMultiplier == 0 {
|
||||||
s.SupertrendMultiplier = 3
|
s.SupertrendMultiplier = 3
|
||||||
}
|
}
|
||||||
s.Supertrend = &indicator.Supertrend{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}, ATRMultiplier: s.SupertrendMultiplier}
|
s.Supertrend = &indicator.Supertrend{IntervalWindow: types.IntervalWindow{Window: s.Window, Interval: s.Interval}, ATRMultiplier: s.SupertrendMultiplier}
|
||||||
s.Supertrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}}
|
s.Supertrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.Window, Interval: s.Interval}}
|
||||||
|
s.Supertrend.Bind(kLineStore)
|
||||||
|
preloadSupertrend(s.Supertrend, kLineStore)
|
||||||
|
|
||||||
|
// Linear Regression
|
||||||
|
if s.LinearRegression != nil {
|
||||||
|
if s.LinearRegression.Window == 0 {
|
||||||
|
s.LinearRegression = nil
|
||||||
|
} else if s.LinearRegression.Interval == "" {
|
||||||
|
s.LinearRegression = nil
|
||||||
|
} else {
|
||||||
|
s.LinearRegression.Bind(kLineStore)
|
||||||
|
s.LinearRegression.preload(kLineStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateIndicators updates indicators
|
func (s *Strategy) shouldStop(kline types.KLine, stSignal types.Direction, demaSignal types.Direction, lgSignal types.Direction) bool {
|
||||||
func (s *Strategy) updateIndicators(kline types.KLine) {
|
stopNow := false
|
||||||
closePrice := kline.GetClose().Float64()
|
base := s.Position.GetBase()
|
||||||
|
baseSign := base.Sign()
|
||||||
|
|
||||||
// Update indicators
|
if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) {
|
||||||
if kline.Interval == s.fastDEMA.Interval {
|
// SL by triggering Kline low/high
|
||||||
s.fastDEMA.Update(closePrice)
|
bbgo.Notify("%s stop loss by triggering the kline low/high", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.TakeProfitAtrMultiplier > 0 && !s.currentTakeProfitPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) < 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) > 0)) {
|
||||||
|
// TP by multiple of ATR
|
||||||
|
bbgo.Notify("%s take profit by multiple of ATR", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.StopByReversedSupertrend && ((baseSign < 0 && stSignal == types.DirectionUp) || (baseSign > 0 && stSignal == types.DirectionDown)) {
|
||||||
|
// Use supertrend signal to TP/SL
|
||||||
|
bbgo.Notify("%s stop by the reversed signal of Supertrend", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.StopByReversedDema && ((baseSign < 0 && demaSignal == types.DirectionUp) || (baseSign > 0 && demaSignal == types.DirectionDown)) {
|
||||||
|
// Use DEMA signal to TP/SL
|
||||||
|
bbgo.Notify("%s stop by the reversed signal of DEMA", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
|
} else if s.StopByReversedLinGre && ((baseSign < 0 && lgSignal == types.DirectionUp) || (baseSign > 0 && lgSignal == types.DirectionDown)) {
|
||||||
|
// Use linear regression signal to TP/SL
|
||||||
|
bbgo.Notify("%s stop by the reversed signal of linear regression", s.Symbol)
|
||||||
|
stopNow = true
|
||||||
}
|
}
|
||||||
if kline.Interval == s.slowDEMA.Interval {
|
|
||||||
s.slowDEMA.Update(closePrice)
|
return stopNow
|
||||||
}
|
}
|
||||||
if kline.Interval == s.Supertrend.Interval {
|
|
||||||
s.Supertrend.Update(kline.GetHigh().Float64(), kline.GetLow().Float64(), closePrice)
|
func (s *Strategy) getSide(stSignal types.Direction, demaSignal types.Direction, lgSignal types.Direction) types.SideType {
|
||||||
|
var side types.SideType
|
||||||
|
|
||||||
|
if stSignal == types.DirectionUp && demaSignal == types.DirectionUp && (s.LinearRegression == nil || lgSignal == types.DirectionUp) {
|
||||||
|
side = types.SideTypeBuy
|
||||||
|
} else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown && (s.LinearRegression == nil || lgSignal == types.DirectionDown) {
|
||||||
|
side = types.SideTypeSell
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return side
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder {
|
func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder {
|
||||||
|
@ -200,7 +243,6 @@ func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Va
|
||||||
Type: types.OrderTypeMarket,
|
Type: types.OrderTypeMarket,
|
||||||
Quantity: quantity,
|
Quantity: quantity,
|
||||||
MarginSideEffect: marginOrderSideEffect,
|
MarginSideEffect: marginOrderSideEffect,
|
||||||
GroupID: s.groupID,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return orderForm
|
return orderForm
|
||||||
|
@ -223,9 +265,11 @@ func (s *Strategy) calculateQuantity(currentPrice fixedpoint.Value) fixedpoint.V
|
||||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||||
s.session = session
|
s.session = session
|
||||||
|
|
||||||
|
s.currentStopLossPrice = fixedpoint.Zero
|
||||||
|
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||||
|
|
||||||
// calculate group id for orders
|
// calculate group id for orders
|
||||||
instanceID := s.InstanceID()
|
instanceID := s.InstanceID()
|
||||||
s.groupID = util.FNV32(instanceID)
|
|
||||||
|
|
||||||
// If position is nil, we need to allocate a new position for calculation
|
// If position is nil, we need to allocate a new position for calculation
|
||||||
if s.Position == nil {
|
if s.Position == nil {
|
||||||
|
@ -235,6 +279,15 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
s.Position.Strategy = ID
|
s.Position.Strategy = ID
|
||||||
s.Position.StrategyInstanceID = s.InstanceID()
|
s.Position.StrategyInstanceID = s.InstanceID()
|
||||||
|
|
||||||
|
// Profit stats
|
||||||
|
if s.ProfitStats == nil {
|
||||||
|
s.ProfitStats = types.NewProfitStats(s.Market)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TradeStats == nil {
|
||||||
|
s.TradeStats = types.NewTradeStats(s.Symbol)
|
||||||
|
}
|
||||||
|
|
||||||
// Set fee rate
|
// Set fee rate
|
||||||
if s.session.MakerFeeRate.Sign() > 0 || s.session.TakerFeeRate.Sign() > 0 {
|
if s.session.MakerFeeRate.Sign() > 0 || s.session.TakerFeeRate.Sign() > 0 {
|
||||||
s.Position.SetExchangeFeeRate(s.session.ExchangeName, types.ExchangeFee{
|
s.Position.SetExchangeFeeRate(s.session.ExchangeName, types.ExchangeFee{
|
||||||
|
@ -243,15 +296,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profit
|
|
||||||
if s.ProfitStats == nil {
|
|
||||||
s.ProfitStats = types.NewProfitStats(s.Market)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup order executor
|
// Setup order executor
|
||||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||||
s.orderExecutor.BindEnvironment(s.Environment)
|
s.orderExecutor.BindEnvironment(s.Environment)
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
|
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||||
s.orderExecutor.Bind()
|
s.orderExecutor.Bind()
|
||||||
|
|
||||||
// Sync position to redis on trade
|
// Sync position to redis on trade
|
||||||
|
@ -259,16 +308,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
bbgo.Sync(s)
|
bbgo.Sync(s)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.stopC = make(chan struct{})
|
|
||||||
|
|
||||||
// StrategyController
|
// StrategyController
|
||||||
s.Status = types.StrategyStatusRunning
|
s.Status = types.StrategyStatusRunning
|
||||||
|
|
||||||
s.OnSuspend(func() {
|
s.OnSuspend(func() {
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
_ = s.Persistence.Sync(s)
|
bbgo.Sync(s)
|
||||||
})
|
})
|
||||||
|
|
||||||
s.OnEmergencyStop(func() {
|
s.OnEmergencyStop(func() {
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
// Close 100% position
|
// Close 100% position
|
||||||
|
@ -278,108 +323,77 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
// Setup indicators
|
// Setup indicators
|
||||||
s.setupIndicators()
|
s.setupIndicators()
|
||||||
|
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
// Exit methods
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
for _, method := range s.ExitMethods {
|
||||||
|
method.Bind(session, s.orderExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) {
|
||||||
// StrategyController
|
// StrategyController
|
||||||
if s.Status != types.StrategyStatusRunning {
|
if s.Status != types.StrategyStatusRunning {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip k-lines from other symbols or other intervals
|
closePrice := kline.GetClose()
|
||||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
openPrice := kline.GetOpen()
|
||||||
return
|
closePrice64 := closePrice.Float64()
|
||||||
}
|
openPrice64 := openPrice.Float64()
|
||||||
|
|
||||||
// Update indicators
|
// Supertrend signal
|
||||||
s.updateIndicators(kline)
|
|
||||||
|
|
||||||
// Get signals
|
|
||||||
closePrice := kline.GetClose().Float64()
|
|
||||||
openPrice := kline.GetOpen().Float64()
|
|
||||||
stSignal := s.Supertrend.GetSignal()
|
stSignal := s.Supertrend.GetSignal()
|
||||||
var demaSignal types.Direction
|
|
||||||
if closePrice > s.fastDEMA.Last() && closePrice > s.slowDEMA.Last() && !(openPrice > s.fastDEMA.Last() && openPrice > s.slowDEMA.Last()) {
|
// DEMA signal
|
||||||
demaSignal = types.DirectionUp
|
demaSignal := s.doubleDema.getDemaSignal(openPrice64, closePrice64)
|
||||||
} else if closePrice < s.fastDEMA.Last() && closePrice < s.slowDEMA.Last() && !(openPrice < s.fastDEMA.Last() && openPrice < s.slowDEMA.Last()) {
|
|
||||||
demaSignal = types.DirectionDown
|
// Linear Regression signal
|
||||||
} else {
|
var lgSignal types.Direction
|
||||||
demaSignal = types.DirectionNone
|
if s.LinearRegression != nil {
|
||||||
|
lgSignal = s.LinearRegression.GetSignal()
|
||||||
}
|
}
|
||||||
|
|
||||||
base := s.Position.GetBase()
|
// TP/SL if there's non-dust position and meets the criteria
|
||||||
baseSign := base.Sign()
|
if !s.Market.IsDustQuantity(s.Position.GetBase().Abs(), closePrice) && s.shouldStop(kline, stSignal, demaSignal, lgSignal) {
|
||||||
|
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
||||||
|
s.currentStopLossPrice = fixedpoint.Zero
|
||||||
|
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TP/SL if there's non-dust position
|
// Get order side
|
||||||
if !s.Market.IsDustQuantity(base.Abs(), kline.GetClose()) {
|
side := s.getSide(stSignal, demaSignal, lgSignal)
|
||||||
if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) {
|
// Set TP/SL price if needed
|
||||||
// SL by triggering Kline low
|
if side == types.SideTypeBuy {
|
||||||
log.Infof("%s SL by triggering Kline low", s.Symbol)
|
if s.StopLossByTriggeringK {
|
||||||
bbgo.Notify("%s StopLoss by triggering the kline low", s.Symbol)
|
s.currentStopLossPrice = kline.GetLow()
|
||||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
}
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
if s.TakeProfitAtrMultiplier > 0 {
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
s.currentTakeProfitPrice = closePrice.Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier))
|
||||||
}
|
}
|
||||||
} else if s.TakeProfitMultiplier > 0 && !s.currentTakeProfitPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) < 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) > 0)) {
|
} else if side == types.SideTypeSell {
|
||||||
// TP by multiple of ATR
|
if s.StopLossByTriggeringK {
|
||||||
log.Infof("%s TP by multiple of ATR", s.Symbol)
|
s.currentStopLossPrice = kline.GetHigh()
|
||||||
bbgo.Notify("%s TakeProfit by multiple of ATR", s.Symbol)
|
}
|
||||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
if s.TakeProfitAtrMultiplier > 0 {
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
s.currentTakeProfitPrice = closePrice.Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier))
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
|
||||||
}
|
|
||||||
} else if s.TPSLBySignal {
|
|
||||||
// Use signals to TP/SL
|
|
||||||
log.Infof("%s TP/SL by reverse of DEMA or Supertrend", s.Symbol)
|
|
||||||
bbgo.Notify("%s TP/SL by reverse of DEMA or Supertrend", s.Symbol)
|
|
||||||
if (baseSign < 0 && (stSignal == types.DirectionUp || demaSignal == types.DirectionUp)) || (baseSign > 0 && (stSignal == types.DirectionDown || demaSignal == types.DirectionDown)) {
|
|
||||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
|
||||||
s.currentStopLossPrice = fixedpoint.Zero
|
|
||||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open position
|
// Open position
|
||||||
var side types.SideType
|
|
||||||
if stSignal == types.DirectionUp && demaSignal == types.DirectionUp {
|
|
||||||
side = types.SideTypeBuy
|
|
||||||
if s.StopLossByTriggeringK {
|
|
||||||
s.currentStopLossPrice = kline.GetLow()
|
|
||||||
}
|
|
||||||
if s.TakeProfitMultiplier > 0 {
|
|
||||||
s.currentTakeProfitPrice = kline.GetClose().Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitMultiplier))
|
|
||||||
}
|
|
||||||
} else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown {
|
|
||||||
side = types.SideTypeSell
|
|
||||||
if s.StopLossByTriggeringK {
|
|
||||||
s.currentStopLossPrice = kline.GetHigh()
|
|
||||||
}
|
|
||||||
if s.TakeProfitMultiplier > 0 {
|
|
||||||
s.currentTakeProfitPrice = kline.GetClose().Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitMultiplier))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The default value of side is an empty string. Unless side is set by the checks above, the result of the following condition is false
|
// The default value of side is an empty string. Unless side is set by the checks above, the result of the following condition is false
|
||||||
if side == types.SideTypeSell || side == types.SideTypeBuy {
|
if side == types.SideTypeSell || side == types.SideTypeBuy {
|
||||||
log.Infof("open %s position for signal %v", s.Symbol, side)
|
|
||||||
bbgo.Notify("open %s position for signal %v", s.Symbol, side)
|
bbgo.Notify("open %s position for signal %v", s.Symbol, side)
|
||||||
// Close opposite position if any
|
// Close opposite position if any
|
||||||
if !s.Position.IsDust(kline.GetClose()) {
|
if !s.Position.IsDust(closePrice) {
|
||||||
if (side == types.SideTypeSell && s.Position.IsLong()) || (side == types.SideTypeBuy && s.Position.IsShort()) {
|
if (side == types.SideTypeSell && s.Position.IsLong()) || (side == types.SideTypeBuy && s.Position.IsShort()) {
|
||||||
log.Infof("close existing %s position before open a new position", s.Symbol)
|
|
||||||
bbgo.Notify("close existing %s position before open a new position", s.Symbol)
|
bbgo.Notify("close existing %s position before open a new position", s.Symbol)
|
||||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("existing %s position has the same direction with the signal", s.Symbol)
|
|
||||||
bbgo.Notify("existing %s position has the same direction with the signal", s.Symbol)
|
bbgo.Notify("existing %s position has the same direction with the signal", s.Symbol)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
orderForm := s.generateOrderForm(side, s.calculateQuantity(kline.GetClose()), types.SideEffectTypeMarginBuy)
|
orderForm := s.generateOrderForm(side, s.calculateQuantity(closePrice), types.SideEffectTypeMarginBuy)
|
||||||
log.Infof("submit open position order %v", orderForm)
|
log.Infof("submit open position order %v", orderForm)
|
||||||
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -387,14 +401,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
||||||
bbgo.Notify("can not place %s open position order", s.Symbol)
|
bbgo.Notify("can not place %s open position order", s.Symbol)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
close(s.stopC)
|
|
||||||
|
|
||||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -94,6 +94,10 @@ type SeriesExtend interface {
|
||||||
Covariance(b Series, length int) float64
|
Covariance(b Series, length int) float64
|
||||||
Correlation(b Series, length int, method ...CorrFunc) float64
|
Correlation(b Series, length int, method ...CorrFunc) float64
|
||||||
Rank(length int) SeriesExtend
|
Rank(length int) SeriesExtend
|
||||||
|
Sigmoid() SeriesExtend
|
||||||
|
Softmax(window int) SeriesExtend
|
||||||
|
Entropy(window int) float64
|
||||||
|
CrossEntropy(b Series, window int) float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type SeriesBase struct {
|
type SeriesBase struct {
|
||||||
|
@ -524,7 +528,69 @@ var _ Series = &MulSeriesResult{}
|
||||||
// if limit is given, will only calculate the first limit numbers (a.Index[0..limit])
|
// if limit is given, will only calculate the first limit numbers (a.Index[0..limit])
|
||||||
// otherwise will operate on all elements
|
// otherwise will operate on all elements
|
||||||
func Dot(a interface{}, b interface{}, limit ...int) float64 {
|
func Dot(a interface{}, b interface{}, limit ...int) float64 {
|
||||||
return Sum(Mul(a, b), limit...)
|
var aaf float64
|
||||||
|
var aas Series
|
||||||
|
var bbf float64
|
||||||
|
var bbs Series
|
||||||
|
var isaf, isbf bool
|
||||||
|
|
||||||
|
switch tp := a.(type) {
|
||||||
|
case float64:
|
||||||
|
aaf = tp
|
||||||
|
isaf = true
|
||||||
|
case Series:
|
||||||
|
aas = tp
|
||||||
|
isaf = false
|
||||||
|
default:
|
||||||
|
panic("input should be either Series or float64")
|
||||||
|
}
|
||||||
|
switch tp := b.(type) {
|
||||||
|
case float64:
|
||||||
|
bbf = tp
|
||||||
|
isbf = true
|
||||||
|
case Series:
|
||||||
|
bbs = tp
|
||||||
|
isbf = false
|
||||||
|
default:
|
||||||
|
panic("input should be either Series or float64")
|
||||||
|
|
||||||
|
}
|
||||||
|
l := 1
|
||||||
|
if len(limit) > 0 {
|
||||||
|
l = limit[0]
|
||||||
|
} else if isaf && isbf {
|
||||||
|
l = 1
|
||||||
|
} else {
|
||||||
|
if !isaf {
|
||||||
|
l = aas.Length()
|
||||||
|
}
|
||||||
|
if !isbf {
|
||||||
|
if l > bbs.Length() {
|
||||||
|
l = bbs.Length()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isaf && isbf {
|
||||||
|
return aaf * bbf * float64(l)
|
||||||
|
} else if isaf && !isbf {
|
||||||
|
sum := 0.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
sum += aaf * bbs.Index(i)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
} else if !isaf && isbf {
|
||||||
|
sum := 0.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
sum += aas.Index(i) * bbf
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
} else {
|
||||||
|
sum := 0.
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
sum += aas.Index(i) * bbs.Index(i)
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract elements from the Series to a float64 array, following the order of Index(0..limit)
|
// Extract elements from the Series to a float64 array, following the order of Index(0..limit)
|
||||||
|
@ -881,4 +947,175 @@ func Rolling(a Series, window int) *RollingResult {
|
||||||
return &RollingResult{a, window}
|
return &RollingResult{a, window}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SigmoidResult struct {
|
||||||
|
a Series
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigmoidResult) Last() float64 {
|
||||||
|
return 1. / (1. + math.Exp(-s.a.Last()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigmoidResult) Index(i int) float64 {
|
||||||
|
return 1. / (1. + math.Exp(-s.a.Index(i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigmoidResult) Length() int {
|
||||||
|
return s.a.Length()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sigmoid returns the input values in range of -1 to 1
|
||||||
|
// along the sigmoid or s-shaped curve.
|
||||||
|
// Commonly used in machine learning while training neural networks
|
||||||
|
// as an activation function.
|
||||||
|
func Sigmoid(a Series) SeriesExtend {
|
||||||
|
return NewSeries(&SigmoidResult{a})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoftMax returns the input value in the range of 0 to 1
|
||||||
|
// with sum of all the probabilities being equal to one.
|
||||||
|
// It is commonly used in machine learning neural networks.
|
||||||
|
// Will return Softmax SeriesExtend result based in latest [window] numbers from [a] Series
|
||||||
|
func Softmax(a Series, window int) SeriesExtend {
|
||||||
|
s := 0.0
|
||||||
|
max := Highest(a, window)
|
||||||
|
for i := 0; i < window; i++ {
|
||||||
|
s += math.Exp(a.Index(i) - max)
|
||||||
|
}
|
||||||
|
out := NewQueue(window)
|
||||||
|
for i := window - 1; i >= 0; i-- {
|
||||||
|
out.Update(math.Exp(a.Index(i)-max) / s)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entropy computes the Shannon entropy of a distribution or the distance between
|
||||||
|
// two distributions. The natural logarithm is used.
|
||||||
|
// - sum(v * ln(v))
|
||||||
|
func Entropy(a Series, window int) (e float64) {
|
||||||
|
for i := 0; i < window; i++ {
|
||||||
|
v := a.Index(i)
|
||||||
|
if v != 0 {
|
||||||
|
e -= v * math.Log(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrossEntropy computes the cross-entropy between the two distributions
|
||||||
|
func CrossEntropy(a, b Series, window int) (e float64) {
|
||||||
|
for i := 0; i < window; i++ {
|
||||||
|
v := a.Index(i)
|
||||||
|
if v != 0 {
|
||||||
|
e -= v * math.Log(b.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func sigmoid(z float64) float64 {
|
||||||
|
return 1. / (1. + math.Exp(-z))
|
||||||
|
}
|
||||||
|
|
||||||
|
func propagate(w []float64, gradient float64, x [][]float64, y []float64) (float64, []float64, float64) {
|
||||||
|
logloss_epoch := 0.0
|
||||||
|
var activations []float64
|
||||||
|
var dw []float64
|
||||||
|
m := len(y)
|
||||||
|
db := 0.0
|
||||||
|
for i, xx := range x {
|
||||||
|
result := 0.0
|
||||||
|
for j, ww := range w {
|
||||||
|
result += ww * xx[j]
|
||||||
|
}
|
||||||
|
a := sigmoid(result + gradient)
|
||||||
|
activations = append(activations, a)
|
||||||
|
logloss := a*math.Log1p(y[i]) + (1.-a)*math.Log1p(1-y[i])
|
||||||
|
logloss_epoch += logloss
|
||||||
|
|
||||||
|
db += a - y[i]
|
||||||
|
}
|
||||||
|
for j := range w {
|
||||||
|
err := 0.0
|
||||||
|
for i, xx := range x {
|
||||||
|
err_i := activations[i] - y[i]
|
||||||
|
err += err_i * xx[j]
|
||||||
|
}
|
||||||
|
err /= float64(m)
|
||||||
|
dw = append(dw, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cost := -(logloss_epoch / float64(len(x)))
|
||||||
|
db /= float64(m)
|
||||||
|
return cost, dw, db
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogisticRegression(x []Series, y Series, lookback, iterations int, learningRate float64) *LogisticRegressionModel {
|
||||||
|
features := len(x)
|
||||||
|
if features == 0 {
|
||||||
|
panic("no feature to train")
|
||||||
|
}
|
||||||
|
w := make([]float64, features)
|
||||||
|
if lookback > x[0].Length() {
|
||||||
|
lookback = x[0].Length()
|
||||||
|
}
|
||||||
|
xx := make([][]float64, lookback)
|
||||||
|
for i := 0; i < lookback; i++ {
|
||||||
|
for j := 0; j < features; j++ {
|
||||||
|
xx[i] = append(xx[i], x[j].Index(lookback-i-1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yy := Reverse(y, lookback)
|
||||||
|
|
||||||
|
b := 0.
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
_, dw, db := propagate(w, b, xx, yy)
|
||||||
|
for j := range w {
|
||||||
|
w[j] = w[j] - (learningRate * dw[j])
|
||||||
|
}
|
||||||
|
b -= learningRate * db
|
||||||
|
}
|
||||||
|
return &LogisticRegressionModel{
|
||||||
|
Weight: w,
|
||||||
|
Gradient: b,
|
||||||
|
LearningRate: learningRate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogisticRegressionModel struct {
|
||||||
|
Weight []float64
|
||||||
|
Gradient float64
|
||||||
|
LearningRate float64
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Might not be correct.
|
||||||
|
// Please double check before uncomment this
|
||||||
|
func (l *LogisticRegressionModel) Update(x []float64, y float64) {
|
||||||
|
z := 0.0
|
||||||
|
for i, w := l.Weight {
|
||||||
|
z += w * x[i]
|
||||||
|
}
|
||||||
|
a := sigmoid(z + l.Gradient)
|
||||||
|
//logloss := a * math.Log1p(y) + (1.-a)*math.Log1p(1-y)
|
||||||
|
db = a - y
|
||||||
|
var dw []float64
|
||||||
|
for j, ww := range l.Weight {
|
||||||
|
err := db * x[j]
|
||||||
|
dw = append(dw, err)
|
||||||
|
}
|
||||||
|
for i := range l.Weight {
|
||||||
|
l.Weight[i] -= l.LearningRate * dw[i]
|
||||||
|
}
|
||||||
|
l.Gradient -= l.LearningRate * db
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (l *LogisticRegressionModel) Predict(x []float64) float64 {
|
||||||
|
z := 0.0
|
||||||
|
for i, w := range l.Weight {
|
||||||
|
z += w * x[i]
|
||||||
|
}
|
||||||
|
return sigmoid(z + l.Gradient)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: ta.linreg
|
// TODO: ta.linreg
|
||||||
|
|
|
@ -2,6 +2,7 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gonum.org/v1/gonum/stat"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,3 +85,62 @@ func TestSkew(t *testing.T) {
|
||||||
sk := Skew(&a, 4)
|
sk := Skew(&a, 4)
|
||||||
assert.InDelta(t, sk, 1.129338, 0.001)
|
assert.InDelta(t, sk, 1.129338, 0.001)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEntropy(t *testing.T) {
|
||||||
|
var a = Float64Slice{.2, .0, .6, .2}
|
||||||
|
e := stat.Entropy(a)
|
||||||
|
assert.InDelta(t, e, Entropy(&a, a.Length()), 0.0001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCrossEntropy(t *testing.T) {
|
||||||
|
var a = Float64Slice{.2, .0, .6, .2}
|
||||||
|
var b = Float64Slice{.3, .6, .0, .1}
|
||||||
|
e := stat.CrossEntropy(a, b)
|
||||||
|
assert.InDelta(t, e, CrossEntropy(&a, &b, a.Length()), 0.0001)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSoftmax(t *testing.T) {
|
||||||
|
var a = Float64Slice{3.0, 1.0, 0.2}
|
||||||
|
out := Softmax(&a, a.Length())
|
||||||
|
r := Float64Slice{0.8360188027814407, 0.11314284146556013, 0.05083835575299916}
|
||||||
|
for i := 0; i < out.Length(); i++ {
|
||||||
|
assert.InDelta(t, r.Index(i), out.Index(i), 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSigmoid(t *testing.T) {
|
||||||
|
a := Float64Slice{3.0, 1.0, 2.1}
|
||||||
|
out := Sigmoid(&a)
|
||||||
|
r := Float64Slice{0.9525741268224334, 0.7310585786300049, 0.8909031788043871}
|
||||||
|
for i := 0; i < out.Length(); i++ {
|
||||||
|
assert.InDelta(t, r.Index(i), out.Index(i), 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// from https://en.wikipedia.org/wiki/Logistic_regression
|
||||||
|
func TestLogisticRegression(t *testing.T) {
|
||||||
|
a := []Float64Slice{{0.5, 0.75, 1., 1.25, 1.5, 1.75, 2.0, 2.25, 2.5, 2.75, 3., 3.25, 3.5, 4., 4.25, 4.5, 4.75, 5., 5.5}}
|
||||||
|
b := Float64Slice{0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1}
|
||||||
|
var x []Series
|
||||||
|
x = append(x, &a[0])
|
||||||
|
|
||||||
|
model := LogisticRegression(x, &b, a[0].Length(), 8000, 0.0009)
|
||||||
|
inputs := []float64{1., 2., 2.7, 3., 4., 5.}
|
||||||
|
results := []bool{false, false, true, true, true, true}
|
||||||
|
for i, x := range inputs {
|
||||||
|
input := []float64{x}
|
||||||
|
pred := model.Predict(input)
|
||||||
|
assert.Equal(t, pred > 0.5, results[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDot(t *testing.T) {
|
||||||
|
a := Float64Slice{7, 6, 5, 4, 3, 2, 1, 0}
|
||||||
|
b := Float64Slice{200., 201., 203., 204., 203., 199.}
|
||||||
|
out1 := Dot(&a, &b, 3)
|
||||||
|
assert.InDelta(t, out1, 611., 0.001)
|
||||||
|
out2 := Dot(&a, 3., 2)
|
||||||
|
assert.InDelta(t, out2, 3., 0.001)
|
||||||
|
out3 := Dot(3., &a, 2)
|
||||||
|
assert.InDelta(t, out2, out3, 0.001)
|
||||||
|
}
|
||||||
|
|
|
@ -609,7 +609,7 @@ type KLineCallBack func(k KLine)
|
||||||
|
|
||||||
func KLineWith(symbol string, interval Interval, callback KLineCallBack) KLineCallBack {
|
func KLineWith(symbol string, interval Interval, callback KLineCallBack) KLineCallBack {
|
||||||
return func(k KLine) {
|
return func(k KLine) {
|
||||||
if k.Symbol != symbol || k.Interval != interval {
|
if k.Symbol != symbol || (k.Interval != "" && k.Interval != interval) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
callback(k)
|
callback(k)
|
||||||
|
|
|
@ -124,3 +124,19 @@ func (s *SeriesBase) Correlation(b Series, length int, method ...CorrFunc) float
|
||||||
func (s *SeriesBase) Rank(length int) SeriesExtend {
|
func (s *SeriesBase) Rank(length int) SeriesExtend {
|
||||||
return Rank(s, length)
|
return Rank(s, length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SeriesBase) Sigmoid() SeriesExtend {
|
||||||
|
return Sigmoid(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SeriesBase) Softmax(window int) SeriesExtend {
|
||||||
|
return Softmax(s, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SeriesBase) Entropy(window int) float64 {
|
||||||
|
return Entropy(s, window)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SeriesBase) CrossEntropy(b Series, window int) float64 {
|
||||||
|
return CrossEntropy(s, b, window)
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
)
|
)
|
||||||
|
@ -11,7 +12,7 @@ import (
|
||||||
func FilterSimpleArgs(args []interface{}) (simpleArgs []interface{}) {
|
func FilterSimpleArgs(args []interface{}) (simpleArgs []interface{}) {
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
switch arg.(type) {
|
switch arg.(type) {
|
||||||
case int, int64, int32, uint64, uint32, string, []byte, float64, float32, fixedpoint.Value:
|
case int, int64, int32, uint64, uint32, string, []byte, float64, float32, fixedpoint.Value, time.Time:
|
||||||
simpleArgs = append(simpleArgs, arg)
|
simpleArgs = append(simpleArgs, arg)
|
||||||
default:
|
default:
|
||||||
rt := reflect.TypeOf(arg)
|
rt := reflect.TypeOf(arg)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//go:build !release
|
|
||||||
// +build !release
|
// +build !release
|
||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
const Version = "v1.35.0-daaa3352-dev"
|
const Version = "v1.36.0-cc8821bb-dev"
|
||||||
|
|
||||||
|
const VersionGitRef = "cc8821bb"
|
||||||
|
|
||||||
const VersionGitRef = "daaa3352"
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//go:build release
|
|
||||||
// +build release
|
// +build release
|
||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
const Version = "v1.35.0-daaa3352"
|
const Version = "v1.36.0-cc8821bb"
|
||||||
|
|
||||||
|
const VersionGitRef = "cc8821bb"
|
||||||
|
|
||||||
const VersionGitRef = "daaa3352"
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user