mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +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
|
||||
|
||||
/localconfig
|
||||
|
||||
/pkg/server/assets.go
|
||||
|
||||
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) => {
|
||||
return fetch(
|
||||
`${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) => {
|
||||
switch (s) {
|
||||
case "1m":
|
||||
|
@ -390,7 +416,7 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
|||
const resizeObserver = useRef<any>();
|
||||
const intervals = props.reportSummary.intervals || [];
|
||||
|
||||
intervals.sort((a,b) => {
|
||||
intervals.sort((a, b) => {
|
||||
const as = parseInterval(a)
|
||||
const bs = parseInterval(b)
|
||||
if (as < bs) {
|
||||
|
@ -403,7 +429,6 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
|||
|
||||
const [currentInterval, setCurrentInterval] = useState(intervals.length > 0 ? intervals[intervals.length - 1] : '1m');
|
||||
const [showPositionBase, setShowPositionBase] = useState(false);
|
||||
const [showCanceledOrders, setShowCanceledOrders] = useState(false);
|
||||
const [showPositionAverageCost, setShowPositionAverageCost] = useState(false);
|
||||
const [orders, setOrders] = useState<Order[]>([]);
|
||||
|
||||
|
@ -412,7 +437,6 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
|||
new Date(props.reportSummary.endTime),
|
||||
]
|
||||
const [selectedTimeRange, setSelectedTimeRange] = useState(reportTimeRange)
|
||||
const [timeRange, setTimeRange] = useState(reportTimeRange);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chartContainerRef.current || chartContainerRef.current.children.length > 0) {
|
||||
|
@ -423,7 +447,7 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
|||
const fetchers = [];
|
||||
const ordersFetcher = fetchOrders(props.basePath, props.runID).then((orders: Order[] | void) => {
|
||||
if (orders) {
|
||||
const markers = ordersToMarkers(currentInterval, orders);
|
||||
const markers = ordersToMarkers(currentInterval, selectOrders(orders, selectedTimeRange[0], selectedTimeRange[1]));
|
||||
chartData.orders = orders;
|
||||
chartData.markers = markers;
|
||||
setOrders(orders);
|
||||
|
@ -436,7 +460,8 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
|||
const manifest = props.reportSummary?.manifests[0];
|
||||
if (manifest && manifest.type === "strategyProperty" && manifest.strategyProperty === "position") {
|
||||
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);
|
||||
}
|
||||
|
@ -594,7 +619,7 @@ const TradingViewChart = (props: TradingViewChartProps) => {
|
|||
|
||||
<TimeRangeSlider
|
||||
selectedInterval={selectedTimeRange}
|
||||
timelineInterval={timeRange}
|
||||
timelineInterval={reportTimeRange}
|
||||
formatTick={(ms: Date) => format(new Date(ms), 'M d HH')}
|
||||
step={1000 * parseInterval(currentInterval)}
|
||||
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");
|
||||
}
|
||||
|
||||
const createOHLCLegendUpdater = (legend: HTMLDivElement, prefix: string) => {
|
||||
return (param: any, time : any) => {
|
||||
return (param: any, time: any) => {
|
||||
if (param) {
|
||||
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;
|
||||
|
|
|
@ -19,7 +19,7 @@ backtest:
|
|||
# see here for more details
|
||||
# https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp
|
||||
startTime: "2022-01-01"
|
||||
endTime: "2022-06-18"
|
||||
endTime: "2022-06-30"
|
||||
symbols:
|
||||
- BTCUSDT
|
||||
accounts:
|
||||
|
@ -36,7 +36,12 @@ exchangeStrategies:
|
|||
symbol: BTCUSDT
|
||||
|
||||
# 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: 1.0
|
||||
|
@ -45,18 +50,31 @@ exchangeStrategies:
|
|||
fastDEMAWindow: 144
|
||||
slowDEMAWindow: 169
|
||||
|
||||
# Supertrend indicator parameters
|
||||
superTrend:
|
||||
# ATR window used by Supertrend
|
||||
averageTrueRangeWindow: 39
|
||||
# ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are
|
||||
averageTrueRangeMultiplier: 3
|
||||
# Use linear regression as trend confirmation
|
||||
linearRegression:
|
||||
interval: 5m
|
||||
window: 80
|
||||
|
||||
# TP according to ATR multiple, 0 to disable this
|
||||
takeProfitMultiplier: 3
|
||||
TakeProfitAtrMultiplier: 0
|
||||
|
||||
# Set SL price to the low of the triggering Kline
|
||||
stopLossByTriggeringK: true
|
||||
stopLossByTriggeringK: false
|
||||
|
||||
# TP/SL by reversed signals
|
||||
tpslBySignal: true
|
||||
# TP/SL by reversed supertrend signal
|
||||
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 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
--optimizer-config string config file (default "optimizer.yaml")
|
||||
--output string backtest report output directory (default "output")
|
||||
--tsv print optimizer metrics in csv format
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
@ -43,4 +44,4 @@ bbgo optimize [flags]
|
|||
|
||||
* [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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
```
|
||||
-h, --help help for pnl
|
||||
--include-transfer convert transfer records into trades
|
||||
--limit int number of trades
|
||||
--session string target exchange
|
||||
--symbol string trading symbol
|
||||
-h, --help help for pnl
|
||||
--include-transfer convert transfer records into trades
|
||||
--limit uint number of trades
|
||||
--session stringArray target exchange sessions
|
||||
--since string query trades from a time point
|
||||
--symbol string trading symbol
|
||||
--sync sync before loading trades
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
@ -48,4 +50,4 @@ bbgo pnl [flags]
|
|||
|
||||
* [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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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
|
||||
|
||||
###### 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.
|
||||
- `averageTrueRangeMultiplier`
|
||||
- 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.
|
||||
- `stopLossByTriggeringK`
|
||||
- Set SL price to the low of the triggering Kline.
|
||||
- `tpslBySignal`
|
||||
- TP/SL by reversed signals.
|
||||
- Set SL price to the low/high of the triggering Kline.
|
||||
- `stopByReversedSupertrend`
|
||||
- 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
|
||||
|
|
|
@ -19,6 +19,8 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
|||
var bidVolume = fixedpoint.Zero
|
||||
var askVolume = fixedpoint.Zero
|
||||
var feeUSD = fixedpoint.Zero
|
||||
var grossProfit = fixedpoint.Zero
|
||||
var grossLoss = fixedpoint.Zero
|
||||
|
||||
if len(trades) == 0 {
|
||||
return &AverageCostPnlReport{
|
||||
|
@ -64,6 +66,12 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
|||
totalNetProfit = totalNetProfit.Add(netProfit)
|
||||
}
|
||||
|
||||
if profit.Sign() > 0 {
|
||||
grossProfit = grossProfit.Add(profit)
|
||||
} else if profit.Sign() < 0 {
|
||||
grossLoss = grossLoss.Add(profit)
|
||||
}
|
||||
|
||||
if trade.IsBuyer {
|
||||
bidVolume = bidVolume.Add(trade.Quantity)
|
||||
} else {
|
||||
|
@ -96,8 +104,12 @@ func (c *AverageCostCalculator) Calculate(symbol string, trades []types.Trade, c
|
|||
Profit: totalProfit,
|
||||
NetProfit: totalNetProfit,
|
||||
UnrealizedProfit: unrealizedProfit,
|
||||
AverageCost: position.AverageCost,
|
||||
FeeInUSD: totalProfit.Sub(totalNetProfit),
|
||||
CurrencyFees: currencyFees,
|
||||
|
||||
GrossProfit: grossProfit,
|
||||
GrossLoss: grossLoss,
|
||||
|
||||
AverageCost: position.AverageCost,
|
||||
FeeInUSD: totalProfit.Sub(totalNetProfit),
|
||||
CurrencyFees: currencyFees,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,14 @@ type AverageCostPnlReport struct {
|
|||
Symbol string `json:"symbol"`
|
||||
Market types.Market `json:"market"`
|
||||
|
||||
NumTrades int `json:"numTrades"`
|
||||
Profit fixedpoint.Value `json:"profit"`
|
||||
NetProfit fixedpoint.Value `json:"netProfit"`
|
||||
UnrealizedProfit fixedpoint.Value `json:"unrealizedProfit"`
|
||||
NumTrades int `json:"numTrades"`
|
||||
Profit fixedpoint.Value `json:"profit"`
|
||||
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"`
|
||||
BuyVolume fixedpoint.Value `json:"buyVolume,omitempty"`
|
||||
SellVolume fixedpoint.Value `json:"sellVolume,omitempty"`
|
||||
|
|
|
@ -27,6 +27,7 @@ type StateRecorder struct {
|
|||
outputDirectory string
|
||||
strategies []Instance
|
||||
writers map[types.CsvFormatter]*tsv.Writer
|
||||
lastLines map[types.CsvFormatter][]string
|
||||
manifests Manifests
|
||||
}
|
||||
|
||||
|
@ -34,6 +35,7 @@ func NewStateRecorder(outputDir string) *StateRecorder {
|
|||
return &StateRecorder{
|
||||
outputDirectory: outputDir,
|
||||
writers: make(map[types.CsvFormatter]*tsv.Writer),
|
||||
lastLines: make(map[types.CsvFormatter][]string),
|
||||
manifests: make(Manifests),
|
||||
}
|
||||
}
|
||||
|
@ -42,11 +44,18 @@ func (r *StateRecorder) Snapshot() (int, error) {
|
|||
var c int
|
||||
for obj, writer := range r.writers {
|
||||
records := obj.CsvRecords()
|
||||
lastLine, hasLastLine := r.lastLines[obj]
|
||||
|
||||
for _, record := range records {
|
||||
if hasLastLine && equalStringSlice(lastLine, record) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := writer.Write(record); err != nil {
|
||||
return c, err
|
||||
}
|
||||
c++
|
||||
r.lastLines[obj] = record
|
||||
}
|
||||
|
||||
writer.Flush()
|
||||
|
@ -129,3 +138,19 @@ func (r *StateRecorder) Close() error {
|
|||
|
||||
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"`
|
||||
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 fixedpoint.Value `json:"totalProfit,omitempty"`
|
||||
TotalUnrealizedProfit fixedpoint.Value `json:"totalUnrealizedProfit,omitempty"`
|
||||
|
||||
TotalGrossProfit fixedpoint.Value `json:"totalGrossProfit,omitempty"`
|
||||
TotalGrossLoss fixedpoint.Value `json:"totalGrossLoss,omitempty"`
|
||||
|
||||
SymbolReports []SessionSymbolReport `json:"symbolReports,omitempty"`
|
||||
|
||||
Manifests Manifests `json:"manifests,omitempty"`
|
||||
|
@ -75,13 +81,21 @@ type SessionSymbolReport struct {
|
|||
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) {
|
||||
color.Green("%s %s PROFIT AND LOSS REPORT", r.Exchange, r.Symbol)
|
||||
color.Green("===============================================")
|
||||
r.PnL.Print()
|
||||
|
||||
initQuoteAsset := inQuoteAsset(r.InitialBalances, r.Market, r.StartPrice)
|
||||
finalQuoteAsset := inQuoteAsset(r.FinalBalances, r.Market, r.LastPrice)
|
||||
initQuoteAsset := r.InitialEquityValue()
|
||||
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("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)
|
||||
}
|
||||
|
||||
// inQuoteAsset converts all balances in quote asset
|
||||
func inQuoteAsset(balances types.BalanceMap, market types.Market, price fixedpoint.Value) fixedpoint.Value {
|
||||
// InQuoteAsset converts all balances in quote asset
|
||||
func InQuoteAsset(balances types.BalanceMap, market types.Market, price fixedpoint.Value) fixedpoint.Value {
|
||||
quote := balances[market.QuoteCurrency]
|
||||
base := balances[market.BaseCurrency]
|
||||
return base.Total().Mul(price).Add(quote.Total())
|
||||
|
|
|
@ -66,6 +66,8 @@ func (e *GeneralOrderExecutor) BindProfitStats(profitStats *types.ProfitStats) {
|
|||
}
|
||||
|
||||
profitStats.AddProfit(*profit)
|
||||
|
||||
Notify(profit)
|
||||
Notify(profitStats)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -340,9 +340,6 @@ var BacktestCmd = &cobra.Command{
|
|||
})
|
||||
|
||||
dumper := backtest.NewKLineDumper(kLineDataDir)
|
||||
defer func() {
|
||||
_ = dumper.Close()
|
||||
}()
|
||||
defer func() {
|
||||
if err := dumper.Close(); err != nil {
|
||||
log.WithError(err).Errorf("kline dumper can not close files")
|
||||
|
@ -496,7 +493,6 @@ var BacktestCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
for _, session := range environ.Sessions() {
|
||||
|
||||
for symbol, trades := range session.Trades {
|
||||
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Trades)
|
||||
if err != nil {
|
||||
|
@ -507,6 +503,10 @@ var BacktestCmd = &cobra.Command{
|
|||
summaryReport.SymbolReports = append(summaryReport.SymbolReports, *symbolReport)
|
||||
summaryReport.TotalProfit = symbolReport.PnL.Profit
|
||||
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
|
||||
if generatingReport {
|
||||
|
|
|
@ -4,12 +4,16 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/data/tsv"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/optimizer"
|
||||
)
|
||||
|
||||
|
@ -17,6 +21,7 @@ func init() {
|
|||
optimizeCmd.Flags().String("optimizer-config", "optimizer.yaml", "config file")
|
||||
optimizeCmd.Flags().String("output", "output", "backtest report output directory")
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -43,6 +48,11 @@ var optimizeCmd = &cobra.Command{
|
|||
return err
|
||||
}
|
||||
|
||||
printTsvFormat, err := cmd.Flags().GetBool("tsv")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputDirectory, err := cmd.Flags().GetString("output")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -104,6 +114,10 @@ var optimizeCmd = &cobra.Command{
|
|||
|
||||
// print metrics JSON to stdout
|
||||
fmt.Println(string(out))
|
||||
} else if printTsvFormat {
|
||||
if err := formatMetricsTsv(metrics, os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for n, values := range metrics {
|
||||
if len(values) == 0 {
|
||||
|
@ -120,3 +134,95 @@ var optimizeCmd = &cobra.Command{
|
|||
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,
|
||||
Locked: b.Locked,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,8 +22,10 @@ type Account struct {
|
|||
Locked fixedpoint.Value `json:"locked"`
|
||||
|
||||
// v3 fields for M wallet
|
||||
Debt fixedpoint.Value `json:"debt"`
|
||||
Interest fixedpoint.Value `json:"interest"`
|
||||
Debt fixedpoint.Value `json:"debt"`
|
||||
Principal fixedpoint.Value `json:"principal"`
|
||||
Borrowed fixedpoint.Value `json:"borrowed"`
|
||||
Interest fixedpoint.Value `json:"interest"`
|
||||
|
||||
// v2 fields
|
||||
FiatCurrency string `json:"fiat_currency"`
|
||||
|
|
|
@ -6,7 +6,7 @@ import "github.com/c9s/requestgen"
|
|||
//go:generate -command PostRequest requestgen -method POST
|
||||
//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 {
|
||||
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
|
||||
|
||||
|
@ -244,7 +244,7 @@ func (c *CreateWalletOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/v3/wallet/:walletType/orders"
|
||||
apiURL := "/api/v3/wallet/:walletType/order"
|
||||
slugs, err := c.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -4,12 +4,12 @@ package indicator
|
|||
|
||||
import ()
|
||||
|
||||
func (A *ATR) OnUpdate(cb func(value float64)) {
|
||||
A.UpdateCallbacks = append(A.UpdateCallbacks, cb)
|
||||
func (inc *ATR) OnUpdate(cb func(value float64)) {
|
||||
inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb)
|
||||
}
|
||||
|
||||
func (A *ATR) EmitUpdate(value float64) {
|
||||
for _, cb := range A.UpdateCallbacks {
|
||||
func (inc *ATR) EmitUpdate(value float64) {
|
||||
for _, cb := range inc.UpdateCallbacks {
|
||||
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) {
|
||||
MergeMigrationsMap(map[int64]*rockhopper.Migration{
|
||||
2: {},
|
||||
3: {},
|
||||
2: &rockhopper.Migration{},
|
||||
3: &rockhopper.Migration{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ func TestGetMigrationsMap(t *testing.T) {
|
|||
|
||||
func TestMergeMigrationsMap(t *testing.T) {
|
||||
MergeMigrationsMap(map[int64]*rockhopper.Migration{
|
||||
2: {},
|
||||
3: {},
|
||||
2: &rockhopper.Migration{},
|
||||
3: &rockhopper.Migration{},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,16 +17,31 @@ import (
|
|||
type MetricValueFunc func(summaryReport *backtest.SummaryReport) fixedpoint.Value
|
||||
|
||||
var TotalProfitMetricValueFunc = func(summaryReport *backtest.SummaryReport) fixedpoint.Value {
|
||||
if summaryReport == nil {
|
||||
return fixedpoint.Zero
|
||||
}
|
||||
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 {
|
||||
Labels []string `json:"labels,omitempty"`
|
||||
Params []interface{} `json:"params,omitempty"`
|
||||
Value fixedpoint.Value `json:"value,omitempty"`
|
||||
// Labels is the labels of the given parameters
|
||||
Labels []string `json:"labels,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{} {
|
||||
|
@ -172,6 +187,7 @@ func (o *GridOptimizer) Run(executor Executor, configJson []byte) (map[string][]
|
|||
|
||||
var valueFunctions = map[string]MetricValueFunc{
|
||||
"totalProfit": TotalProfitMetricValueFunc,
|
||||
"totalVolume": TotalVolume,
|
||||
}
|
||||
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
|
||||
|
||||
for result := range resultsC {
|
||||
for metricName, metricFunc := range valueFunctions {
|
||||
if result.Report == nil {
|
||||
log.Errorf("no summaryReport found for params: %+v", result.Params)
|
||||
}
|
||||
if result.Report == nil {
|
||||
log.Errorf("no summaryReport found for params: %+v", result.Params)
|
||||
continue
|
||||
}
|
||||
|
||||
for metricKey, metricFunc := range valueFunctions {
|
||||
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()
|
||||
metrics[metricName] = append(metrics[metricName], Metric{
|
||||
|
||||
metrics[metricKey] = append(metrics[metricKey], Metric{
|
||||
Params: result.Params,
|
||||
Labels: result.Labels,
|
||||
Key: metricKey,
|
||||
Value: metricValue,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -123,7 +123,8 @@ func (s *Strategy) checkAndBorrow(ctx context.Context) {
|
|||
minMarginLevel := s.MinMarginLevel
|
||||
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.MarginRatio.String(),
|
||||
account.MarginTolerance.String(),
|
||||
|
@ -280,7 +281,8 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
|||
return
|
||||
}
|
||||
|
||||
if s.ExchangeSession.GetAccount().MarginLevel.Compare(s.MinMarginLevel) > 0 {
|
||||
account := s.ExchangeSession.GetAccount()
|
||||
if account.MarginLevel.Compare(s.MinMarginLevel) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -291,7 +293,6 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
|||
return
|
||||
}
|
||||
|
||||
account := s.ExchangeSession.GetAccount()
|
||||
minMarginLevel := s.MinMarginLevel
|
||||
curMarginLevel := account.MarginLevel
|
||||
|
||||
|
@ -300,7 +301,11 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
|||
return
|
||||
}
|
||||
|
||||
toRepay := b.Available
|
||||
toRepay := fixedpoint.Min(b.Borrowed, b.Available)
|
||||
if toRepay.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
bbgo.Notify(&MarginAction{
|
||||
Exchange: s.ExchangeSession.ExchangeName,
|
||||
Action: "Repay",
|
||||
|
@ -309,6 +314,7 @@ func (s *Strategy) handleBinanceBalanceUpdateEvent(event *binance.BalanceUpdateE
|
|||
MarginLevel: curMarginLevel,
|
||||
MinMarginLevel: minMarginLevel,
|
||||
})
|
||||
|
||||
if err := s.marginBorrowRepay.RepayMarginAsset(context.Background(), event.Asset, toRepay); err != nil {
|
||||
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
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
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
|
||||
|
||||
marginBorrowRepay, ok := session.Exchange.(types.MarginBorrowRepayService)
|
||||
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
|
||||
|
|
|
@ -40,6 +40,7 @@ type BreakLow struct {
|
|||
|
||||
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: types.Interval1m})
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -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
|
||||
if lastKLine != nil {
|
||||
s.findNextResistancePriceAndPlaceOrders(lastKLine.Close)
|
||||
s.updateResistanceOrders(lastKLine.Close)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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:]
|
||||
}
|
||||
|
||||
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()
|
||||
groupDistance := s.GroupDistance.Float64()
|
||||
resistancePrices := findPossibleResistancePrices(closePrice.Float64()*(1.0+minDistance), groupDistance, tail(s.resistancePivot.Lows, 6))
|
||||
|
||||
if len(resistancePrices) == 0 {
|
||||
return false
|
||||
}
|
||||
|
@ -88,9 +91,6 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value)
|
|||
|
||||
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() {
|
||||
s.currentResistancePrice = nextResistancePrice
|
||||
return true
|
||||
|
@ -99,9 +99,8 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value)
|
|||
// if the current sell price is out-dated
|
||||
// or
|
||||
// the next resistance is lower than the current one.
|
||||
currentSellPrice := s.currentResistancePrice.Mul(one.Add(s.Ratio))
|
||||
if closePrice.Compare(currentSellPrice) > 0 ||
|
||||
nextResistancePrice.Compare(currentSellPrice) < 0 {
|
||||
minPriceToUpdate := s.currentResistancePrice.Mul(one.Add(s.MinDistance))
|
||||
if closePrice.Compare(minPriceToUpdate) > 0 || nextResistancePrice.Compare(s.currentResistancePrice) < 0 {
|
||||
s.currentResistancePrice = nextResistancePrice
|
||||
return true
|
||||
}
|
||||
|
@ -109,11 +108,11 @@ func (s *ResistanceShort) updateNextResistancePrice(closePrice fixedpoint.Value)
|
|||
return false
|
||||
}
|
||||
|
||||
func (s *ResistanceShort) findNextResistancePriceAndPlaceOrders(closePrice fixedpoint.Value) {
|
||||
func (s *ResistanceShort) updateResistanceOrders(closePrice fixedpoint.Value) {
|
||||
ctx := context.Background()
|
||||
resistanceUpdated := s.updateNextResistancePrice(closePrice)
|
||||
resistanceUpdated := s.updateCurrentResistancePrice(closePrice)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -151,8 +150,7 @@ func (s *ResistanceShort) placeResistanceOrders(ctx context.Context, resistanceP
|
|||
|
||||
spread := layerSpread.Mul(fixedpoint.NewFromInt(int64(i)))
|
||||
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())
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
|
@ -18,10 +17,9 @@ import (
|
|||
|
||||
const ID = "supertrend"
|
||||
|
||||
const stateKey = "state-v1"
|
||||
|
||||
var log = logrus.WithField("strategy", ID)
|
||||
|
||||
// TODO: limit order for ATR TP
|
||||
func init() {
|
||||
// Register the pointer of the strategy struct,
|
||||
// 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 {
|
||||
*bbgo.Persistence
|
||||
|
||||
Environment *bbgo.Environment
|
||||
session *bbgo.ExchangeSession
|
||||
Market types.Market
|
||||
|
||||
// persistence fields
|
||||
Position *types.Position `json:"position,omitempty" persistence:"position"`
|
||||
ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_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{}
|
||||
Position *types.Position `persistence:"position"`
|
||||
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
||||
TradeStats *types.TradeStats `persistence:"trade_stats"`
|
||||
|
||||
// Symbol is the market symbol you want to trade
|
||||
Symbol string `json:"symbol"`
|
||||
|
||||
// Interval is how long do you want to update your order price and quantity
|
||||
Interval types.Interval `json:"interval"`
|
||||
types.IntervalWindow
|
||||
|
||||
// Double DEMA
|
||||
doubleDema *DoubleDema
|
||||
// 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
|
||||
|
||||
// SuperTrend indicator
|
||||
// SuperTrend SuperTrend `json:"superTrend"`
|
||||
Supertrend *indicator.Supertrend
|
||||
// SupertrendWindow ATR window for calculation of supertrend
|
||||
SupertrendWindow int `json:"supertrendWindow"`
|
||||
// SupertrendMultiplier ATR multiplier for calculation of supertrend
|
||||
SupertrendMultiplier float64 `json:"supertrendMultiplier"`
|
||||
|
||||
// LinearRegression Use linear regression as trend confirmation
|
||||
LinearRegression *LinGre `json:"linearRegression,omitempty"`
|
||||
|
||||
// Leverage
|
||||
Leverage float64 `json:"leverage"`
|
||||
|
||||
// TakeProfitMultiplier TP according to ATR multiple, 0 to disable this
|
||||
TakeProfitMultiplier float64 `json:"takeProfitMultiplier"`
|
||||
// TakeProfitAtrMultiplier TP according to ATR multiple, 0 to disable this
|
||||
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"`
|
||||
|
||||
// TPSLBySignal TP/SL by reversed signals
|
||||
TPSLBySignal bool `json:"tpslBySignal"`
|
||||
// StopByReversedSupertrend TP/SL by reversed supertrend signal
|
||||
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
|
||||
currentStopLossPrice fixedpoint.Value
|
||||
|
||||
|
@ -105,7 +103,7 @@ func (s *Strategy) Validate() error {
|
|||
return errors.New("interval is required")
|
||||
}
|
||||
|
||||
if s.Leverage == 0.0 {
|
||||
if s.Leverage <= 0.0 {
|
||||
return errors.New("leverage is required")
|
||||
}
|
||||
|
||||
|
@ -114,6 +112,7 @@ func (s *Strategy) Validate() error {
|
|||
|
||||
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.LinearRegression.Interval})
|
||||
}
|
||||
|
||||
// Position control
|
||||
|
@ -141,8 +140,7 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
|||
|
||||
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)
|
||||
bbgo.Notify("submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, orderForm)
|
||||
|
||||
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
||||
if err != nil {
|
||||
|
@ -153,43 +151,88 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu
|
|||
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
|
||||
func (s *Strategy) setupIndicators() {
|
||||
if s.FastDEMAWindow == 0 {
|
||||
s.FastDEMAWindow = 144
|
||||
}
|
||||
s.fastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FastDEMAWindow}}
|
||||
// K-line store for indicators
|
||||
kLineStore, _ := s.session.MarketDataStore(s.Symbol)
|
||||
|
||||
if s.SlowDEMAWindow == 0 {
|
||||
s.SlowDEMAWindow = 169
|
||||
}
|
||||
s.slowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}}
|
||||
// Double DEMA
|
||||
s.doubleDema = newDoubleDema(kLineStore, s.Interval, s.FastDEMAWindow, s.SlowDEMAWindow)
|
||||
|
||||
if s.SupertrendWindow == 0 {
|
||||
s.SupertrendWindow = 39
|
||||
// Supertrend
|
||||
if s.Window == 0 {
|
||||
s.Window = 39
|
||||
}
|
||||
if s.SupertrendMultiplier == 0 {
|
||||
s.SupertrendMultiplier = 3
|
||||
}
|
||||
s.Supertrend = &indicator.Supertrend{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}, ATRMultiplier: s.SupertrendMultiplier}
|
||||
s.Supertrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SupertrendWindow, Interval: s.Interval}}
|
||||
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.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) updateIndicators(kline types.KLine) {
|
||||
closePrice := kline.GetClose().Float64()
|
||||
func (s *Strategy) shouldStop(kline types.KLine, stSignal types.Direction, demaSignal types.Direction, lgSignal types.Direction) bool {
|
||||
stopNow := false
|
||||
base := s.Position.GetBase()
|
||||
baseSign := base.Sign()
|
||||
|
||||
// Update indicators
|
||||
if kline.Interval == s.fastDEMA.Interval {
|
||||
s.fastDEMA.Update(closePrice)
|
||||
if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) {
|
||||
// SL by triggering Kline low/high
|
||||
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)
|
||||
}
|
||||
if kline.Interval == s.Supertrend.Interval {
|
||||
s.Supertrend.Update(kline.GetHigh().Float64(), kline.GetLow().Float64(), closePrice)
|
||||
|
||||
return stopNow
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -200,7 +243,6 @@ func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Va
|
|||
Type: types.OrderTypeMarket,
|
||||
Quantity: quantity,
|
||||
MarginSideEffect: marginOrderSideEffect,
|
||||
GroupID: s.groupID,
|
||||
}
|
||||
|
||||
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 {
|
||||
s.session = session
|
||||
|
||||
s.currentStopLossPrice = fixedpoint.Zero
|
||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||
|
||||
// calculate group id for orders
|
||||
instanceID := s.InstanceID()
|
||||
s.groupID = util.FNV32(instanceID)
|
||||
|
||||
// If position is nil, we need to allocate a new position for calculation
|
||||
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.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
|
||||
if s.session.MakerFeeRate.Sign() > 0 || s.session.TakerFeeRate.Sign() > 0 {
|
||||
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
|
||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
||||
s.orderExecutor.BindEnvironment(s.Environment)
|
||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
||||
s.orderExecutor.BindTradeStats(s.TradeStats)
|
||||
s.orderExecutor.Bind()
|
||||
|
||||
// Sync position to redis on trade
|
||||
|
@ -259,16 +308,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
bbgo.Sync(s)
|
||||
})
|
||||
|
||||
s.stopC = make(chan struct{})
|
||||
|
||||
// StrategyController
|
||||
s.Status = types.StrategyStatusRunning
|
||||
|
||||
s.OnSuspend(func() {
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
_ = s.Persistence.Sync(s)
|
||||
bbgo.Sync(s)
|
||||
})
|
||||
|
||||
s.OnEmergencyStop(func() {
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
// Close 100% position
|
||||
|
@ -278,108 +323,77 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
// Setup indicators
|
||||
s.setupIndicators()
|
||||
|
||||
s.currentStopLossPrice = fixedpoint.Zero
|
||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||
// Exit methods
|
||||
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
|
||||
if s.Status != types.StrategyStatusRunning {
|
||||
return
|
||||
}
|
||||
|
||||
// skip k-lines from other symbols or other intervals
|
||||
if kline.Symbol != s.Symbol || kline.Interval != s.Interval {
|
||||
return
|
||||
}
|
||||
closePrice := kline.GetClose()
|
||||
openPrice := kline.GetOpen()
|
||||
closePrice64 := closePrice.Float64()
|
||||
openPrice64 := openPrice.Float64()
|
||||
|
||||
// Update indicators
|
||||
s.updateIndicators(kline)
|
||||
|
||||
// Get signals
|
||||
closePrice := kline.GetClose().Float64()
|
||||
openPrice := kline.GetOpen().Float64()
|
||||
// Supertrend signal
|
||||
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()) {
|
||||
demaSignal = types.DirectionUp
|
||||
} else if closePrice < s.fastDEMA.Last() && closePrice < s.slowDEMA.Last() && !(openPrice < s.fastDEMA.Last() && openPrice < s.slowDEMA.Last()) {
|
||||
demaSignal = types.DirectionDown
|
||||
} else {
|
||||
demaSignal = types.DirectionNone
|
||||
|
||||
// DEMA signal
|
||||
demaSignal := s.doubleDema.getDemaSignal(openPrice64, closePrice64)
|
||||
|
||||
// Linear Regression signal
|
||||
var lgSignal types.Direction
|
||||
if s.LinearRegression != nil {
|
||||
lgSignal = s.LinearRegression.GetSignal()
|
||||
}
|
||||
|
||||
base := s.Position.GetBase()
|
||||
baseSign := base.Sign()
|
||||
// TP/SL if there's non-dust position and meets the criteria
|
||||
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
|
||||
if !s.Market.IsDustQuantity(base.Abs(), kline.GetClose()) {
|
||||
if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) {
|
||||
// SL by triggering Kline low
|
||||
log.Infof("%s SL by triggering Kline low", s.Symbol)
|
||||
bbgo.Notify("%s StopLoss by triggering the kline low", s.Symbol)
|
||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
||||
s.currentStopLossPrice = fixedpoint.Zero
|
||||
s.currentTakeProfitPrice = fixedpoint.Zero
|
||||
}
|
||||
} else if s.TakeProfitMultiplier > 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
|
||||
log.Infof("%s TP by multiple of ATR", s.Symbol)
|
||||
bbgo.Notify("%s TakeProfit by multiple of ATR", s.Symbol)
|
||||
if err := s.ClosePosition(ctx, fixedpoint.One); err == nil {
|
||||
s.currentStopLossPrice = fixedpoint.Zero
|
||||
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
|
||||
}
|
||||
}
|
||||
// Get order side
|
||||
side := s.getSide(stSignal, demaSignal, lgSignal)
|
||||
// Set TP/SL price if needed
|
||||
if side == types.SideTypeBuy {
|
||||
if s.StopLossByTriggeringK {
|
||||
s.currentStopLossPrice = kline.GetLow()
|
||||
}
|
||||
if s.TakeProfitAtrMultiplier > 0 {
|
||||
s.currentTakeProfitPrice = closePrice.Add(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier))
|
||||
}
|
||||
} else if side == types.SideTypeSell {
|
||||
if s.StopLossByTriggeringK {
|
||||
s.currentStopLossPrice = kline.GetHigh()
|
||||
}
|
||||
if s.TakeProfitAtrMultiplier > 0 {
|
||||
s.currentTakeProfitPrice = closePrice.Sub(fixedpoint.NewFromFloat(s.Supertrend.AverageTrueRange.Last() * s.TakeProfitAtrMultiplier))
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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)
|
||||
// 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()) {
|
||||
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)
|
||||
_ = s.ClosePosition(ctx, fixedpoint.One)
|
||||
} 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)
|
||||
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)
|
||||
_, err := s.orderExecutor.SubmitOrders(ctx, orderForm)
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
// Graceful shutdown
|
||||
bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
close(s.stopC)
|
||||
|
||||
_ = s.orderExecutor.GracefulCancel(ctx)
|
||||
_, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String())
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -94,6 +94,10 @@ type SeriesExtend interface {
|
|||
Covariance(b Series, length int) float64
|
||||
Correlation(b Series, length int, method ...CorrFunc) float64
|
||||
Rank(length int) SeriesExtend
|
||||
Sigmoid() SeriesExtend
|
||||
Softmax(window int) SeriesExtend
|
||||
Entropy(window int) float64
|
||||
CrossEntropy(b Series, window int) float64
|
||||
}
|
||||
|
||||
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])
|
||||
// otherwise will operate on all elements
|
||||
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)
|
||||
|
@ -881,4 +947,175 @@ func Rolling(a Series, window int) *RollingResult {
|
|||
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
|
||||
|
|
|
@ -2,6 +2,7 @@ package types
|
|||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gonum.org/v1/gonum/stat"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -84,3 +85,62 @@ func TestSkew(t *testing.T) {
|
|||
sk := Skew(&a, 4)
|
||||
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 {
|
||||
return func(k KLine) {
|
||||
if k.Symbol != symbol || k.Interval != interval {
|
||||
if k.Symbol != symbol || (k.Interval != "" && k.Interval != interval) {
|
||||
return
|
||||
}
|
||||
callback(k)
|
||||
|
|
|
@ -124,3 +124,19 @@ func (s *SeriesBase) Correlation(b Series, length int, method ...CorrFunc) float
|
|||
func (s *SeriesBase) Rank(length int) SeriesExtend {
|
||||
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 (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
@ -11,7 +12,7 @@ import (
|
|||
func FilterSimpleArgs(args []interface{}) (simpleArgs []interface{}) {
|
||||
for _, arg := range args {
|
||||
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)
|
||||
default:
|
||||
rt := reflect.TypeOf(arg)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//go:build !release
|
||||
// +build !release
|
||||
|
||||
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
|
||||
|
||||
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