move files

This commit is contained in:
c9s 2020-07-11 15:18:31 +08:00
parent 1c99ca52cb
commit a3fa292dc5
20 changed files with 467 additions and 681 deletions

View File

@ -1,7 +1,7 @@
package bbgo
import (
"github.com/adshao/go-binance"
types2 "github.com/c9s/bbgo/pkg/bbgo/types"
"math"
)
@ -22,10 +22,10 @@ func SellVolumeModifier(price float64) float64 {
return math.Min(2, math.Exp((price-targetPrice)/flatness))
}
func VolumeByPriceChange(market Market, currentPrice float64, change float64, side binance.SideType) float64 {
func VolumeByPriceChange(market Market, currentPrice float64, change float64, side types2.SideType) float64 {
volume := BaseVolumeByPriceChange(change)
if side == binance.SideTypeSell {
if side == types2.SideTypeSell {
volume *= SellVolumeModifier(currentPrice)
} else {
volume *= BuyVolumeModifier(currentPrice)

25
bbgo/config/config.go Normal file
View File

@ -0,0 +1,25 @@
package config
import (
"encoding/json"
"io/ioutil"
)
func LoadConfigFile(filename string, v interface{}) error {
data, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
return json.Unmarshal(data, v)
}
func SaveConfigFile(filename string, v interface{}) error {
out, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filename, out, 0644)
}

View File

@ -1,6 +1,6 @@
package bbgo
import "github.com/c9s/bbgo/pkg/types"
import "github.com/c9s/bbgo/pkg/bbgo/types"
type TradingContext struct {
KLineWindowSize int

View File

@ -3,7 +3,8 @@ package binance
import (
"context"
"github.com/adshao/go-binance"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/bbgo/types"
types2 "github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/sirupsen/logrus"
"strconv"
@ -44,7 +45,7 @@ func (e *Exchange) NewPrivateStream(ctx context.Context) (*PrivateStream, error)
}, nil
}
func (e *Exchange) SubmitOrder(ctx context.Context, order *types.Order) error {
func (e *Exchange) SubmitOrder(ctx context.Context, order *types2.Order) error {
/*
limit order example
@ -60,7 +61,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order *types.Order) error {
req := e.Client.NewCreateOrderService().
Symbol(order.Symbol).
Side(order.Side).
Side(binance.SideType(order.Side)).
Type(order.Type).
Quantity(order.VolumeStr)
@ -103,7 +104,7 @@ func (e *Exchange) QueryKLines(ctx context.Context, symbol, interval string, lim
return kLines, nil
}
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, startTime time.Time) (trades []types.Trade, err error) {
func (e *Exchange) QueryTrades(ctx context.Context, symbol string, startTime time.Time) (trades []types2.Trade, err error) {
logrus.Infof("[binance] querying %s trades from %s", symbol, startTime)
var lastTradeID int64 = 0
@ -159,7 +160,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, startTime tim
return nil, err
}
trades = append(trades, types.Trade{
trades = append(trades, types2.Trade{
ID: t.ID,
Price: price,
Volume: quantity,

View File

@ -4,7 +4,8 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/c9s/bbgo/pkg/types"
types2 "github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/valyala/fastjson"
"time"
@ -83,13 +84,13 @@ type ExecutionReportEvent struct {
OrderCreationTime int `json:"O"`
}
func (e *ExecutionReportEvent) Trade() (*types.Trade, error) {
func (e *ExecutionReportEvent) Trade() (*types2.Trade, error) {
if e.CurrentExecutionType != "TRADE" {
return nil, errors.New("execution report is not a trade")
}
tt := time.Unix(0, e.TransactionTime/1000000)
return &types.Trade{
return &types2.Trade{
ID: e.TradeID,
Symbol: e.Symbol,
Price: util.MustParseFloat(e.LastExecutedPrice),

View File

@ -1,235 +0,0 @@
package bbgo
import (
"fmt"
"github.com/adshao/go-binance"
"github.com/c9s/bbgo/pkg/bbgo/types"
binance2 "github.com/c9s/bbgo/pkg/exchange/binance"
types2 "github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/slack-go/slack"
"math"
"strconv"
)
const epsilon = 0.0000001
func NotZero(v float64) bool {
return math.Abs(v) > epsilon
}
type KLineDetector struct {
Name string `json:"name"`
Interval string `json:"interval"`
// MinMaxPriceChange is the minimal max price change trigger
MinMaxPriceChange float64 `json:"minMaxPriceChange"`
// MaxMaxPriceChange is the max - max price change trigger
MaxMaxPriceChange float64 `json:"maxMaxPriceChange"`
EnableMinThickness bool `json:"enableMinThickness"`
MinThickness float64 `json:"minThickness"`
EnableMaxShadowRatio bool `json:"enableMaxShadowRatio"`
MaxShadowRatio float64 `json:"maxShadowRatio"`
EnableLookBack bool `json:"enableLookBack"`
LookBackFrames int `json:"lookBackFrames"`
MinProfitPriceTick float64 `json:"minProfitPriceTick"`
DelayMilliseconds int `json:"delayMsec"`
Stop bool `json:"stop"`
}
func (d *KLineDetector) SlackAttachment() slack.Attachment {
var name = "Detector "
if len(d.Name) > 0 {
name += " " + d.Name
}
name += fmt.Sprintf(" %s", d.Interval)
if d.EnableLookBack {
name += fmt.Sprintf(" x %d", d.LookBackFrames)
}
var maxPriceChangeRange = fmt.Sprintf("%.2f ~ NO LIMIT", d.MinMaxPriceChange)
if NotZero(d.MaxMaxPriceChange) {
maxPriceChangeRange = fmt.Sprintf("%.2f ~ %.2f", d.MinMaxPriceChange, d.MaxMaxPriceChange)
}
name += " MaxMaxPriceChange " + maxPriceChangeRange
var fields = []slack.AttachmentField{
{
Title: "Interval",
Value: d.Interval,
Short: true,
},
}
if d.EnableMinThickness && NotZero(d.MinThickness) {
fields = append(fields, slack.AttachmentField{
Title: "MinThickness",
Value: util.FormatFloat(d.MinThickness, 4),
Short: true,
})
}
if d.EnableMaxShadowRatio && NotZero(d.MaxShadowRatio) {
fields = append(fields, slack.AttachmentField{
Title: "MaxShadowRatio",
Value: util.FormatFloat(d.MaxShadowRatio, 4),
Short: true,
})
}
if d.EnableLookBack {
fields = append(fields, slack.AttachmentField{
Title: "LookBackFrames",
Value: strconv.Itoa(d.LookBackFrames),
Short: true,
})
}
return slack.Attachment{
Color: "",
Fallback: "",
ID: 0,
Title: name,
Pretext: "",
Text: "",
Fields: fields,
Footer: "",
FooterIcon: "",
Ts: "",
}
}
func (d *KLineDetector) String() string {
var name = fmt.Sprintf("Detector %s (%f < x < %f)", d.Interval, d.MinMaxPriceChange, d.MaxMaxPriceChange)
if d.EnableMinThickness {
name += fmt.Sprintf(" [MinThickness: %f]", d.MinThickness)
}
if d.EnableLookBack {
name += fmt.Sprintf(" [LookBack: %d]", d.LookBackFrames)
}
if d.EnableMaxShadowRatio {
name += fmt.Sprintf(" [MaxShadowRatio: %f]", d.MaxShadowRatio)
}
return name
}
func (d *KLineDetector) NewOrder(e *binance2.KLineEvent, tradingCtx *TradingContext) *types2.Order {
var kline types.KLineOrWindow = e.KLine
if d.EnableLookBack {
klineWindow := tradingCtx.KLineWindows[e.KLine.Interval]
if len(klineWindow) >= d.LookBackFrames {
kline = klineWindow.Tail(d.LookBackFrames)
}
}
var trend = kline.GetTrend()
var side binance.SideType
if trend < 0 {
side = binance.SideTypeBuy
} else if trend > 0 {
side = binance.SideTypeSell
}
var volume = tradingCtx.Market.FormatVolume(VolumeByPriceChange(tradingCtx.Market, kline.GetClose(), kline.GetChange(), side))
return &types2.Order{
Symbol: e.KLine.Symbol,
Type: binance.OrderTypeMarket,
Side: side,
VolumeStr: volume,
}
}
func (d *KLineDetector) Detect(e *binance2.KLineEvent, tradingCtx *TradingContext) (reason string, kline types.KLineOrWindow, ok bool) {
kline = e.KLine
// if the 3m trend is drop, do not buy, let 5m window handle it.
if d.EnableLookBack {
klineWindow := tradingCtx.KLineWindows[e.KLine.Interval]
if len(klineWindow) >= d.LookBackFrames {
kline = klineWindow.Tail(d.LookBackFrames)
}
/*
if lookbackKline.AllDrop() {
trader.Infof("1m window all drop down (%d frames), do not buy: %+v", d.LookBackFrames, klineWindow)
} else if lookbackKline.AllRise() {
trader.Infof("1m window all rise up (%d frames), do not sell: %+v", d.LookBackFrames, klineWindow)
}
*/
}
var maxChange = math.Abs(kline.GetMaxChange())
if maxChange < d.MinMaxPriceChange {
return "", kline, false
}
if NotZero(d.MaxMaxPriceChange) && maxChange > d.MaxMaxPriceChange {
return fmt.Sprintf("exceeded max price change %.4f > %.4f", maxChange, d.MaxMaxPriceChange), kline, false
}
if d.EnableMinThickness {
if kline.GetThickness() < d.MinThickness {
return fmt.Sprintf("kline too thin. %.4f < min kline thickness %.4f", kline.GetThickness(), d.MinThickness), kline, false
}
}
var trend = kline.GetTrend()
if d.EnableMaxShadowRatio {
if trend > 0 {
if kline.GetUpperShadowRatio() > d.MaxShadowRatio {
return fmt.Sprintf("kline upper shadow ratio too high. %.4f > %.4f (MaxShadowRatio)", kline.GetUpperShadowRatio(), d.MaxShadowRatio), kline, false
}
} else if trend < 0 {
if kline.GetLowerShadowRatio() > d.MaxShadowRatio {
return fmt.Sprintf("kline lower shadow ratio too high. %.4f > %.4f (MaxShadowRatio)", kline.GetLowerShadowRatio(), d.MaxShadowRatio), kline, false
}
}
}
if trend > 0 && kline.BounceUp() { // trend up, ignore bounce up
return fmt.Sprintf("bounce up, do not sell, kline mid: %.4f", kline.Mid()), kline, false
} else if trend < 0 && kline.BounceDown() { // trend down, ignore bounce down
return fmt.Sprintf("bounce down, do not buy, kline mid: %.4f", kline.Mid()), kline, false
}
if NotZero(d.MinProfitPriceTick) {
// do not buy too early if it's greater than the average bid price + min profit tick
if trend < 0 && kline.GetClose() > (tradingCtx.AverageBidPrice-d.MinProfitPriceTick) {
return fmt.Sprintf("price %f is greater than the average price + min profit tick %f", kline.GetClose(), tradingCtx.AverageBidPrice - d.MinProfitPriceTick), kline, false
}
// do not sell too early if it's less than the average bid price + min profit tick
if trend > 0 && kline.GetClose() < (tradingCtx.AverageBidPrice+d.MinProfitPriceTick) {
return fmt.Sprintf("price %f is less than the average price + min profit tick %f", kline.GetClose(), tradingCtx.AverageBidPrice + d.MinProfitPriceTick), kline, false
}
}
/*
if toPrice(kline.GetClose()) == toPrice(kline.GetLow()) {
return fmt.Sprintf("close near the lowest price, the price might continue to drop."), false
}
*/
return "", kline, true
}

View File

@ -1,2 +0,0 @@
package bbgo

View File

@ -1,7 +1,7 @@
package bbgo
import (
"github.com/c9s/bbgo/pkg/types"
types2 "github.com/c9s/bbgo/pkg/bbgo/types"
log "github.com/sirupsen/logrus"
"strings"
"time"
@ -11,12 +11,12 @@ type ProfitAndLossCalculator struct {
Symbol string
StartTime time.Time
CurrentPrice float64
Trades []types.Trade
Trades []types2.Trade
CurrencyPrice map[string]float64
}
func (c *ProfitAndLossCalculator) AddTrade(trade types.Trade) {
func (c *ProfitAndLossCalculator) AddTrade(trade types2.Trade) {
c.Trades = append(c.Trades, trade)
}

View File

@ -3,9 +3,9 @@ package bbgo
import (
"context"
"fmt"
"github.com/c9s/bbgo/pkg/exchange/binance"
"github.com/c9s/bbgo/pkg/bbgo/exchange/binance"
types2 "github.com/c9s/bbgo/pkg/bbgo/types"
"github.com/c9s/bbgo/pkg/slack/slackstyle"
"github.com/c9s/bbgo/pkg/types"
"github.com/c9s/bbgo/pkg/util"
"github.com/leekchan/accounting"
"github.com/sirupsen/logrus"
@ -76,7 +76,7 @@ func (t *Trader) Errorf(err error, format string, args ...interface{}) {
}
}
func (t *Trader) ReportTrade(e *binance.ExecutionReportEvent, trade *types.Trade) {
func (t *Trader) ReportTrade(e *binance.ExecutionReportEvent, trade *types2.Trade) {
var color = ""
if trade.IsBuyer {
color = "#228B22"
@ -171,7 +171,7 @@ func (t *Trader) ReportPnL() {
}
}
func (t *Trader) SubmitOrder(ctx context.Context, order *types.Order) {
func (t *Trader) SubmitOrder(ctx context.Context, order *types2.Order) {
t.Infof(":memo: Submitting %s order on side %s with volume: %s", order.Type, order.Side, order.VolumeStr, order.SlackAttachment())
err := t.Exchange.SubmitOrder(ctx, order)

View File

@ -2,13 +2,6 @@ package types
import "time"
type Trade interface {
GetPrice() float64
GetVolume() float64
GetFeeCurrency() string
GetFeeAmount() float64
}
type Exchange interface {
QueryKLines(interval string, startFrom time.Time, endTo time.Time) []KLineOrWindow
QueryTrades(symbol string, startFrom time.Time) []Trade

View File

@ -1,6 +1,11 @@
package types
import "github.com/slack-go/slack"
import (
"fmt"
"github.com/c9s/bbgo/pkg/util"
"github.com/slack-go/slack"
"math"
)
type KLineOrWindow interface {
GetTrend() int
@ -22,3 +27,408 @@ type KLineOrWindow interface {
SlackAttachment() slack.Attachment
}
// KLine uses binance's kline as the standard structure
type KLine struct {
StartTime int64 `json:"t"`
EndTime int64 `json:"T"`
Symbol string `json:"s"`
Interval string `json:"i"`
Open string `json:"o"`
Close string `json:"c"`
High string `json:"h"`
Low string `json:"l"`
Volume string `json:"V"` // taker buy base asset volume (like 10 BTC)
QuoteVolume string `json:"Q"` // taker buy quote asset volume (like 1000USDT)
LastTradeID int `json:"L"`
NumberOfTrades int64 `json:"n"`
Closed bool `json:"x"`
}
func (k KLine) Mid() float64 {
return (k.GetHigh() + k.GetLow()) / 2
}
// green candle with open and close near high price
func (k KLine) BounceUp() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() > mid && k.GetClose() > mid
}
// red candle with open and close near low price
func (k KLine) BounceDown() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() < mid && k.GetClose() < mid
}
func (k KLine) GetTrend() int {
o := k.GetOpen()
c := k.GetClose()
if c > o {
return 1
} else if c < o {
return -1
}
return 0
}
func (k KLine) GetHigh() float64 {
return util.MustParseFloat(k.High)
}
func (k KLine) GetLow() float64 {
return util.MustParseFloat(k.Low)
}
func (k KLine) GetOpen() float64 {
return util.MustParseFloat(k.Open)
}
func (k KLine) GetClose() float64 {
return util.MustParseFloat(k.Close)
}
func (k KLine) GetMaxChange() float64 {
return k.GetHigh() - k.GetLow()
}
// GetThickness returns the thickness of the kline. 1 => thick, 0.1 => thin
func (k KLine) GetThickness() float64 {
return math.Abs(k.GetChange()) / math.Abs(k.GetMaxChange())
}
func (k KLine) GetUpperShadowRatio() float64 {
return k.GetUpperShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLine) GetUpperShadowHeight() float64 {
high := k.GetHigh()
if k.GetOpen() > k.GetClose() {
return high - k.GetOpen()
}
return high - k.GetClose()
}
func (k KLine) GetLowerShadowRatio() float64 {
return k.GetLowerShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLine) GetLowerShadowHeight() float64 {
low := k.GetLow()
if k.GetOpen() < k.GetClose() {
return k.GetOpen() - low
}
return k.GetClose() - low
}
// GetBody returns the height of the candle real body
func (k KLine) GetBody() float64 {
return k.GetChange()
}
func (k KLine) GetChange() float64 {
return k.GetClose() - k.GetOpen()
}
func (k KLine) String() string {
return fmt.Sprintf("%s %s Open: % 14s Close: % 14s High: % 14s Low: % 14s Volume: % 15s Change: % 11f Max Change: % 11f", k.Symbol, k.Interval, k.Open, k.Close, k.High, k.Low, k.Volume, k.GetChange(), k.GetMaxChange())
}
func (k KLine) Color() string {
if k.GetTrend() > 0 {
return Green
} else if k.GetTrend() < 0 {
return Red
}
return "#f0f0f0"
}
func (k KLine) SlackAttachment() slack.Attachment {
return slack.Attachment{
Text: "KLine",
Color: k.Color(),
Fields: []slack.AttachmentField{
{
Title: "Open",
Value: k.Open,
Short: true,
},
{
Title: "Close",
Value: k.Close,
Short: true,
},
{
Title: "High",
Value: k.High,
Short: true,
},
{
Title: "Low",
Value: k.Low,
Short: true,
},
{
Title: "Mid",
Value: util.FormatFloat(k.Mid(), 2),
Short: true,
},
{
Title: "Change",
Value: util.FormatFloat(k.GetChange(), 2),
Short: true,
},
{
Title: "Max Change",
Value: util.FormatFloat(k.GetMaxChange(), 2),
Short: true,
},
{
Title: "Thickness",
Value: util.FormatFloat(k.GetThickness(), 4),
Short: true,
},
{
Title: "UpperShadowRatio",
Value: util.FormatFloat(k.GetUpperShadowRatio(), 4),
Short: true,
},
{
Title: "LowerShadowRatio",
Value: util.FormatFloat(k.GetLowerShadowRatio(), 4),
Short: true,
},
},
Footer: "",
FooterIcon: "",
}
}
type KLineWindow []KLine
func (k KLineWindow) Len() int {
return len(k)
}
func (k KLineWindow) GetOpen() float64 {
return k[0].GetOpen()
}
func (k KLineWindow) GetClose() float64 {
end := len(k) - 1
return k[end].GetClose()
}
func (k KLineWindow) GetHigh() float64 {
high := k.GetOpen()
for _, line := range k {
val := line.GetHigh()
if val > high {
high = val
}
}
return high
}
func (k KLineWindow) GetLow() float64 {
low := k.GetOpen()
for _, line := range k {
val := line.GetLow()
if val < low {
low = val
}
}
return low
}
func (k KLineWindow) GetChange() float64 {
return k.GetClose() - k.GetOpen()
}
func (k KLineWindow) GetMaxChange() float64 {
return k.GetHigh() - k.GetLow()
}
func (k KLineWindow) AllDrop() bool {
for _, n := range k {
if n.GetTrend() >= 0 {
return false
}
}
return true
}
func (k KLineWindow) AllRise() bool {
for _, n := range k {
if n.GetTrend() <= 0 {
return false
}
}
return true
}
func (k KLineWindow) GetTrend() int {
o := k.GetOpen()
c := k.GetClose()
if c > o {
return 1
} else if c < o {
return -1
}
return 0
}
func (k KLineWindow) Color() string {
if k.GetTrend() > 0 {
return Green
} else if k.GetTrend() < 0 {
return Red
}
return "#f0f0f0"
}
func (k KLineWindow) Mid() float64 {
return k.GetHigh() - k.GetLow()/2
}
// green candle with open and close near high price
func (k KLineWindow) BounceUp() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() > mid && k.GetClose() > mid
}
// red candle with open and close near low price
func (k KLineWindow) BounceDown() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() < mid && k.GetClose() < mid
}
func (k *KLineWindow) Add(line KLine) {
*k = append(*k, line)
}
func (k KLineWindow) Take(size int) KLineWindow {
return k[:size]
}
func (k KLineWindow) Tail(size int) KLineWindow {
if len(k) <= size {
return k[:]
}
return k[len(k)-size:]
}
func (k *KLineWindow) Truncate(size int) {
if len(*k) <= size {
return
}
end := len(*k) - 1
start := end - size
if start < 0 {
start = 0
}
*k = (*k)[end-5 : end]
}
func (k KLineWindow) GetBody() float64 {
return k.GetChange()
}
func (k KLineWindow) GetThickness() float64 {
return math.Abs(k.GetChange()) / math.Abs(k.GetMaxChange())
}
func (k KLineWindow) GetUpperShadowRatio() float64 {
return k.GetUpperShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLineWindow) GetUpperShadowHeight() float64 {
high := k.GetHigh()
if k.GetOpen() > k.GetClose() {
return high - k.GetOpen()
}
return high - k.GetClose()
}
func (k KLineWindow) GetLowerShadowRatio() float64 {
return k.GetLowerShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLineWindow) GetLowerShadowHeight() float64 {
low := k.GetLow()
if k.GetOpen() < k.GetClose() {
return k.GetOpen() - low
}
return k.GetClose() - low
}
func (k KLineWindow) SlackAttachment() slack.Attachment {
return slack.Attachment{
Text: "KLine",
Color: k.Color(),
Fields: []slack.AttachmentField{
{
Title: "Open",
Value: util.FormatFloat(k.GetOpen(), 2),
Short: true,
},
{
Title: "Close",
Value: util.FormatFloat(k.GetClose(), 2),
Short: true,
},
{
Title: "High",
Value: util.FormatFloat(k.GetHigh(), 2),
Short: true,
},
{
Title: "Low",
Value: util.FormatFloat(k.GetLow(), 2),
Short: true,
},
{
Title: "Mid",
Value: util.FormatFloat(k.Mid(), 2),
Short: true,
},
{
Title: "Change",
Value: util.FormatFloat(k.GetChange(), 2),
Short: true,
},
{
Title: "Max Change",
Value: util.FormatFloat(k.GetMaxChange(), 2),
Short: true,
},
{
Title: "Thickness",
Value: util.FormatFloat(k.GetThickness(), 4),
Short: true,
},
{
Title: "UpperShadowRatio",
Value: util.FormatFloat(k.GetUpperShadowRatio(), 4),
Short: true,
},
{
Title: "LowerShadowRatio",
Value: util.FormatFloat(k.GetLowerShadowRatio(), 4),
Short: true,
},
},
Footer: "",
FooterIcon: "",
}
}

View File

@ -7,7 +7,7 @@ import (
type Order struct {
Symbol string
Side binance.SideType
Side SideType
Type binance.OrderType
VolumeStr string
PriceStr string
@ -27,7 +27,7 @@ func (o *Order) SlackAttachment() slack.Attachment {
}
return slack.Attachment{
Color: SideToColorName(SideType(o.Side)),
Color: SideToColorName(o.Side),
Title: string(o.Type) + " Order " + string(o.Side),
// Text: "",
Fields: fields,

View File

@ -1,414 +0,0 @@
package types
import (
"fmt"
"github.com/c9s/bbgo/pkg/util"
"github.com/slack-go/slack"
"math"
)
// KLine uses binance's kline as the standard structure
type KLine struct {
StartTime int64 `json:"t"`
EndTime int64 `json:"T"`
Symbol string `json:"s"`
Interval string `json:"i"`
Open string `json:"o"`
Close string `json:"c"`
High string `json:"h"`
Low string `json:"l"`
Volume string `json:"V"` // taker buy base asset volume (like 10 BTC)
QuoteVolume string `json:"Q"` // taker buy quote asset volume (like 1000USDT)
LastTradeID int `json:"L"`
NumberOfTrades int64 `json:"n"`
Closed bool `json:"x"`
}
func (k KLine) Mid() float64 {
return (k.GetHigh() + k.GetLow()) / 2
}
// green candle with open and close near high price
func (k KLine) BounceUp() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() > mid && k.GetClose() > mid
}
// red candle with open and close near low price
func (k KLine) BounceDown() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() < mid && k.GetClose() < mid
}
func (k KLine) GetTrend() int {
o := k.GetOpen()
c := k.GetClose()
if c > o {
return 1
} else if c < o {
return -1
}
return 0
}
func (k KLine) GetHigh() float64 {
return util.MustParseFloat(k.High)
}
func (k KLine) GetLow() float64 {
return util.MustParseFloat(k.Low)
}
func (k KLine) GetOpen() float64 {
return util.MustParseFloat(k.Open)
}
func (k KLine) GetClose() float64 {
return util.MustParseFloat(k.Close)
}
func (k KLine) GetMaxChange() float64 {
return k.GetHigh() - k.GetLow()
}
// GetThickness returns the thickness of the kline. 1 => thick, 0.1 => thin
func (k KLine) GetThickness() float64 {
return math.Abs(k.GetChange()) / math.Abs(k.GetMaxChange())
}
func (k KLine) GetUpperShadowRatio() float64 {
return k.GetUpperShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLine) GetUpperShadowHeight() float64 {
high := k.GetHigh()
if k.GetOpen() > k.GetClose() {
return high - k.GetOpen()
}
return high - k.GetClose()
}
func (k KLine) GetLowerShadowRatio() float64 {
return k.GetLowerShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLine) GetLowerShadowHeight() float64 {
low := k.GetLow()
if k.GetOpen() < k.GetClose() {
return k.GetOpen() - low
}
return k.GetClose() - low
}
// GetBody returns the height of the candle real body
func (k KLine) GetBody() float64 {
return k.GetChange()
}
func (k KLine) GetChange() float64 {
return k.GetClose() - k.GetOpen()
}
func (k KLine) String() string {
return fmt.Sprintf("%s %s Open: % 14s Close: % 14s High: % 14s Low: % 14s Volume: % 15s Change: % 11f Max Change: % 11f", k.Symbol, k.Interval, k.Open, k.Close, k.High, k.Low, k.Volume, k.GetChange(), k.GetMaxChange())
}
func (k KLine) Color() string {
if k.GetTrend() > 0 {
return Green
} else if k.GetTrend() < 0 {
return Red
}
return "#f0f0f0"
}
func (k KLine) SlackAttachment() slack.Attachment {
return slack.Attachment{
Text: "KLine",
Color: k.Color(),
Fields: []slack.AttachmentField{
{
Title: "Open",
Value: k.Open,
Short: true,
},
{
Title: "Close",
Value: k.Close,
Short: true,
},
{
Title: "High",
Value: k.High,
Short: true,
},
{
Title: "Low",
Value: k.Low,
Short: true,
},
{
Title: "Mid",
Value: util.FormatFloat(k.Mid(), 2),
Short: true,
},
{
Title: "Change",
Value: util.FormatFloat(k.GetChange(), 2),
Short: true,
},
{
Title: "Max Change",
Value: util.FormatFloat(k.GetMaxChange(), 2),
Short: true,
},
{
Title: "Thickness",
Value: util.FormatFloat(k.GetThickness(), 4),
Short: true,
},
{
Title: "UpperShadowRatio",
Value: util.FormatFloat(k.GetUpperShadowRatio(), 4),
Short: true,
},
{
Title: "LowerShadowRatio",
Value: util.FormatFloat(k.GetLowerShadowRatio(), 4),
Short: true,
},
},
Footer: "",
FooterIcon: "",
}
}
type KLineWindow []KLine
func (k KLineWindow) Len() int {
return len(k)
}
func (k KLineWindow) GetOpen() float64 {
return k[0].GetOpen()
}
func (k KLineWindow) GetClose() float64 {
end := len(k) - 1
return k[end].GetClose()
}
func (k KLineWindow) GetHigh() float64 {
high := k.GetOpen()
for _, line := range k {
val := line.GetHigh()
if val > high {
high = val
}
}
return high
}
func (k KLineWindow) GetLow() float64 {
low := k.GetOpen()
for _, line := range k {
val := line.GetLow()
if val < low {
low = val
}
}
return low
}
func (k KLineWindow) GetChange() float64 {
return k.GetClose() - k.GetOpen()
}
func (k KLineWindow) GetMaxChange() float64 {
return k.GetHigh() - k.GetLow()
}
func (k KLineWindow) AllDrop() bool {
for _, n := range k {
if n.GetTrend() >= 0 {
return false
}
}
return true
}
func (k KLineWindow) AllRise() bool {
for _, n := range k {
if n.GetTrend() <= 0 {
return false
}
}
return true
}
func (k KLineWindow) GetTrend() int {
o := k.GetOpen()
c := k.GetClose()
if c > o {
return 1
} else if c < o {
return -1
}
return 0
}
func (k KLineWindow) Color() string {
if k.GetTrend() > 0 {
return Green
} else if k.GetTrend() < 0 {
return Red
}
return "#f0f0f0"
}
func (k KLineWindow) Mid() float64 {
return k.GetHigh() - k.GetLow()/2
}
// green candle with open and close near high price
func (k KLineWindow) BounceUp() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() > mid && k.GetClose() > mid
}
// red candle with open and close near low price
func (k KLineWindow) BounceDown() bool {
mid := k.Mid()
trend := k.GetTrend()
return trend > 0 && k.GetOpen() < mid && k.GetClose() < mid
}
func (k *KLineWindow) Add(line KLine) {
*k = append(*k, line)
}
func (k KLineWindow) Take(size int) KLineWindow {
return k[:size]
}
func (k KLineWindow) Tail(size int) KLineWindow {
if len(k) <= size {
return k[:]
}
return k[len(k)-size:]
}
func (k *KLineWindow) Truncate(size int) {
if len(*k) <= size {
return
}
end := len(*k) - 1
start := end - size
if start < 0 {
start = 0
}
*k = (*k)[end-5 : end]
}
func (k KLineWindow) GetBody() float64 {
return k.GetChange()
}
func (k KLineWindow) GetThickness() float64 {
return math.Abs(k.GetChange()) / math.Abs(k.GetMaxChange())
}
func (k KLineWindow) GetUpperShadowRatio() float64 {
return k.GetUpperShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLineWindow) GetUpperShadowHeight() float64 {
high := k.GetHigh()
if k.GetOpen() > k.GetClose() {
return high - k.GetOpen()
}
return high - k.GetClose()
}
func (k KLineWindow) GetLowerShadowRatio() float64 {
return k.GetLowerShadowHeight() / math.Abs(k.GetMaxChange())
}
func (k KLineWindow) GetLowerShadowHeight() float64 {
low := k.GetLow()
if k.GetOpen() < k.GetClose() {
return k.GetOpen() - low
}
return k.GetClose() - low
}
func (k KLineWindow) SlackAttachment() slack.Attachment {
return slack.Attachment{
Text: "KLine",
Color: k.Color(),
Fields: []slack.AttachmentField{
{
Title: "Open",
Value: util.FormatFloat(k.GetOpen(), 2),
Short: true,
},
{
Title: "Close",
Value: util.FormatFloat(k.GetClose(), 2),
Short: true,
},
{
Title: "High",
Value: util.FormatFloat(k.GetHigh(), 2),
Short: true,
},
{
Title: "Low",
Value: util.FormatFloat(k.GetLow(), 2),
Short: true,
},
{
Title: "Mid",
Value: util.FormatFloat(k.Mid(), 2),
Short: true,
},
{
Title: "Change",
Value: util.FormatFloat(k.GetChange(), 2),
Short: true,
},
{
Title: "Max Change",
Value: util.FormatFloat(k.GetMaxChange(), 2),
Short: true,
},
{
Title: "Thickness",
Value: util.FormatFloat(k.GetThickness(), 4),
Short: true,
},
{
Title: "UpperShadowRatio",
Value: util.FormatFloat(k.GetUpperShadowRatio(), 4),
Short: true,
},
{
Title: "LowerShadowRatio",
Value: util.FormatFloat(k.GetLowerShadowRatio(), 4),
Short: true,
},
},
Footer: "",
FooterIcon: "",
}
}

View File

@ -1,6 +1,7 @@
package util
import (
"math"
"strconv"
)
@ -29,3 +30,9 @@ func MustParseFloat(s string) float64 {
return v
}
const epsilon = 0.0000001
func NotZero(v float64) bool {
return math.Abs(v) > epsilon
}

View File

@ -1,4 +1,4 @@
package bbgo
package util
import "testing"