Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

:sparles: getChartRangeRequest #1

Merged
merged 4 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ xapiRealClient, err := xapi.NewClient(os.Getenv("XAPI_USER_ID"), os.Getenv("XAPI

### GetCandles

#### With subscription

```go
xapiClient.SubscribeCandles("EURUSD")
for {
Expand All @@ -33,6 +35,26 @@ for {
}
```

#### With query

```go
end := int(time.Now().Add(-24 * 1 * time.Hour).UnixMilli())
period := 1
ticks := 50
start := int(time.Now().Add(-24 * time.Hour).UnixMilli())
candles, err := xapiClient.GetCandles(end, period, start, "EURUSD", ticks)
```

| Value | Type | Description |
| ----- | ---- | ----------- |
| end | int | End of chart block (rounded down to the nearest interval and excluding) |
| period | int | Period code |
| start | int | Start of chart block (rounded down to the nearest interval and excluding) |
| symbol | string | Symbol |
| ticks | int | Number of ticks needed |

More details here : http://developers.xstore.pro/documentation/current#getChartRangeRequest

## Contributions

Contributions and feedback are welcome! If you encounter any issues, have suggestions for improvement, or would like to contribute new features, please open an issue or submit a pull request on the GitHub repository.
Expand Down
18 changes: 14 additions & 4 deletions internal/protocols/socket/request.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package socket

type Request struct {
Command string `json:"command"`
Arguments RequestArguments `json:"arguments"`
Command string `json:"command"`
Arguments interface{} `json:"arguments"`
}

type RequestArguments interface{}

type LoginRequestArguments struct {
UserId string `json:"userId"`
Password string `json:"password"`
}

type InfoArguments struct {
Info interface{} `json:"info"`
}

type GetCandlesInfo struct {
End int `json:"end"`
Period int `json:"period"`
Start int `json:"start"`
Symbol string `json:"symbol"`
Ticks int `json:"ticks"`
}
25 changes: 22 additions & 3 deletions internal/protocols/socket/response.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
package socket

type Response struct {
Status bool `json:"status"`
type LoginResponse struct {
Status bool `json:"status"`
StreamSessionId string `json:"streamSessionId"`
ErrorCode string `json:"errorCode"`
ErrorDescr string `json:"errorDescr"`
}

type LoginResponse struct {
type Response struct {
Status bool `json:"status"`
StreamSessionId string `json:"streamSessionId"`
ErrorCode string `json:"errorCode"`
ErrorDescr string `json:"errorDescr"`
ReturnData struct {
Digits int `json:"digits"`
RateInfos []Candle `json:"rateInfos"`
} `json:"returnData"`
}

// TODO: Move it to a common package (stream and socket use it)
type Candle struct {
Close float64 `json:"close"`
Ctm int64 `json:"ctm"`
CtmString string `json:"ctmString"`
High float64 `json:"high"`
Low float64 `json:"low"`
Open float64 `json:"open"`
QuoteId int `json:"quoteId"`
Vol float64 `json:"vol"`
}
64 changes: 44 additions & 20 deletions xapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package xapi
import (
"encoding/json"
"fmt"
"sync"
"time"

"github.com/MateoGreil/xapi-go/internal/protocols/socket"
Expand All @@ -14,9 +15,9 @@ type client struct {
conn *websocket.Conn
streamConn *websocket.Conn
streamSessionId string
socketMessageChannel chan interface{}
streamMessageChannel chan interface{}
CandlesChannel chan stream.Candle
mutexSendMessage sync.Mutex
}

const (
Expand Down Expand Up @@ -53,18 +54,18 @@ func NewClient(userId string, password string, connectionType string) (*client,
}
getKeepAlive(streamConn, streamSessionId)

var m sync.Mutex
c := &client{
conn: conn,
streamConn: streamConn,
streamSessionId: streamSessionId,
socketMessageChannel: make(chan interface{}),
streamMessageChannel: make(chan interface{}),
CandlesChannel: make(chan stream.Candle),
mutexSendMessage: m,
}
go c.pingSocket()
go c.pingStream()
go c.listenStream()
go c.socketWriteJSON()
go c.streamWriteJSON()

return c, nil
Expand All @@ -79,6 +80,33 @@ func (c *client) SubscribeCandles(symbol string) {
c.streamMessageChannel <- request
}

func (c *client) GetCandles(end int, period int, start int, symbol string, ticks int) ([]socket.Candle, error) {
request := socket.Request{
Command: "getChartRangeRequest",
Arguments: socket.InfoArguments{
Info: socket.GetCandlesInfo{
End: end,
Period: period,
Start: start,
Symbol: symbol,
Ticks: ticks,
},
},
}
response := socket.Response{}
c.mutexSendMessage.Lock()
c.conn.WriteJSON(request)
err := c.conn.ReadJSON(&response)
c.mutexSendMessage.Unlock()
if err != nil {
return nil, err
}
if response.Status != true {
return nil, fmt.Errorf("Error on sending getChartRangeRequest: %+v, response:, %+v", request, response)
}
return response.ReturnData.RateInfos, nil
}

func (c *client) listenStream() {
for {
_, message, err := c.streamConn.ReadMessage()
Expand Down Expand Up @@ -109,16 +137,20 @@ func (c *client) listenStream() {

func (c *client) pingSocket() {
for {
request := socket.Request{
Command: "ping",
Arguments: nil,
request := struct {
Command string `json:"command"`
}{Command: "ping"}
response := socket.Response{}
c.mutexSendMessage.Lock()
c.conn.WriteJSON(request)
err := c.conn.ReadJSON(&response)
c.mutexSendMessage.Unlock()
if err != nil {
//TODO: Handle error
fmt.Printf("Ping socket failed: %s", err.Error())
} else if response.Status != true {
fmt.Errorf("Error on sending request: %+v, response:, %+v", request, response)
}
c.socketMessageChannel <- request
// response := socket.Response{}
// err := c.conn.ReadJSON(&response)
// if err != nil {
// fmt.Println(err.Error())
// }
time.Sleep(pingInterval)
}
}
Expand All @@ -142,14 +174,6 @@ func (c *client) streamWriteJSON() {
}
}

func (c *client) socketWriteJSON() {
for {
message := <-c.socketMessageChannel
c.conn.WriteJSON(message)
fmt.Printf("messageSocket: %+v\n", message)
}
}

func login(conn *websocket.Conn, userId string, password string) (string, error) {
request := socket.Request{
Command: "login",
Expand Down
33 changes: 33 additions & 0 deletions xapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,36 @@ func TestSuscribeCandles(t *testing.T) {
t.Error("Did not receive candles")
}
}

func TestGetCandles(t *testing.T) {
xapiClient, err := NewClient(os.Getenv("XAPI_USER_ID"), os.Getenv("XAPI_PASSWORD"), "demo")
if err != nil {
t.Error(err)
}

start := int(time.Now().Add(-24 * 1 * time.Hour).UnixMilli())
period := 1
ticks := 1
end := int(time.Now().Add(-24 * 1 * time.Hour).UnixMilli())
candles, err := xapiClient.GetCandles(start, period, end, "EURUSD", ticks)
if err != nil {
t.Error(err)
} else {
length := len(candles)
if length != 1 {
t.Errorf("Should contain 1 candle, but contains %d", length)
}
}

ticks = 50
candles, err = xapiClient.GetCandles(start, period, end, "EURUSD", ticks)
if err != nil {
t.Error(err)
} else {
length := len(candles)
if length != 50 {
t.Errorf("Should contain 50 candle, but contains %d", length)
}
}

}
Loading