technical_analysis: TWAP & VWAP + TA methods to candles and link to existing RPC server for GCTCLI prototyping (#970)

* kline: add weighted price helpers for candles

* twap/vwap: basic implementation and hook to rpc for protype

* ta: cont implementation. (WIP)

* kline: Add tests

* kline: add helpers

* ta: full impl.

* kline: remove support for macd and add in correlation-coefficient handling

* rpc: change naming convention

* linter: fix

* protolinter: fix

* linter: ++

* kline: reinstate macd handling after adding in check

* glorious: nits

* gctcl: linter

* Update exchanges/kline/weighted_price.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits

* glorious: nits v2.0

* kline: fix test

* huobi-tests: shift from next quarter to this weeks contracts as they were erroring out in tests.

* btcmarkets: update supported kline intervals

* zb: fix test

* rpcserver: fix bug and tests

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
Ryan O'Hara-Reid
2022-07-08 15:21:56 +10:00
committed by GitHub
parent 68db4155bf
commit 7da745120f
21 changed files with 3509 additions and 325 deletions

View File

@@ -72,22 +72,29 @@ func (f fExchange) GetHistoricCandles(ctx context.Context, p currency.Pair, a as
}, nil
}
func generateCandles(amount int, timeStart time.Time, interval kline.Interval) []kline.Candle {
candy := make([]kline.Candle, amount)
for x := 0; x < amount; x++ {
candy[x] = kline.Candle{
Time: timeStart,
Open: 1337,
High: 1337,
Low: 1337,
Close: 1337,
Volume: 1337,
}
timeStart = timeStart.Add(interval.Duration())
}
return candy
}
func (f fExchange) GetHistoricCandlesExtended(ctx context.Context, p currency.Pair, a asset.Item, timeStart, _ time.Time, interval kline.Interval) (kline.Item, error) {
return kline.Item{
Exchange: fakeExchangeName,
Pair: p,
Asset: a,
Interval: interval,
Candles: []kline.Candle{
{
Time: timeStart,
Open: 1337,
High: 1337,
Low: 1337,
Close: 1337,
Volume: 1337,
},
},
Candles: generateCandles(33, timeStart, interval),
}, nil
}
@@ -2340,3 +2347,275 @@ func TestShutdown(t *testing.T) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
}
func TestGetTechnicalAnalysis(t *testing.T) {
t.Parallel()
em := SetupExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
}
b := exch.GetBase()
b.Name = fakeExchangeName
b.Enabled = true
cp, err := currency.NewPairFromString("btc-usd")
if !errors.Is(err, nil) {
t.Fatalf("received '%v', expected '%v'", err, nil)
}
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
b.CurrencyPairs.Pairs[asset.Futures] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
b.Features.Enabled.Kline.Intervals = map[string]bool{
kline.OneDay.Word(): true,
}
em.Add(fExchange{IBotExchange: exch})
s := RPCServer{
Engine: &Engine{
ExchangeManager: em,
currencyStateManager: &CurrencyStateManager{
started: 1,
iExchangeManager: em,
},
},
}
_, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{})
if !errors.Is(err, ErrExchangeNameIsEmpty) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrExchangeNameIsEmpty)
}
_, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
})
if !errors.Is(err, asset.ErrNotSupported) {
t.Fatalf("received: '%v' but expected: '%v'", err, asset.ErrNotSupported)
}
_, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "upsideprofitcontract",
Pair: &gctrpc.CurrencyPair{},
})
if !errors.Is(err, kline.ErrValidatingParams) {
t.Fatalf("received: '%v' but expected: '%v'", err, kline.ErrValidatingParams)
}
_, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
})
if !errors.Is(err, errInvalidStrategy) {
t.Fatalf("received: '%v' but expected: '%v'", err, errInvalidStrategy)
}
resp, err := s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "twap",
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if resp.Signals["TWAP"].Signals[0] != 1337 {
t.Fatalf("received: '%v' but expected: '%v'", resp.Signals["TWAP"].Signals[0], 1337)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "vwap",
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["VWAP"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["VWAP"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "atr",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["ATR"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["ATR"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "bbands",
Period: 9,
StandardDeviationUp: 0.5,
StandardDeviationDown: 0.5,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["UPPER"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["UPPER"].Signals), 33)
}
if len(resp.Signals["MIDDLE"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["MIDDLE"].Signals), 33)
}
if len(resp.Signals["LOWER"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["LOWER"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
OtherPair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "COCO",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["COCO"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["COCO"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "sma",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["SMA"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["SMA"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "ema",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["EMA"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["EMA"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "macd",
Period: 9,
FastPeriod: 12,
SlowPeriod: 26,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["MACD"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["MACD"].Signals), 33)
}
if len(resp.Signals["SIGNAL"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["SIGNAL"].Signals), 33)
}
if len(resp.Signals["HISTOGRAM"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["HISTOGRAM"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "mfi",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["MFI"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["MFI"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "obv",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["OBV"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["OBV"].Signals), 33)
}
resp, err = s.GetTechnicalAnalysis(context.Background(), &gctrpc.GetTechnicalAnalysisRequest{
Exchange: fakeExchangeName,
AssetType: "spot",
Pair: &gctrpc.CurrencyPair{Base: "btc", Quote: "usd"},
Interval: int64(kline.OneDay),
AlgorithmType: "rsi",
Period: 9,
})
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
if len(resp.Signals["RSI"].Signals) != 33 {
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["RSI"].Signals), 33)
}
}