Added Oanda module

This commit is contained in:
Luke I. Wilson
2023-05-17 17:57:56 -05:00
parent 494df7827b
commit 2d4c5df187
7 changed files with 209 additions and 13 deletions

57
oanda/defs.go Normal file
View File

@@ -0,0 +1,57 @@
package oanda
import (
"fmt"
"strconv"
"time"
)
// CandlestickResponse represents the response from the Oanda API for a request for candlestick data.
type CandlestickResponse struct {
Instrument string `json:"instrument"` // The instrument whose Prices are represented by the candlesticks.
Granularity string `json:"granularity"` // The granularity of the candlesticks provided.
Candles []Candlestick `json:"candles"` // The list of candlesticks that satisfy the request.
}
// Candlestick represents a single candlestick.
type Candlestick struct {
Time time.Time `json:"time"` // The start time of the candlestick.
Bid *CandlestickData `json:"bid"` // The candlestick data based on bids. Only provided if bid-based candles were requested.
Ask *CandlestickData `json:"ask"` // The candlestick data based on asks. Only provided if ask-based candles were requested.
Mid *CandlestickData `json:"mid"` // The candlestick data based on midpoints. Only provided if midpoint-based candles were requested.
Volume int `json:"volume"` // The number of prices created during the time-range represented by the candlestick.
Complete bool `json:"complete"` // A flag indicating if the candlestick is complete. A complete candlestick is one whose ending time is not in the future.
}
// CandlestickData represents the price information for a candlestick.
type CandlestickData struct {
// The first (open) price in the time-range represented by the candlestick.
O string `json:"o"`
// The highest price in the time-range represented by the candlestick.
H string `json:"h"`
// The lowest price in the time-range represented by the candlestick.
L string `json:"l"`
// The last (closing) price in the time-range represented by the candlestick.
C string `json:"c"`
}
func (d CandlestickData) Parse(o, h, l, c *float64) error {
var err error
*o, err = strconv.ParseFloat(d.O, 64)
if err != nil {
return fmt.Errorf("error parsing O field of CandlestickData: %w", err)
}
*h, err = strconv.ParseFloat(d.H, 64)
if err != nil {
return fmt.Errorf("error parsing H field of CandlestickData: %w", err)
}
*l, err = strconv.ParseFloat(d.L, 64)
if err != nil {
return fmt.Errorf("error parsing L field of CandlestickData: %w", err)
}
*c, err = strconv.ParseFloat(d.C, 64)
if err != nil {
return fmt.Errorf("error parsing C field of CandlestickData: %w", err)
}
return nil
}

1
oanda/endpoints.go Normal file
View File

@@ -0,0 +1 @@
package oanda

3
oanda/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/fivemoreminix/autotrader/oanda
go 1.20

117
oanda/oanda.go Normal file
View File

@@ -0,0 +1,117 @@
package oanda
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
auto "github.com/fivemoreminix/autotrader"
)
const (
oandaLiveURL = "https://api-fxtrade.oanda.com"
oandaPracticeURL = "https://api-fxpractice.oanda.com"
TimeLayout = time.RFC3339
)
var _ auto.Broker = (*OandaBroker)(nil) // Compile-time interface check.
type OandaBroker struct {
*auto.SignalManager
client *http.Client
token string
accountID string
baseUrl string // Either oandaLiveURL or oandaPracticeURL.
}
func NewOandaBroker(token, accountID string, practice bool) *OandaBroker {
var baseUrl string
if practice {
baseUrl = oandaPracticeURL
} else {
baseUrl = oandaLiveURL
}
return &OandaBroker{
SignalManager: &auto.SignalManager{},
client: &http.Client{},
token: token,
accountID: accountID,
baseUrl: baseUrl,
}
}
func (b *OandaBroker) Candles(symbol, frequency string, count int) (*auto.DataFrame, error) {
req, err := http.NewRequest("GET", b.baseUrl+"/v3/accounts/"+b.accountID+"/instruments/"+symbol+"/candles", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+b.token)
q := req.URL.Query()
q.Add("granularity", frequency)
q.Add("count", strconv.Itoa(auto.Min(count, 5000))) // API says max is 5000.
req.URL.RawQuery = q.Encode()
resp, err := b.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var candlestickResponse *CandlestickResponse
if err := json.NewDecoder(resp.Body).Decode(&candlestickResponse); err != nil {
return nil, err
}
return newDataframe(candlestickResponse)
}
func (b *OandaBroker) MarketOrder(symbol string, units, stopLoss, takeProfit float64) (auto.Order, error) {
return nil, nil
}
func (b *OandaBroker) NAV() float64 {
return 0
}
func (b *OandaBroker) PL() float64 {
return 0
}
func (b *OandaBroker) OpenOrders() []auto.Order {
return nil
}
func (b *OandaBroker) OpenPositions() []auto.Position {
return nil
}
func (b *OandaBroker) Orders() []auto.Order {
return nil
}
func (b *OandaBroker) Positions() []auto.Position {
return nil
}
func (b *OandaBroker) fetchAccountUpdates() {
}
func newDataframe(candles *CandlestickResponse) (*auto.DataFrame, error) {
if candles == nil {
return nil, fmt.Errorf("candles is nil or empty")
}
data := auto.NewDOHLCVDataFrame()
for _, candle := range candles.Candles {
if candle.Mid == nil {
return nil, fmt.Errorf("mid is nil or empty")
}
var o, h, l, c float64
err := candle.Mid.Parse(&o, &h, &l, &c)
if err != nil {
return nil, fmt.Errorf("error parsing mid field of a candlestick: %w", err)
}
data.PushCandle(candle.Time, o, h, l, c, int64(candle.Volume))
}
return data, nil
}