mirror of
https://github.com/tiennm99/tcbs-api.git
synced 2026-08-24 00:28:52 +00:00
refactor: rewrite SDK to align with OpenAPI spec
- Split monolithic models.go (738 lines) into 6 domain files - Fix schema drift: BasicInfo, PersonalInfo, TotalCashDerivativeResponse, derivative order types, money transfer types aligned to spec - Add missing REST endpoints: bsa-ext, bsa-month (supply/demand) - Add WebSocket support for 5 streaming endpoints (nhooyr.io/websocket) - Add 45 httptest-based tests (74.9% coverage) - Rewrite README with full API coverage table BREAKING CHANGE: struct fields and types changed to match OpenAPI spec. BasicInfo reduced to 5 fields, TokenResponse uses 'token' field, PlaceOrderRequest uses int types, derivative order types renamed.
This commit is contained in:
@@ -1,2 +1,160 @@
|
||||
# tcbs-api
|
||||
Api for [TCBS](https://tcinvest.tcbs.com.vn/) trading
|
||||
# tcbs-api
|
||||
|
||||
Go SDK for [TCBS](https://tcinvest.tcbs.com.vn/) OpenAPI trading platform.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/tiennm99/tcbs-api
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
tcbs "github.com/tiennm99/tcbs-api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := tcbs.NewClient()
|
||||
ctx := context.Background()
|
||||
|
||||
// Authenticate
|
||||
token, err := client.GetToken(ctx, "your-api-key", "your-otp")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Authenticated:", token.Token)
|
||||
|
||||
// Get stock prices
|
||||
prices, err := client.GetStockPrices(ctx, []string{"FPT", "VNM"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, p := range prices {
|
||||
fmt.Printf("%s: %.0f\n", p.Ticker, p.MatchPrice)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
// Production (default)
|
||||
client := tcbs.NewClient()
|
||||
|
||||
// SIT environment
|
||||
client := tcbs.NewClient(tcbs.WithBaseURL(tcbs.SITBaseURL))
|
||||
|
||||
// Custom HTTP client
|
||||
client := tcbs.NewClient(tcbs.WithHTTPClient(&http.Client{Timeout: 60 * time.Second}))
|
||||
|
||||
// Pre-set token
|
||||
client := tcbs.NewClient(tcbs.WithToken("your-jwt-token"))
|
||||
```
|
||||
|
||||
## API Coverage
|
||||
|
||||
### Authentication
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `GetToken` | Exchange API Key + OTP for JWT token |
|
||||
|
||||
### Account
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `GetSubAccountInfo` | Get sub-account profile information |
|
||||
|
||||
### Stock Orders
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `PlaceOrder` | Place a stock order |
|
||||
| `UpdateOrder` | Modify an existing order |
|
||||
| `CancelOrder` | Cancel existing orders |
|
||||
|
||||
### Stock Queries
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `GetOrders` | Get order book |
|
||||
| `GetOrderByID` | Get specific order by ID |
|
||||
| `GetMatchingDetails` | Get order matching details |
|
||||
| `GetPurchasingPower` | Get purchasing power |
|
||||
| `GetPurchasingPowerBySymbol` | Get purchasing power for symbol |
|
||||
| `GetPurchasingPowerBySymbolPrice` | Get purchasing power for symbol at price |
|
||||
| `GetMarginQuota` | Get margin quota |
|
||||
| `GetMarginAccountInfo` | Get margin account risk info |
|
||||
| `GetSupplementaryLoanPackages` | Get loan package details |
|
||||
| `GetLoans` | Get loan list |
|
||||
| `GetStockAssets` | Get stock holdings |
|
||||
| `GetCashBalance` | Get cash balance |
|
||||
| `GetCashStatements` | Get cash statement history |
|
||||
| `GetMarginInfo` | Get debt inquiry info |
|
||||
|
||||
### Market Data
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `GetStockPrices` | Get stock ticker pricing |
|
||||
| `GetForeignRoom` | Get foreign investor room info |
|
||||
| `GetPutThroughInfo` | Get put-through match info |
|
||||
| `GetIntradayHistory` | Get intraday price history |
|
||||
| `GetSupplyDemand` | Get 15-min supply/demand data |
|
||||
| `GetSupplyDemandExt` | Get extended supply/demand data |
|
||||
| `GetSupplyDemandMonth` | Get monthly supply/demand data |
|
||||
|
||||
### Money Management
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `TransferMoney` | Transfer between sub-accounts |
|
||||
| `DepositMargin` | Deposit margin for derivatives |
|
||||
| `WithdrawMargin` | Withdraw margin for derivatives |
|
||||
|
||||
### Derivatives
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `GetDerivativeCashStatus` | Get derivative cash/margin overview |
|
||||
| `GetDerivativeClosedPositions` | Get closed positions |
|
||||
| `GetDerivativeOpenPositions` | Get open positions |
|
||||
| `GetDerivativeNormalOrders` | List normal orders |
|
||||
| `GetDerivativeConditionOrders` | List conditional orders |
|
||||
| `PlaceDerivativeNormalOrder` | Place normal order |
|
||||
| `PlaceDerivativeConditionOrder` | Place conditional order |
|
||||
| `ChangeDerivativeNormalOrder` | Modify normal order |
|
||||
| `ChangeDerivativeConditionOrder` | Modify conditional order |
|
||||
| `CancelDerivativeNormalOrder` | Cancel normal order |
|
||||
| `CancelDerivativeConditionOrder` | Cancel conditional order |
|
||||
| `GetDerivativeMarketInfo` | Get derivative contract pricing |
|
||||
|
||||
### WebSocket Streams
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ConnectStockMatch` | Stock match information stream |
|
||||
| `ConnectDerivativeMatch` | Derivative match information stream |
|
||||
| `ConnectCenter` | General WebSocket center |
|
||||
| `ConnectStockPrice` | Normal stock price stream |
|
||||
| `ConnectDerivativePrice` | Derivative price stream |
|
||||
|
||||
## WebSocket Usage
|
||||
|
||||
```go
|
||||
ctx := context.Background()
|
||||
ws, err := client.ConnectStockPrice(ctx, func(msgType websocket.MessageType, data []byte) {
|
||||
fmt.Println("Received:", string(data))
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
// Send subscription message
|
||||
ws.SendJSON(ctx, map[string]string{"action": "subscribe", "ticker": "FPT"})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE) for details.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSubAccountInfo(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/eros/v2/get-profile/by-username/105C001" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("fields") != "basicInfo" {
|
||||
t.Errorf("unexpected fields param: %s", r.URL.Query().Get("fields"))
|
||||
}
|
||||
writeJSON(t, w, AccountInformationResponse{
|
||||
BasicInfo: &BasicInfo{Code105C: "105C001", Status: "active"},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetSubAccountInfo(context.Background(), "105C001", "basicInfo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.BasicInfo == nil || resp.BasicInfo.Code105C != "105C001" {
|
||||
t.Error("unexpected response")
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,22 @@ import "context"
|
||||
|
||||
// TokenRequest represents the request body for exchanging API Key + OTP for JWT Token.
|
||||
type TokenRequest struct {
|
||||
APIKey string `json:"apiKey"`
|
||||
OTP string `json:"otp"`
|
||||
APIKey string `json:"apiKey"`
|
||||
}
|
||||
|
||||
// TokenResponse represents a successful token exchange response.
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
TokenType string `json:"tokenType"`
|
||||
ExpiresIn int64 `json:"expiresIn"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// TokenErrorResponse represents a failed token exchange response.
|
||||
type TokenErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
ErrorDescription string `json:"error_description"`
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// GetToken exchanges an API Key and OTP for a JWT token.
|
||||
// The returned token is valid for up to 8 hours.
|
||||
func (c *Client) GetToken(ctx context.Context, apiKey, otp string) (*TokenResponse, error) {
|
||||
var resp TokenResponse
|
||||
err := c.doRequest(ctx, "POST", "/gaia/v1/oauth2/openapi/token", nil, &TokenRequest{
|
||||
@@ -32,6 +29,6 @@ func (c *Client) GetToken(ctx context.Context, apiKey, otp string) (*TokenRespon
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.SetToken(resp.AccessToken)
|
||||
c.SetToken(resp.Token)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetToken(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/gaia/v1/oauth2/openapi/token" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, TokenResponse{Token: "jwt-123"})
|
||||
})
|
||||
|
||||
resp, err := client.GetToken(context.Background(), "key", "otp")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Token != "jwt-123" {
|
||||
t.Errorf("expected token 'jwt-123', got %q", resp.Token)
|
||||
}
|
||||
if client.currentToken() != "jwt-123" {
|
||||
t.Errorf("expected client token updated to 'jwt-123', got %q", client.currentToken())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// newTestServer creates a test HTTP server and a Client pointing at it.
|
||||
func newTestServer(t *testing.T, handler http.HandlerFunc) (*Client, *httptest.Server) {
|
||||
t.Helper()
|
||||
srv := httptest.NewServer(handler)
|
||||
t.Cleanup(srv.Close)
|
||||
client := NewClient(WithBaseURL(srv.URL), WithToken("test-token"))
|
||||
return client, srv
|
||||
}
|
||||
|
||||
// writeJSON is a test helper to write JSON responses.
|
||||
func writeJSON(t *testing.T, w http.ResponseWriter, v any) {
|
||||
t.Helper()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
t.Fatalf("failed to encode response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient_Defaults(t *testing.T) {
|
||||
c := NewClient()
|
||||
if c.baseURL != ProductionBaseURL {
|
||||
t.Errorf("expected base URL %s, got %s", ProductionBaseURL, c.baseURL)
|
||||
}
|
||||
if c.httpClient == nil {
|
||||
t.Error("expected non-nil http client")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClient_Options(t *testing.T) {
|
||||
c := NewClient(WithBaseURL(SITBaseURL), WithToken("tok"))
|
||||
if c.baseURL != SITBaseURL {
|
||||
t.Errorf("expected base URL %s, got %s", SITBaseURL, c.baseURL)
|
||||
}
|
||||
if c.currentToken() != "tok" {
|
||||
t.Errorf("expected token 'tok', got %q", c.currentToken())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetToken(t *testing.T) {
|
||||
c := NewClient()
|
||||
c.SetToken("abc")
|
||||
if c.currentToken() != "abc" {
|
||||
t.Errorf("expected token 'abc', got %q", c.currentToken())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIError(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(`{"error":"bad"}`))
|
||||
})
|
||||
err := client.get(context.Background(), "/fail", nil, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
apiErr, ok := err.(*APIError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *APIError, got %T", err)
|
||||
}
|
||||
if apiErr.StatusCode != 400 {
|
||||
t.Errorf("expected status 400, got %d", apiErr.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationHeader(t *testing.T) {
|
||||
var gotAuth string
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
gotAuth = r.Header.Get("Authorization")
|
||||
writeJSON(t, w, []MarketStockInfo{})
|
||||
})
|
||||
_, _ = client.GetStockPrices(context.Background(), []string{"FPT"})
|
||||
if gotAuth != "Bearer test-token" {
|
||||
t.Errorf("expected 'Bearer test-token', got %q", gotAuth)
|
||||
}
|
||||
}
|
||||
+12
-13
@@ -113,8 +113,8 @@ func (c *Client) GetDerivativeConditionOrders(ctx context.Context, accountID, su
|
||||
}
|
||||
|
||||
// PlaceDerivativeNormalOrder places a normal derivative order.
|
||||
func (c *Client) PlaceDerivativeNormalOrder(ctx context.Context, req *DerivativeNormalOrderRequest) (*OrderIDResponse, error) {
|
||||
var resp OrderIDResponse
|
||||
func (c *Client) PlaceDerivativeNormalOrder(ctx context.Context, req *DerivativeNormalOrderRequest) (*DerivativeResponse[*DerivativeNormalOrderPlaceResponse], error) {
|
||||
var resp DerivativeResponse[*DerivativeNormalOrderPlaceResponse]
|
||||
err := c.post(ctx, "/khronos/v1/order/place", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -123,8 +123,8 @@ func (c *Client) PlaceDerivativeNormalOrder(ctx context.Context, req *Derivative
|
||||
}
|
||||
|
||||
// PlaceDerivativeConditionOrder places a conditional derivative order (SL/TP, Arbitrage, etc.).
|
||||
func (c *Client) PlaceDerivativeConditionOrder(ctx context.Context, req *DerivativeConditionOrderRequest) (*OrderIDResponse, error) {
|
||||
var resp OrderIDResponse
|
||||
func (c *Client) PlaceDerivativeConditionOrder(ctx context.Context, req *DerivativeConditionOrderRequest) (*DerivativeResponse[*DerivativeConditionOrderPlaceResponse], error) {
|
||||
var resp DerivativeResponse[*DerivativeConditionOrderPlaceResponse]
|
||||
err := c.post(ctx, "/khronos/v1/order/condition/place", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -133,8 +133,8 @@ func (c *Client) PlaceDerivativeConditionOrder(ctx context.Context, req *Derivat
|
||||
}
|
||||
|
||||
// ChangeDerivativeNormalOrder modifies an existing normal derivative order.
|
||||
func (c *Client) ChangeDerivativeNormalOrder(ctx context.Context, req *DerivativeChangeOrderRequest) (*OrderIDResponse, error) {
|
||||
var resp OrderIDResponse
|
||||
func (c *Client) ChangeDerivativeNormalOrder(ctx context.Context, req *DerivativeChangeNormalOrderRequest) (*DerivativeResponse[string], error) {
|
||||
var resp DerivativeResponse[string]
|
||||
err := c.post(ctx, "/khronos/v1/order/change", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -143,8 +143,8 @@ func (c *Client) ChangeDerivativeNormalOrder(ctx context.Context, req *Derivativ
|
||||
}
|
||||
|
||||
// ChangeDerivativeConditionOrder modifies an existing conditional derivative order.
|
||||
func (c *Client) ChangeDerivativeConditionOrder(ctx context.Context, req *DerivativeChangeOrderRequest) (*OrderIDResponse, error) {
|
||||
var resp OrderIDResponse
|
||||
func (c *Client) ChangeDerivativeConditionOrder(ctx context.Context, req *DerivativeChangeConditionOrderRequest) (*DerivativeResponse[string], error) {
|
||||
var resp DerivativeResponse[string]
|
||||
err := c.post(ctx, "/khronos/v2/order/condition/change", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -153,8 +153,8 @@ func (c *Client) ChangeDerivativeConditionOrder(ctx context.Context, req *Deriva
|
||||
}
|
||||
|
||||
// CancelDerivativeNormalOrder cancels a normal derivative order.
|
||||
func (c *Client) CancelDerivativeNormalOrder(ctx context.Context, req *DerivativeCancelOrderRequest) (*OrderIDResponse, error) {
|
||||
var resp OrderIDResponse
|
||||
func (c *Client) CancelDerivativeNormalOrder(ctx context.Context, req *DerivativeCancelNormalOrderRequest) (*DerivativeResponse[*DerivativeCancelNormalOrderResponse], error) {
|
||||
var resp DerivativeResponse[*DerivativeCancelNormalOrderResponse]
|
||||
err := c.post(ctx, "/khronos/v1/order/cancel", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -163,8 +163,8 @@ func (c *Client) CancelDerivativeNormalOrder(ctx context.Context, req *Derivativ
|
||||
}
|
||||
|
||||
// CancelDerivativeConditionOrder cancels a conditional derivative order.
|
||||
func (c *Client) CancelDerivativeConditionOrder(ctx context.Context, req *DerivativeCancelOrderRequest) (*OrderIDResponse, error) {
|
||||
var resp OrderIDResponse
|
||||
func (c *Client) CancelDerivativeConditionOrder(ctx context.Context, req *DerivativeCancelConditionOrderRequest) (*DerivativeResponse[string], error) {
|
||||
var resp DerivativeResponse[string]
|
||||
err := c.post(ctx, "/khronos/v1/order/condition/cancel", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -173,7 +173,6 @@ func (c *Client) CancelDerivativeConditionOrder(ctx context.Context, req *Deriva
|
||||
}
|
||||
|
||||
// GetDerivativeMarketInfo retrieves derivative contract pricing and information.
|
||||
// tickers is a list of derivative contract symbols.
|
||||
func (c *Client) GetDerivativeMarketInfo(ctx context.Context, tickers []string) ([]DerivativeMarketInfo, error) {
|
||||
query := url.Values{}
|
||||
query.Set("tickers", strings.Join(tickers, ","))
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetDerivativeCashStatus(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/khronos/v1/account/status" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("accountId") != "ACC1" {
|
||||
t.Errorf("unexpected accountId: %s", r.URL.Query().Get("accountId"))
|
||||
}
|
||||
writeJSON(t, w, DerivativeResponse[*TotalCashDerivativeResponse]{
|
||||
RC: "0",
|
||||
Data: &TotalCashDerivativeResponse{NAV: 50000000, Cash: 10000000},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetDerivativeCashStatus(context.Background(), "ACC1", "SUB1", "0")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Data == nil || resp.Data.NAV != 50000000 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDerivativeClosedPositions(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[[]AssetPositionCloseDerivativeResponse]{
|
||||
Data: []AssetPositionCloseDerivativeResponse{{Symbol: "VN30F2503", Side: "B"}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetDerivativeClosedPositions(context.Background(), DerivativePositionCloseParams{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", PageNo: 1, PageSize: 10,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Data) != 1 || resp.Data[0].Symbol != "VN30F2503" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDerivativeOpenPositions(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[[]AssetPositionOpenDerivativeResponse]{
|
||||
Data: []AssetPositionOpenDerivativeResponse{{Symbol: "VN30F2503", Net: 5}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetDerivativeOpenPositions(context.Background(), "ACC1", "SUB1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Data) != 1 || resp.Data[0].Net != 5 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDerivativeNormalOrders(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[[]DerivativeNormalOrderResponse]{
|
||||
Data: []DerivativeNormalOrderResponse{{OrderNo: "N001", Symbol: "VN30F2503"}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetDerivativeNormalOrders(context.Background(), DerivativeOrdersParams{
|
||||
PageNo: 1, PageSize: 10, AccountID: "ACC1", Symbol: "ALL,ALL", Status: "0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Data) != 1 || resp.Data[0].OrderNo != "N001" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaceDerivativeNormalOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
writeJSON(t, w, DerivativeResponse[*DerivativeNormalOrderPlaceResponse]{
|
||||
RC: "0",
|
||||
Data: &DerivativeNormalOrderPlaceResponse{OrderNo: "N002", Symbol: "VN30F2503"},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.PlaceDerivativeNormalOrder(context.Background(), &DerivativeNormalOrderRequest{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", Side: "B",
|
||||
Symbol: "VN30F2503", Price: 1200, Volume: 1, OrderType: "LO",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Data == nil || resp.Data.OrderNo != "N002" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDerivativeConditionOrders(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/khronos/v1/order/condition/detail" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, DerivativeResponse[[]DerivativeConditionOrderResponse]{
|
||||
Data: []DerivativeConditionOrderResponse{{OrderNo: "C001", Symbol: "VN30F2503"}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetDerivativeConditionOrders(context.Background(), "ACC1", "SUB1", 1, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Data) != 1 || resp.Data[0].OrderNo != "C001" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaceDerivativeConditionOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[*DerivativeConditionOrderPlaceResponse]{
|
||||
Data: &DerivativeConditionOrderPlaceResponse{OrderNo: 101, Symbol: "VN30F2503"},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.PlaceDerivativeConditionOrder(context.Background(), &DerivativeConditionOrderRequest{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", Side: "B",
|
||||
Symbol: "VN30F2503", Price: 1200, Volume: 1, OrderType: "LO",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Data == nil || resp.Data.OrderNo != 101 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeDerivativeNormalOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[string]{RC: "0", Data: "ok"})
|
||||
})
|
||||
|
||||
_, err := client.ChangeDerivativeNormalOrder(context.Background(), &DerivativeChangeNormalOrderRequest{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", OrderNo: "N001", RefID: "ref1", NVol: 2, NPrice: 1300,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangeDerivativeConditionOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[string]{RC: "0", Data: "ok"})
|
||||
})
|
||||
|
||||
_, err := client.ChangeDerivativeConditionOrder(context.Background(), &DerivativeChangeConditionOrderRequest{
|
||||
AccountID: "ACC1", PKOrderNo: "PK001", Type: "SL", RefID: "ref1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelDerivativeNormalOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[*DerivativeCancelNormalOrderResponse]{
|
||||
Data: &DerivativeCancelNormalOrderResponse{OrderNo: "N001", Status: "cancelled"},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.CancelDerivativeNormalOrder(context.Background(), &DerivativeCancelNormalOrderRequest{
|
||||
AccountID: "ACC1", OrderNo: "N001", Cmd: "cancel",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Data == nil || resp.Data.Status != "cancelled" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelDerivativeConditionOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, DerivativeResponse[string]{RC: "0", Data: "ok"})
|
||||
})
|
||||
|
||||
_, err := client.CancelDerivativeConditionOrder(context.Background(), &DerivativeCancelConditionOrderRequest{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", OrderNo: "C001",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDerivativeMarketInfo(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, []DerivativeMarketInfo{
|
||||
{Ticker: "VN30F2503", LastPrice: 1250.5, OpenInterest: 30000},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetDerivativeMarketInfo(context.Background(), []string{"VN30F2503"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp) != 1 || resp[0].Ticker != "VN30F2503" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
+20
-3
@@ -22,7 +22,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get token: %v", err)
|
||||
}
|
||||
fmt.Printf("Token obtained, expires in %d seconds\n", token.ExpiresIn)
|
||||
fmt.Printf("Token obtained: %s\n", token.Token)
|
||||
|
||||
// Or set token directly if you already have one:
|
||||
// client.SetToken("your-jwt-token")
|
||||
@@ -33,7 +33,10 @@ func main() {
|
||||
log.Fatalf("Failed to get account info: %v", err)
|
||||
}
|
||||
if account.BasicInfo != nil {
|
||||
fmt.Printf("Account: %s - %s\n", account.BasicInfo.Code105C, account.BasicInfo.FullName)
|
||||
fmt.Printf("Account: %s (status: %s)\n", account.BasicInfo.Code105C, account.BasicInfo.Status)
|
||||
}
|
||||
if account.PersonalInfo != nil {
|
||||
fmt.Printf("Name: %s\n", account.PersonalInfo.FullName)
|
||||
}
|
||||
|
||||
// 3. Get stock prices
|
||||
@@ -49,7 +52,7 @@ func main() {
|
||||
order, err := client.PlaceOrder(ctx, "0001170730", &tcbs.PlaceOrderRequest{
|
||||
Symbol: "FPT",
|
||||
ExecType: "NB", // Buy
|
||||
OrderQtty: 100,
|
||||
Quantity: 100,
|
||||
Price: 120000,
|
||||
PriceType: "LO", // Limit order
|
||||
})
|
||||
@@ -89,4 +92,18 @@ func main() {
|
||||
for _, d := range derivatives {
|
||||
fmt.Printf("%s: last=%.1f OI=%.0f\n", d.Ticker, d.LastPrice, d.OpenInterest)
|
||||
}
|
||||
|
||||
// 9. Get supply/demand (15-minute)
|
||||
sd, err := client.GetSupplyDemand(ctx, "FPT", "all")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get supply/demand: %v", err)
|
||||
}
|
||||
fmt.Printf("Supply/demand data points: %d\n", len(sd.Data))
|
||||
|
||||
// 10. Get monthly supply/demand
|
||||
sdm, err := client.GetSupplyDemandMonth(ctx, "FPT", "all")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get monthly supply/demand: %v", err)
|
||||
}
|
||||
fmt.Printf("Monthly supply/demand data points: %d\n", len(sdm.Data))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
module github.com/tiennm99/tcbs-api
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require nhooyr.io/websocket v1.8.17 // indirect
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||
@@ -1,737 +0,0 @@
|
||||
package tcbs
|
||||
|
||||
// DerivativeResponse is a generic wrapper for derivative API responses.
|
||||
type DerivativeResponse[T any] struct {
|
||||
Cmd string `json:"cmd"`
|
||||
RC string `json:"rc"`
|
||||
RS string `json:"rs"`
|
||||
OID string `json:"oID"`
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
// --- Account Models ---
|
||||
|
||||
// AccountInformationResponse represents sub-account information.
|
||||
type AccountInformationResponse struct {
|
||||
BasicInfo *BasicInfo `json:"basicInfo,omitempty"`
|
||||
PersonalInfo *PersonalInfo `json:"personalInfo,omitempty"`
|
||||
BankSubAccounts []BankSubAccount `json:"bankSubAccounts,omitempty"`
|
||||
BankAccounts []BankAccount `json:"bankAccounts,omitempty"`
|
||||
}
|
||||
|
||||
// BasicInfo holds basic account information.
|
||||
type BasicInfo struct {
|
||||
TcbsID string `json:"tcbsId"`
|
||||
Code105C string `json:"code105C"`
|
||||
Status string `json:"status"`
|
||||
FullName string `json:"fullName"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
CustodyCD string `json:"custodycd"`
|
||||
BranchCode string `json:"branchCode"`
|
||||
}
|
||||
|
||||
// PersonalInfo holds personal information.
|
||||
type PersonalInfo struct {
|
||||
IDNumber string `json:"idNumber"`
|
||||
IDIssueDate string `json:"idIssueDate"`
|
||||
IDPlace string `json:"idPlace"`
|
||||
DateOfBirth string `json:"dateOfBirth"`
|
||||
Gender string `json:"gender"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// BankSubAccount represents a sub-account linked to a bank.
|
||||
type BankSubAccount struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
AccountType string `json:"accountType"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// BankAccount represents a linked bank account.
|
||||
type BankAccount struct {
|
||||
BankName string `json:"bankName"`
|
||||
BankAccount string `json:"bankAccount"`
|
||||
BankBranch string `json:"bankBranch"`
|
||||
IsDefault string `json:"isDefault"`
|
||||
}
|
||||
|
||||
// --- Order Models ---
|
||||
|
||||
// PlaceOrderRequest represents a stock order placement request.
|
||||
type PlaceOrderRequest struct {
|
||||
Symbol string `json:"symbol"`
|
||||
ExecType string `json:"execType"`
|
||||
OrderQtty float64 `json:"orderQtty"`
|
||||
Price float64 `json:"price"`
|
||||
PriceType string `json:"priceType"`
|
||||
Via string `json:"via,omitempty"`
|
||||
}
|
||||
|
||||
// PlaceOrderResponse represents the response after placing a stock order.
|
||||
type PlaceOrderResponse struct {
|
||||
Object string `json:"object"`
|
||||
OrderID string `json:"orderID"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// UpdateOrderRequest represents a stock order update request.
|
||||
type UpdateOrderRequest struct {
|
||||
OrderQtty float64 `json:"orderQtty"`
|
||||
Price float64 `json:"price"`
|
||||
PriceType string `json:"priceType"`
|
||||
}
|
||||
|
||||
// UpdateOrderResponse represents the response after updating a stock order.
|
||||
type UpdateOrderResponse struct {
|
||||
Object string `json:"object"`
|
||||
OrderID string `json:"orderID"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// CancelOrderRequest represents a stock order cancellation request.
|
||||
type CancelOrderRequest struct {
|
||||
OrderID string `json:"orderID"`
|
||||
}
|
||||
|
||||
// CancelOrderResponse represents the response after cancelling a stock order.
|
||||
type CancelOrderResponse struct {
|
||||
Object string `json:"object"`
|
||||
OrderID string `json:"orderID"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// OrderSearchResponse represents the order book response.
|
||||
type OrderSearchResponse struct {
|
||||
Object string `json:"object"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex string `json:"pageIndex"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
Data []OrderInfo `json:"data"`
|
||||
}
|
||||
|
||||
// OrderInfo represents a single order in the order book.
|
||||
type OrderInfo struct {
|
||||
Object string `json:"object"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
OrderID string `json:"orderID"`
|
||||
ExecType string `json:"execType"`
|
||||
OrderQtty float64 `json:"orderQtty"`
|
||||
ExecQtty float64 `json:"execQtty"`
|
||||
Symbol string `json:"symbol"`
|
||||
PriceType string `json:"priceType"`
|
||||
TxTime string `json:"txtime"`
|
||||
TxDate string `json:"txdate"`
|
||||
ExpDate string `json:"expDate"`
|
||||
TimeType string `json:"timeType"`
|
||||
OrStatus string `json:"orStatus"`
|
||||
FeeAcr float64 `json:"feeAcr"`
|
||||
LimitPrice float64 `json:"limitPrice"`
|
||||
CancelQtty float64 `json:"cancelQtty"`
|
||||
RemainQtty float64 `json:"remainQtty"`
|
||||
Via string `json:"via"`
|
||||
QuotePrice float64 `json:"quotePrice"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
TradePlace string `json:"tradePlace"`
|
||||
MatchType string `json:"matchType"`
|
||||
IsDisposal string `json:"isDisposal"`
|
||||
IsCancel string `json:"isCancel"`
|
||||
IsAmend string `json:"isAmend"`
|
||||
UserName string `json:"userName"`
|
||||
OrsOrderID string `json:"orsOrderID"`
|
||||
SecType string `json:"sectype"`
|
||||
IsFOOrder string `json:"isFOOrder"`
|
||||
OdTimeStamp string `json:"odTimeStamp"`
|
||||
MatchAmount float64 `json:"matchAmount"`
|
||||
BRatio float64 `json:"bRatio"`
|
||||
TaxSellAmt float64 `json:"taxSellAmout"`
|
||||
}
|
||||
|
||||
// CommandMatchInformationResponse represents matching details.
|
||||
type CommandMatchInformationResponse struct {
|
||||
Object string `json:"object"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex string `json:"pageIndex"`
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
Data []CommandMatchInformation `json:"data"`
|
||||
}
|
||||
|
||||
// CommandMatchInformation represents a single matching detail.
|
||||
type CommandMatchInformation struct {
|
||||
Object string `json:"object"`
|
||||
OrderID string `json:"orderID"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
Symbol string `json:"symbol"`
|
||||
ExecType string `json:"execType"`
|
||||
MatchQtty float64 `json:"matchQtty"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
MatchDate string `json:"matchDate"`
|
||||
MatchTime string `json:"matchTime"`
|
||||
PriceType string `json:"priceType"`
|
||||
}
|
||||
|
||||
// --- Purchasing Power Models ---
|
||||
|
||||
// PurchasingPowerResponse represents purchasing power information.
|
||||
type PurchasingPowerResponse struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
CustodyID string `json:"custodyID"`
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
PP0 float64 `json:"pp0"`
|
||||
PPSE float64 `json:"ppse"`
|
||||
PPSERef float64 `json:"ppseref"`
|
||||
MaxBuyQuantity float64 `json:"maxBuyQuantity"`
|
||||
RealMaxBuyQty float64 `json:"realMaxBuyQuantity"`
|
||||
MinBuyQuantity float64 `json:"minBuyQuantity"`
|
||||
MarginRatioLoan float64 `json:"marginRatioLoan"`
|
||||
MarginPriceLoan float64 `json:"marginPriceLoan"`
|
||||
RateBrkS string `json:"rateBrkS"`
|
||||
RateBrkB string `json:"rateBrkB"`
|
||||
}
|
||||
|
||||
// MarginQuotaResponse represents margin quota information.
|
||||
type MarginQuotaResponse struct {
|
||||
CustodyID string `json:"custodyID"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
AFType string `json:"aftype"`
|
||||
VSDStatus string `json:"vsdStatus"`
|
||||
AccountStatus string `json:"accountStatus"`
|
||||
MarginLimit float64 `json:"marginLimit"`
|
||||
IsIA string `json:"isIA"`
|
||||
BankName string `json:"bankName"`
|
||||
BankAccount string `json:"bankAccount"`
|
||||
AccountType string `json:"accountType"`
|
||||
}
|
||||
|
||||
// MarginAccountInfoResponse represents margin account details.
|
||||
type MarginAccountInfoResponse struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
RiskPolicy *RiskPolicy `json:"riskPolicy,omitempty"`
|
||||
RTT float64 `json:"rtt"`
|
||||
Outstanding float64 `json:"outstanding"`
|
||||
AccruedInterest float64 `json:"accruedInterest"`
|
||||
DueAmount float64 `json:"dueAmount"`
|
||||
OverdueAmount float64 `json:"overdueAmount"`
|
||||
RiskStatus *RiskStatus `json:"riskStatus,omitempty"`
|
||||
TotalFeeDebt float64 `json:"totalFeeDebt"`
|
||||
}
|
||||
|
||||
// RiskPolicy represents margin risk policy parameters.
|
||||
type RiskPolicy struct {
|
||||
MaintenanceMargin float64 `json:"maintenanceMargin"`
|
||||
InitialMargin float64 `json:"initialMargin"`
|
||||
LiquidationMargin float64 `json:"liquidationMargin"`
|
||||
}
|
||||
|
||||
// RiskStatus represents RTT status.
|
||||
type RiskStatus struct {
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// --- Asset Models ---
|
||||
|
||||
// SeInfoDTO represents stock asset information.
|
||||
type SeInfoDTO struct {
|
||||
Object string `json:"object"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
CustodyID string `json:"custodyID"`
|
||||
FullName string `json:"fullName"`
|
||||
Stock []StockHoldingInfo `json:"stock"`
|
||||
}
|
||||
|
||||
// StockHoldingInfo represents a single stock holding.
|
||||
type StockHoldingInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
SecType string `json:"secType"`
|
||||
SecTypeName string `json:"secTypeName"`
|
||||
AvailableTrading float64 `json:"availableTrading"`
|
||||
Mortgaged float64 `json:"mortgaged"`
|
||||
T0 float64 `json:"t0"`
|
||||
T1 float64 `json:"t1"`
|
||||
T2 float64 `json:"t2"`
|
||||
Blocked float64 `json:"blocked"`
|
||||
SecuredQuantity float64 `json:"securedQuantity"`
|
||||
SellRemain float64 `json:"sellRemain"`
|
||||
ExercisedCA float64 `json:"exercisedCA"`
|
||||
UnexercisedCA float64 `json:"unexercisedCA"`
|
||||
StockDividend float64 `json:"stockDividend"`
|
||||
CashDividend float64 `json:"cashDividend"`
|
||||
WaitForTrade float64 `json:"waitForTrade"`
|
||||
WaitForTransfer float64 `json:"waitForTransfer"`
|
||||
WaitForWithdraw float64 `json:"waitForWithdraw"`
|
||||
CurrentPrice float64 `json:"currentPrice"`
|
||||
CostPrice float64 `json:"costPrice"`
|
||||
SellExec float64 `json:"sellExec"`
|
||||
OnHold float64 `json:"onHold"`
|
||||
TotalQtty float64 `json:"totalQtty"`
|
||||
Settlement float64 `json:"settlement"`
|
||||
}
|
||||
|
||||
// CashInvestmentResponse represents cash balance information.
|
||||
type CashInvestmentResponse struct {
|
||||
Object string `json:"object"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex int `json:"pageIndex"`
|
||||
Data []CashInvestment `json:"data"`
|
||||
}
|
||||
|
||||
// CashInvestment represents a single cash investment record.
|
||||
type CashInvestment struct {
|
||||
Object string `json:"object"`
|
||||
IAInfos []IAInfo `json:"iaInfos"`
|
||||
PP0ForBF float64 `json:"pp0forBF"`
|
||||
BankAvlBalanceBF float64 `json:"bankAvlBalanceBF"`
|
||||
BodBalance float64 `json:"bodBalance"`
|
||||
CashBalance float64 `json:"cashBalance"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
CustodyID string `json:"custodyID"`
|
||||
FullName string `json:"fullName"`
|
||||
Balance float64 `json:"balance"`
|
||||
AvlAdvanceAmount float64 `json:"avlAdvanceAmount"`
|
||||
BuyingAmount float64 `json:"buyingAmount"`
|
||||
BlockAmount float64 `json:"blockAmount"`
|
||||
CashDividend float64 `json:"cashDevident"`
|
||||
BankAvlBalance float64 `json:"bankAvlBalance"`
|
||||
BankBlockAmount float64 `json:"bankBlockAmount"`
|
||||
AvlWithdraw float64 `json:"avlWithdraw"`
|
||||
PP0 float64 `json:"pp0"`
|
||||
SecureAmtPO float64 `json:"secureAmtPO"`
|
||||
BondBlockAmount float64 `json:"bondBlockAmount"`
|
||||
MBlockAmount float64 `json:"mBlockAmount"`
|
||||
FundBlockAmount float64 `json:"fundBlockAmount"`
|
||||
AvalBondBlock float64 `json:"avalBondBlockAmount"`
|
||||
DepoFee float64 `json:"depoFee"`
|
||||
BCashDividend float64 `json:"bCashDividend"`
|
||||
SCashDividend float64 `json:"sCashDividend"`
|
||||
DSecured float64 `json:"dsecured"`
|
||||
AdUsed float64 `json:"adused"`
|
||||
MrUsed float64 `json:"mrused"`
|
||||
}
|
||||
|
||||
// IAInfo represents instant account (IA) source information.
|
||||
type IAInfo struct {
|
||||
Partner string `json:"partner"`
|
||||
Available float64 `json:"available"`
|
||||
Hold float64 `json:"hold"`
|
||||
}
|
||||
|
||||
// TransHistCashStatementsResponse represents cash statement history.
|
||||
type TransHistCashStatementsResponse struct {
|
||||
Response *TransHistCashStatementsData `json:"response"`
|
||||
}
|
||||
|
||||
// TransHistCashStatementsData holds the paged data of cash statements.
|
||||
type TransHistCashStatementsData struct {
|
||||
PageIndex int `json:"pageIndex"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalCreditAmt int64 `json:"totalCreditAmount"`
|
||||
TotalDebitAmt int64 `json:"totalDebitAmount"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Data []CashStatementEntry `json:"data"`
|
||||
}
|
||||
|
||||
// CashStatementEntry represents a single cash statement entry.
|
||||
type CashStatementEntry struct {
|
||||
CustodyID string `json:"custodyID"`
|
||||
TransactionCode string `json:"transactionCode"`
|
||||
DebitAmount float64 `json:"debitAmount"`
|
||||
TransactionName string `json:"transactionName"`
|
||||
Descriptions string `json:"descriptions"`
|
||||
BusinessDate string `json:"businessDate"`
|
||||
TransactionNum string `json:"transactionNum"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
TransactionDate string `json:"transationDate"`
|
||||
CreditAmount float64 `json:"creditAmount"`
|
||||
}
|
||||
|
||||
// MarginInfoResponse represents debt inquiry response.
|
||||
type MarginInfoResponse struct {
|
||||
Response *MarginInfoData `json:"response"`
|
||||
}
|
||||
|
||||
// MarginInfoData holds paged margin info data.
|
||||
type MarginInfoData struct {
|
||||
TotalRow int `json:"totalRow"`
|
||||
TotalPage int `json:"totalPage"`
|
||||
Data []MarginInfoItem `json:"data"`
|
||||
}
|
||||
|
||||
// MarginInfoItem represents a single margin/debt record.
|
||||
type MarginInfoItem struct {
|
||||
RemainingInterestFee float64 `json:"remainingInterestFee"`
|
||||
ReleasedDay int `json:"releasedDay"`
|
||||
PrintAmount float64 `json:"printAmount"`
|
||||
PaidInterestFee float64 `json:"paidInterestFee"`
|
||||
IntAmount float64 `json:"intAmount"`
|
||||
ReleaseDate string `json:"releaseDate"`
|
||||
Rate2 float64 `json:"rate2"`
|
||||
OverDueDate string `json:"overDueDate"`
|
||||
PaidFee float64 `json:"paidFee"`
|
||||
ReleasedAmount float64 `json:"releasedAmount"`
|
||||
RemainingFee float64 `json:"remainingFee"`
|
||||
IntPaid float64 `json:"intPaid"`
|
||||
PrinPaid float64 `json:"prinPaid"`
|
||||
}
|
||||
|
||||
// SupplementaryLoanPackageResponse represents supplementary loan package info.
|
||||
type SupplementaryLoanPackageResponse struct {
|
||||
MarginSureViews []MarginSureView `json:"marginSureViews"`
|
||||
TPlus *TPlusData `json:"tplus,omitempty"`
|
||||
}
|
||||
|
||||
// MarginSureView represents a margin-sure insurance package.
|
||||
type MarginSureView struct {
|
||||
ID float64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
SubscriptionFee float64 `json:"subscriptionFee"`
|
||||
Status string `json:"status"`
|
||||
Proposals []MarginSureProposal `json:"proposals"`
|
||||
Default bool `json:"default"`
|
||||
}
|
||||
|
||||
// MarginSureProposal represents a proposal within a margin-sure package.
|
||||
type MarginSureProposal struct {
|
||||
ID float64 `json:"id"`
|
||||
MarginInsuranceID float64 `json:"marginInsuranceId"`
|
||||
InterestAdjustmentValue float64 `json:"interestAdjustmentValue"`
|
||||
InterestPercentThreshold float64 `json:"interestPercentThreshold"`
|
||||
ThresholdType string `json:"thresholdType"`
|
||||
}
|
||||
|
||||
// TPlusData contains T+ loan package info.
|
||||
type TPlusData struct {
|
||||
Data []TPlusPackage `json:"data"`
|
||||
}
|
||||
|
||||
// TPlusPackage represents a single T+ loan package.
|
||||
type TPlusPackage struct {
|
||||
FirstRate float64 `json:"firstRate"`
|
||||
ID float64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
UndueInterestType string `json:"undueInterestType"`
|
||||
UndueLadderValue []TPlusLadder `json:"undueLadderValue"`
|
||||
OverdueInterest float64 `json:"overdueInterest"`
|
||||
ExtensionInterest float64 `json:"extensionInterest"`
|
||||
ExtensionInterestBeforeInterestSettlement float64 `json:"extensionInterestBeforeInterestSettlement"`
|
||||
InterestCalculationBasis float64 `json:"interestCalculationBasis"`
|
||||
UndueFee float64 `json:"undueFee"`
|
||||
OverdueFee float64 `json:"overdueFee"`
|
||||
ExtensionFee float64 `json:"extensionFee"`
|
||||
DebtCollectionFee float64 `json:"debtCollectionFee"`
|
||||
Description string `json:"description"`
|
||||
ValidFrom string `json:"validFrom"`
|
||||
}
|
||||
|
||||
// TPlusLadder represents a ladder interest rate tier.
|
||||
type TPlusLadder struct {
|
||||
ID float64 `json:"id"`
|
||||
Rate float64 `json:"rate"`
|
||||
StartDate float64 `json:"startDate"`
|
||||
DueDate float64 `json:"dueDate"`
|
||||
}
|
||||
|
||||
// LoanResponse represents the loan list response.
|
||||
type LoanResponse struct {
|
||||
Size int `json:"size"`
|
||||
Content []LoanItem `json:"content"`
|
||||
}
|
||||
|
||||
// LoanItem represents a single loan.
|
||||
type LoanItem struct {
|
||||
OpeningDate string `json:"openingDate"`
|
||||
DueDate string `json:"dueDate"`
|
||||
RenewTime int `json:"renewTime"`
|
||||
MaxRenewTime int `json:"maxRenewTime"`
|
||||
IsRenewable bool `json:"isRenewable"`
|
||||
ReasonList []string `json:"reasonList"`
|
||||
Symbol string `json:"symbol"`
|
||||
ID float64 `json:"id"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
Principal float64 `json:"principal"`
|
||||
RemainingPrincipal float64 `json:"remainingPrincipal"`
|
||||
Interest float64 `json:"interest"`
|
||||
Rate float64 `json:"rate"`
|
||||
Status string `json:"status"`
|
||||
LoanDays int `json:"loanDays"`
|
||||
MrxLoanID float64 `json:"mrxLoanId"`
|
||||
Fee float64 `json:"fee"`
|
||||
UndueLoanFee float64 `json:"undueLoanFee"`
|
||||
PricingPolicyType string `json:"pricingPolicyType"`
|
||||
}
|
||||
|
||||
// --- Money Management Models ---
|
||||
|
||||
// MoneyTransferRequest represents an internal money transfer request.
|
||||
type MoneyTransferRequest struct {
|
||||
SenderAccount string `json:"senderAccount"`
|
||||
ReceiverAccount string `json:"receiverAccount"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// MoneyTransferResponse represents the transfer response.
|
||||
type MoneyTransferResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// MarginDepositWithdrawRequest represents a margin deposit or withdrawal request.
|
||||
type MarginDepositWithdrawRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// MarginDepositWithdrawResponse represents the deposit/withdraw response.
|
||||
type MarginDepositWithdrawResponse struct {
|
||||
Cmd string `json:"cmd"`
|
||||
RC string `json:"rc"`
|
||||
RS string `json:"rs"`
|
||||
OID string `json:"oID"`
|
||||
}
|
||||
|
||||
// --- Market Information Models ---
|
||||
|
||||
// MarketStockInfo represents stock ticker information.
|
||||
type MarketStockInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Exchange string `json:"exchange"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
CeilingPrice float64 `json:"ceilingPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
LowPrice float64 `json:"lowPrice"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
MatchQtty float64 `json:"matchQtty"`
|
||||
TotalMatchQtty float64 `json:"totalMatchQtty"`
|
||||
TotalMatchValue float64 `json:"totalMatchValue"`
|
||||
Best1BidPrice float64 `json:"best1BidPrice"`
|
||||
Best1BidQtty float64 `json:"best1BidQtty"`
|
||||
Best2BidPrice float64 `json:"best2BidPrice"`
|
||||
Best2BidQtty float64 `json:"best2BidQtty"`
|
||||
Best3BidPrice float64 `json:"best3BidPrice"`
|
||||
Best3BidQtty float64 `json:"best3BidQtty"`
|
||||
Best1OfferPrice float64 `json:"best1OfferPrice"`
|
||||
Best1OfferQtty float64 `json:"best1OfferQtty"`
|
||||
Best2OfferPrice float64 `json:"best2OfferPrice"`
|
||||
Best2OfferQtty float64 `json:"best2OfferQtty"`
|
||||
Best3OfferPrice float64 `json:"best3OfferPrice"`
|
||||
Best3OfferQtty float64 `json:"best3OfferQtty"`
|
||||
}
|
||||
|
||||
// ForeignRoomInfo represents foreign investor room information.
|
||||
type ForeignRoomInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
TotalRoom float64 `json:"totalRoom"`
|
||||
CurrentRoom float64 `json:"currentRoom"`
|
||||
BuyVol float64 `json:"buyVol"`
|
||||
SellVol float64 `json:"sellVol"`
|
||||
}
|
||||
|
||||
// PutThroughInfo represents put-through agreement information.
|
||||
type PutThroughInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Vol float64 `json:"vol"`
|
||||
Val float64 `json:"val"`
|
||||
}
|
||||
|
||||
// IntradayHistoryResponse represents intraday price matching history.
|
||||
type IntradayHistoryResponse struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Data []IntradayHistoryItem `json:"data"`
|
||||
}
|
||||
|
||||
// IntradayHistoryItem represents a single intraday trade.
|
||||
type IntradayHistoryItem struct {
|
||||
P float64 `json:"p"`
|
||||
V float64 `json:"v"`
|
||||
CP float64 `json:"cp"`
|
||||
RCP float64 `json:"rcp"`
|
||||
A string `json:"a"`
|
||||
BA string `json:"ba"`
|
||||
SA string `json:"sa"`
|
||||
HL string `json:"hl"`
|
||||
PCP float64 `json:"pcp"`
|
||||
T string `json:"t"`
|
||||
}
|
||||
|
||||
// SupplyDemandResponse represents supply and demand data.
|
||||
type SupplyDemandResponse struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Data []SupplyDemandItem `json:"data"`
|
||||
}
|
||||
|
||||
// SupplyDemandItem represents a single supply/demand data point.
|
||||
type SupplyDemandItem struct {
|
||||
BU float64 `json:"bu"`
|
||||
BMS float64 `json:"bms"`
|
||||
BUP float64 `json:"bup"`
|
||||
SD float64 `json:"sd"`
|
||||
SMS float64 `json:"sms"`
|
||||
SDP float64 `json:"sdp"`
|
||||
BSR float64 `json:"bsr"`
|
||||
T string `json:"t"`
|
||||
S int64 `json:"s"`
|
||||
}
|
||||
|
||||
// --- Derivative Models ---
|
||||
|
||||
// TotalCashDerivativeResponse represents derivative cash/margin overview.
|
||||
type TotalCashDerivativeResponse struct {
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
Others float64 `json:"others"`
|
||||
CashWithdraw float64 `json:"cashWithdraw"`
|
||||
TienBoSung float64 `json:"tienbosung"`
|
||||
CashAvailWithdraw float64 `json:"cashavaiwithdraw"`
|
||||
Assets float64 `json:"assets"`
|
||||
NAV float64 `json:"nav"`
|
||||
CashOut float64 `json:"cashOut"`
|
||||
VSDDeposit float64 `json:"vsdDeposit"`
|
||||
IM float64 `json:"im"`
|
||||
Cash float64 `json:"cash"`
|
||||
PL float64 `json:"pl"`
|
||||
VM float64 `json:"vm"`
|
||||
EE float64 `json:"ee"`
|
||||
}
|
||||
|
||||
// AssetPositionCloseDerivativeResponse represents a closed derivative position.
|
||||
type AssetPositionCloseDerivativeResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OpenPrice float64 `json:"openPrice"`
|
||||
ClosePrice float64 `json:"closePrice"`
|
||||
ClosePosition any `json:"closePosition"`
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
CloseVM float64 `json:"closeVM"`
|
||||
Unrealize float64 `json:"unrealize"`
|
||||
ClosePC float64 `json:"closePC"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// AssetPositionOpenDerivativeResponse represents an open derivative position.
|
||||
type AssetPositionOpenDerivativeResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
IM string `json:"im"`
|
||||
Deliver string `json:"deliver"`
|
||||
Receive string `json:"receive"`
|
||||
Net float64 `json:"net"`
|
||||
Side string `json:"side"`
|
||||
Account string `json:"account"`
|
||||
WASP float64 `json:"wasp"`
|
||||
WAPB float64 `json:"wapb"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
IMValue float64 `json:"imValue"`
|
||||
VMValue float64 `json:"vmValue"`
|
||||
MRValue float64 `json:"mrValue"`
|
||||
DueDate string `json:"duedate"`
|
||||
NetOffVol float64 `json:"netoffvol"`
|
||||
AvgRemain float64 `json:"avg_remain"`
|
||||
VMRemain float64 `json:"vm_remain"`
|
||||
PCRemain string `json:"pc_remain"`
|
||||
StopLoss string `json:"stoploss"`
|
||||
TakeProfit string `json:"takeprofit"`
|
||||
}
|
||||
|
||||
// DerivativeNormalOrderResponse represents a normal derivative order.
|
||||
type DerivativeNormalOrderResponse struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
PKOrderNo string `json:"pk_orderNo"`
|
||||
RefID string `json:"refId"`
|
||||
OrderTime string `json:"orderTime"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
Volume string `json:"volume"`
|
||||
ShowPrice string `json:"showPrice"`
|
||||
MatchVolume string `json:"matchVolume"`
|
||||
Status string `json:"status"`
|
||||
OrderStatus string `json:"orderStatus"`
|
||||
Channel string `json:"channel"`
|
||||
Group string `json:"group"`
|
||||
}
|
||||
|
||||
// DerivativeConditionOrderResponse represents a conditional derivative order.
|
||||
type DerivativeConditionOrderResponse struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
RefID string `json:"refId"`
|
||||
OrderTime string `json:"orderTime"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
Volume string `json:"volume"`
|
||||
Price string `json:"price"`
|
||||
Status string `json:"status"`
|
||||
OrderType string `json:"orderType"`
|
||||
}
|
||||
|
||||
// DerivativeNormalOrderRequest represents a request to place a normal derivative order.
|
||||
type DerivativeNormalOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"orderType"`
|
||||
Volume int `json:"volume"`
|
||||
Price string `json:"price"`
|
||||
}
|
||||
|
||||
// DerivativeConditionOrderRequest represents a request to place a conditional derivative order.
|
||||
type DerivativeConditionOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OrderType string `json:"orderType"`
|
||||
Volume int `json:"volume"`
|
||||
Price string `json:"price"`
|
||||
StopPrice string `json:"stopPrice,omitempty"`
|
||||
TakeProfit string `json:"takeProfit,omitempty"`
|
||||
StopLoss string `json:"stopLoss,omitempty"`
|
||||
}
|
||||
|
||||
// DerivativeChangeOrderRequest represents a request to modify a derivative order.
|
||||
type DerivativeChangeOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
RefID string `json:"refId"`
|
||||
Volume int `json:"volume"`
|
||||
Price string `json:"price"`
|
||||
}
|
||||
|
||||
// DerivativeCancelOrderRequest represents a request to cancel a derivative order.
|
||||
type DerivativeCancelOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
RefID string `json:"refId"`
|
||||
}
|
||||
|
||||
// DerivativeMarketInfo represents derivative contract pricing and information.
|
||||
type DerivativeMarketInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
CeilingPrice float64 `json:"ceilingPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
LowPrice float64 `json:"lowPrice"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
LastVol float64 `json:"lastVol"`
|
||||
TotalVol float64 `json:"totalVol"`
|
||||
OpenInterest float64 `json:"openInterest"`
|
||||
}
|
||||
|
||||
// OrderIDResponse represents a generic order ID response from derivative endpoints.
|
||||
type OrderIDResponse struct {
|
||||
Cmd string `json:"cmd"`
|
||||
RC string `json:"rc"`
|
||||
RS string `json:"rs"`
|
||||
OID string `json:"oID"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package tcbs
|
||||
|
||||
// AccountInformationResponse represents sub-account information.
|
||||
type AccountInformationResponse struct {
|
||||
BasicInfo *BasicInfo `json:"basicInfo,omitempty"`
|
||||
PersonalInfo *PersonalInfo `json:"personalInfo,omitempty"`
|
||||
BankAccounts []BankAccount `json:"bankAccounts,omitempty"`
|
||||
BankSubAccounts []BankSubAccount `json:"bankSubAccounts,omitempty"`
|
||||
}
|
||||
|
||||
// BasicInfo holds basic account information.
|
||||
type BasicInfo struct {
|
||||
TcbsID string `json:"tcbsId"`
|
||||
Code105C string `json:"code105C"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
Depository bool `json:"depository"`
|
||||
}
|
||||
|
||||
// PersonalInfo holds personal information.
|
||||
type PersonalInfo struct {
|
||||
FullName string `json:"fullName"`
|
||||
FullNameNoAccent string `json:"fullNameNoAccent"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
Email string `json:"email"`
|
||||
PhoneNumber string `json:"phoneNumber"`
|
||||
Gender string `json:"gender"`
|
||||
Birthday string `json:"birthday"`
|
||||
ContactAddress string `json:"contactAddress"`
|
||||
PermanentAddress string `json:"permanentAddress"`
|
||||
Nationality string `json:"nationality"`
|
||||
NationalityName string `json:"nationalityName"`
|
||||
TaxIDNumber string `json:"taxIdNumber"`
|
||||
Acronym string `json:"acronym"`
|
||||
CreatedDate string `json:"createdDate"`
|
||||
UpdatedDate string `json:"updatedDate"`
|
||||
FlowOpenAccount string `json:"flowOpenAccount"`
|
||||
AvatarURL string `json:"avatarUrl"`
|
||||
BusinessType string `json:"businessType"`
|
||||
PPBusinessType string `json:"ppBusinessType"`
|
||||
PPBusinessField string `json:"ppBusinessField"`
|
||||
PPBusinessTypeName string `json:"ppBusinessTypeName"`
|
||||
PPBusinessFieldName string `json:"ppBusinessFieldName"`
|
||||
IdentityCard *IdentityCard `json:"identityCard,omitempty"`
|
||||
}
|
||||
|
||||
// IdentityCard holds identity document information.
|
||||
type IdentityCard struct {
|
||||
Object string `json:"object"`
|
||||
IDNumber string `json:"idNumner"` // note: typo in spec
|
||||
IDPlace string `json:"idPlace"`
|
||||
IDDate string `json:"idDate"`
|
||||
ExpireDate string `json:"expireDate"`
|
||||
IDType string `json:"idType"`
|
||||
}
|
||||
|
||||
// BankSubAccount represents a sub-account linked to a bank.
|
||||
type BankSubAccount struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
AccountName string `json:"accountName"`
|
||||
AccountType string `json:"accountType"`
|
||||
AccountTypeName string `json:"accountTypeName"`
|
||||
Status string `json:"status"`
|
||||
IsDefault string `json:"isDefault"`
|
||||
}
|
||||
|
||||
// BankAccount represents a linked bank account.
|
||||
type BankAccount struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
AccountName string `json:"accountName"`
|
||||
AccountNameNoAccent string `json:"accountNameNoAccent"`
|
||||
BankCode string `json:"bankCode"`
|
||||
BankName string `json:"bankName"`
|
||||
BranchCode string `json:"branchCode"`
|
||||
BankType string `json:"bankType"`
|
||||
BankSys string `json:"bankSys"`
|
||||
Authorized string `json:"authorized"`
|
||||
BankAccountType string `json:"bankAccountType"`
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
package tcbs
|
||||
|
||||
// --- Stock Assets ---
|
||||
|
||||
// SeInfoDTO represents stock asset information.
|
||||
type SeInfoDTO struct {
|
||||
Object string `json:"object"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
CustodyID string `json:"custodyID"`
|
||||
FullName string `json:"fullName"`
|
||||
Stock []StockHoldingInfo `json:"stock"`
|
||||
}
|
||||
|
||||
// StockHoldingInfo represents a single stock holding.
|
||||
type StockHoldingInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
SecType string `json:"secType"`
|
||||
SecTypeName string `json:"secTypeName"`
|
||||
AvailableTrading float64 `json:"availableTrading"`
|
||||
Mortgaged float64 `json:"mortgaged"`
|
||||
T0 float64 `json:"t0"`
|
||||
T1 float64 `json:"t1"`
|
||||
T2 float64 `json:"t2"`
|
||||
Blocked float64 `json:"blocked"`
|
||||
SecuredQuantity float64 `json:"securedQuantity"`
|
||||
SellRemain float64 `json:"sellRemain"`
|
||||
ExercisedCA float64 `json:"exercisedCA"`
|
||||
UnexercisedCA float64 `json:"unexercisedCA"`
|
||||
StockDividend float64 `json:"stockDividend"`
|
||||
CashDividend float64 `json:"cashDividend"`
|
||||
WaitForTrade float64 `json:"waitForTrade"`
|
||||
WaitForTransfer float64 `json:"waitForTransfer"`
|
||||
WaitForWithdraw float64 `json:"waitForWithdraw"`
|
||||
CurrentPrice float64 `json:"currentPrice"`
|
||||
CostPrice float64 `json:"costPrice"`
|
||||
SellExec float64 `json:"sellExec"`
|
||||
OnHold float64 `json:"onHold"`
|
||||
TotalQtty float64 `json:"totalQtty"`
|
||||
Settlement float64 `json:"settlement"`
|
||||
}
|
||||
|
||||
// --- Cash Balance ---
|
||||
|
||||
// CashInvestmentResponse represents cash balance information.
|
||||
type CashInvestmentResponse struct {
|
||||
Object string `json:"object"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex int `json:"pageIndex"`
|
||||
Data []CashInvestment `json:"data"`
|
||||
}
|
||||
|
||||
// CashInvestment represents a single cash investment record.
|
||||
type CashInvestment struct {
|
||||
Object string `json:"object"`
|
||||
IAInfos []IAInfo `json:"iaInfos"`
|
||||
PP0ForBF float64 `json:"pp0forBF"`
|
||||
BankAvlBalanceBF float64 `json:"bankAvlBalanceBF"`
|
||||
BodBalance float64 `json:"bodBalance"`
|
||||
CashBalance float64 `json:"cashBalance"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
CustodyID string `json:"custodyID"`
|
||||
FullName string `json:"fullName"`
|
||||
Balance float64 `json:"balance"`
|
||||
AvlAdvanceAmount float64 `json:"avlAdvanceAmount"`
|
||||
BuyingAmount float64 `json:"buyingAmount"`
|
||||
BlockAmount float64 `json:"blockAmount"`
|
||||
CashDividend float64 `json:"cashDevident"` // note: typo in spec
|
||||
BankAvlBalance float64 `json:"bankAvlBalance"`
|
||||
BankBlockAmount float64 `json:"bankBlockAmount"`
|
||||
AvlWithdraw float64 `json:"avlWithdraw"`
|
||||
PP0 float64 `json:"pp0"`
|
||||
SecureAmtPO float64 `json:"secureAmtPO"`
|
||||
BondBlockAmount float64 `json:"bondBlockAmount"`
|
||||
MBlockAmount float64 `json:"mBlockAmount"`
|
||||
FundBlockAmount float64 `json:"fundBlockAmount"`
|
||||
AvalBondBlock float64 `json:"avalBondBlockAmount"`
|
||||
DepoFee float64 `json:"depoFee"`
|
||||
BCashDividend float64 `json:"bCashDividend"`
|
||||
SCashDividend float64 `json:"sCashDividend"`
|
||||
DSecured float64 `json:"dsecured"`
|
||||
AdUsed float64 `json:"adused"`
|
||||
MrUsed float64 `json:"mrused"`
|
||||
}
|
||||
|
||||
// IAInfo represents instant account (IA) source information.
|
||||
type IAInfo struct {
|
||||
Partner string `json:"partner"`
|
||||
Available float64 `json:"available"`
|
||||
Hold float64 `json:"hold"`
|
||||
}
|
||||
|
||||
// --- Cash Statements ---
|
||||
|
||||
// TransHistCashStatementsResponse represents cash statement history.
|
||||
type TransHistCashStatementsResponse struct {
|
||||
Response *TransHistCashStatementsData `json:"response"`
|
||||
}
|
||||
|
||||
// TransHistCashStatementsData holds the paged data of cash statements.
|
||||
type TransHistCashStatementsData struct {
|
||||
PageIndex int `json:"pageIndex"`
|
||||
PageSize int `json:"pageSize"`
|
||||
TotalCreditAmt int64 `json:"totalCreditAmount"`
|
||||
TotalDebitAmt int64 `json:"totalDebitAmount"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Data []CashStatementEntry `json:"data"`
|
||||
}
|
||||
|
||||
// CashStatementEntry represents a single cash statement entry.
|
||||
type CashStatementEntry struct {
|
||||
CustodyID string `json:"custodyID"`
|
||||
TransactionCode string `json:"transactionCode"`
|
||||
DebitAmount float64 `json:"debitAmount"`
|
||||
TransactionName string `json:"transactionName"`
|
||||
Descriptions string `json:"descriptions"`
|
||||
BusinessDate string `json:"businessDate"`
|
||||
TransactionNum string `json:"transactionNum"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
TransactionDate string `json:"transationDate"` // note: typo in spec
|
||||
CreditAmount float64 `json:"creditAmount"`
|
||||
}
|
||||
|
||||
// --- Margin Info ---
|
||||
|
||||
// MarginInfoResponse represents debt inquiry response.
|
||||
type MarginInfoResponse struct {
|
||||
Response *MarginInfoData `json:"response"`
|
||||
}
|
||||
|
||||
// MarginInfoData holds paged margin info data.
|
||||
type MarginInfoData struct {
|
||||
TotalRow int `json:"totalRow"`
|
||||
TotalPage int `json:"totalPage"`
|
||||
Data []MarginInfoItem `json:"data"`
|
||||
}
|
||||
|
||||
// MarginInfoItem represents a single margin/debt record.
|
||||
type MarginInfoItem struct {
|
||||
RemainingInterestFee float64 `json:"remainingInterestFee"`
|
||||
ReleasedDay int `json:"releasedDay"`
|
||||
PrintAmount float64 `json:"printAmount"`
|
||||
PaidInterestFee float64 `json:"paidInterestFee"`
|
||||
IntAmount float64 `json:"intAmount"`
|
||||
ReleaseDate string `json:"releaseDate"`
|
||||
Rate2 float64 `json:"rate2"`
|
||||
OverDueDate string `json:"overDueDate"`
|
||||
PaidFee float64 `json:"paidFee"`
|
||||
ReleasedAmount float64 `json:"releasedAmount"`
|
||||
RemainingFee float64 `json:"remainingFee"`
|
||||
IntPaid float64 `json:"intPaid"`
|
||||
PrinPaid float64 `json:"prinPaid"`
|
||||
}
|
||||
|
||||
// --- Supplementary Loan Package ---
|
||||
|
||||
// SupplementaryLoanPackageResponse represents supplementary loan package info.
|
||||
type SupplementaryLoanPackageResponse struct {
|
||||
MarginSureViews []MarginSureView `json:"marginSureViews"`
|
||||
TPlus *TPlusData `json:"tplus,omitempty"`
|
||||
}
|
||||
|
||||
// MarginSureView represents a margin-sure insurance package.
|
||||
type MarginSureView struct {
|
||||
ID float64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
SubscriptionFee float64 `json:"subscriptionFee"`
|
||||
Status string `json:"status"`
|
||||
Proposals []MarginSureProposal `json:"proposals"`
|
||||
Default bool `json:"default"`
|
||||
}
|
||||
|
||||
// MarginSureProposal represents a proposal within a margin-sure package.
|
||||
type MarginSureProposal struct {
|
||||
ID float64 `json:"id"`
|
||||
MarginInsuranceID float64 `json:"marginInsuranceId"`
|
||||
InterestAdjustmentValue float64 `json:"interestAdjustmentValue"`
|
||||
InterestPercentThreshold float64 `json:"interestPercentThreshold"`
|
||||
ThresholdType string `json:"thresholdType"`
|
||||
}
|
||||
|
||||
// TPlusData contains T+ loan package info.
|
||||
type TPlusData struct {
|
||||
Data []TPlusPackage `json:"data"`
|
||||
}
|
||||
|
||||
// TPlusPackage represents a single T+ loan package.
|
||||
type TPlusPackage struct {
|
||||
FirstRate float64 `json:"firstRate"`
|
||||
ID float64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
UndueInterestType string `json:"undueInterestType"`
|
||||
UndueLadderValue []TPlusLadder `json:"undueLadderValue"`
|
||||
OverdueInterest float64 `json:"overdueInterest"`
|
||||
ExtensionInterest float64 `json:"extensionInterest"`
|
||||
ExtensionInterestBeforeInterestSettlement float64 `json:"extensionInterestBeforeInterestSettlement"`
|
||||
InterestCalculationBasis float64 `json:"interestCalculationBasis"`
|
||||
UndueFee float64 `json:"undueFee"`
|
||||
OverdueFee float64 `json:"overdueFee"`
|
||||
ExtensionFee float64 `json:"extensionFee"`
|
||||
DebtCollectionFee float64 `json:"debtCollectionFee"`
|
||||
Description string `json:"description"`
|
||||
ValidFrom string `json:"validFrom"`
|
||||
}
|
||||
|
||||
// TPlusLadder represents a ladder interest rate tier.
|
||||
type TPlusLadder struct {
|
||||
ID float64 `json:"id"`
|
||||
Rate float64 `json:"rate"`
|
||||
StartDate float64 `json:"startDate"`
|
||||
DueDate float64 `json:"dueDate"`
|
||||
}
|
||||
|
||||
// --- Loans ---
|
||||
|
||||
// LoanResponse represents the loan list response.
|
||||
type LoanResponse struct {
|
||||
Size int `json:"size"`
|
||||
Content []LoanItem `json:"content"`
|
||||
}
|
||||
|
||||
// LoanItem represents a single loan.
|
||||
type LoanItem struct {
|
||||
OpeningDate string `json:"openingDate"`
|
||||
DueDate string `json:"dueDate"`
|
||||
RenewTime int `json:"renewTime"`
|
||||
MaxRenewTime int `json:"maxRenewTime"`
|
||||
IsRenewable bool `json:"isRenewable"`
|
||||
ReasonList []string `json:"reasonList"`
|
||||
Symbol string `json:"symbol"`
|
||||
ID float64 `json:"id"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
Principal float64 `json:"principal"`
|
||||
RemainingPrincipal float64 `json:"remainingPrincipal"`
|
||||
Interest float64 `json:"interest"`
|
||||
Rate float64 `json:"rate"`
|
||||
Status string `json:"status"`
|
||||
LoanDays int `json:"loanDays"`
|
||||
MrxLoanID float64 `json:"mrxLoanId"`
|
||||
Fee float64 `json:"fee"`
|
||||
UndueLoanFee float64 `json:"undueLoanFee"`
|
||||
PricingPolicyType string `json:"pricingPolicyType"`
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package tcbs
|
||||
|
||||
// DerivativeResponse is a generic wrapper for derivative API responses.
|
||||
type DerivativeResponse[T any] struct {
|
||||
Cmd string `json:"cmd"`
|
||||
RC string `json:"rc"`
|
||||
RS string `json:"rs"`
|
||||
OID string `json:"oID"`
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
// --- Cash & Positions ---
|
||||
|
||||
// TotalCashDerivativeResponse represents derivative cash/margin overview.
|
||||
type TotalCashDerivativeResponse struct {
|
||||
Cash float64 `json:"cash"`
|
||||
Stock float64 `json:"stock"`
|
||||
Collateral float64 `json:"collateral"`
|
||||
Type string `json:"type"`
|
||||
Net string `json:"net"`
|
||||
Tyle string `json:"tyle"`
|
||||
IM float64 `json:"im"`
|
||||
VM float64 `json:"vm"`
|
||||
DM float64 `json:"dm"`
|
||||
MR float64 `json:"mr"`
|
||||
AvaiCash float64 `json:"avaiCash"`
|
||||
AvaiColla float64 `json:"avaiColla"`
|
||||
VMUnpay float64 `json:"vmunpay"`
|
||||
Info string `json:"info"`
|
||||
Color string `json:"color"`
|
||||
VMEod string `json:"vm_eod"`
|
||||
Others float64 `json:"others"`
|
||||
Tax float64 `json:"tax"`
|
||||
FeeCTCK float64 `json:"feeCTCK"`
|
||||
FeeHNX float64 `json:"feeHNX"`
|
||||
CashWithdraw float64 `json:"cashWithdraw"`
|
||||
TienBoSung float64 `json:"tienbosung"`
|
||||
CashAvailWithdraw float64 `json:"cashavaiwithdraw"`
|
||||
Assets float64 `json:"assets"`
|
||||
NAV float64 `json:"nav"`
|
||||
CashOut float64 `json:"cashOut"`
|
||||
UnrealizeVM float64 `json:"unrelizeVM"`
|
||||
FeePos float64 `json:"feePos"`
|
||||
FeeMan float64 `json:"feeMan"`
|
||||
Product string `json:"product"`
|
||||
Status string `json:"status"`
|
||||
Debt string `json:"debt"`
|
||||
W1 float64 `json:"w1"`
|
||||
W2 float64 `json:"w2"`
|
||||
Limit float64 `json:"limit"`
|
||||
Package string `json:"package"`
|
||||
}
|
||||
|
||||
// AssetPositionCloseDerivativeResponse represents a closed derivative position.
|
||||
type AssetPositionCloseDerivativeResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Side string `json:"side"`
|
||||
OpenPrice string `json:"openPrice"`
|
||||
ClosePrice string `json:"closePrice"`
|
||||
ClosePosition string `json:"closePosition"`
|
||||
Fee string `json:"fee"`
|
||||
Tax string `json:"tax"`
|
||||
CloseVM string `json:"closeVM"`
|
||||
Unrealize string `json:"unrealize"`
|
||||
ClosePC string `json:"closePC"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// AssetPositionOpenDerivativeResponse represents an open derivative position.
|
||||
type AssetPositionOpenDerivativeResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
IM string `json:"im"`
|
||||
Deliver int `json:"deliver"`
|
||||
Receive int `json:"receive"`
|
||||
Net int `json:"net"`
|
||||
Side string `json:"side"`
|
||||
Account string `json:"account"`
|
||||
WASP float64 `json:"wasp"`
|
||||
WAPB float64 `json:"wapb"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
IMValue float64 `json:"imValue"`
|
||||
VMValue float64 `json:"vmValue"`
|
||||
MRValue float64 `json:"mrValue"`
|
||||
DueDate string `json:"duedate"`
|
||||
NetOffVol int `json:"netoffvol"`
|
||||
AvgRemain float64 `json:"avg_remain"`
|
||||
VMRemain float64 `json:"vm_remain"`
|
||||
PCRemain float64 `json:"pc_remain"`
|
||||
StopLoss string `json:"stoploss"`
|
||||
TakeProfit string `json:"takeprofit"`
|
||||
}
|
||||
|
||||
// --- Normal Orders ---
|
||||
|
||||
// DerivativeNormalOrderRequest represents a request to place a normal derivative order.
|
||||
type DerivativeNormalOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
Volume int `json:"volume"`
|
||||
Advance string `json:"advance,omitempty"`
|
||||
RefID string `json:"refId,omitempty"`
|
||||
OrderType string `json:"orderType"`
|
||||
Pin string `json:"pin,omitempty"`
|
||||
}
|
||||
|
||||
// DerivativeNormalOrderResponse represents a normal derivative order in list responses.
|
||||
type DerivativeNormalOrderResponse struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
PKOrderNo string `json:"pk_orderNo"`
|
||||
RefID string `json:"refId"`
|
||||
OrderTime string `json:"orderTime"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
Volume float64 `json:"volume"`
|
||||
ShowPrice float64 `json:"showPrice"`
|
||||
MatchVolume float64 `json:"matchVolume"`
|
||||
MatchPriceBQ float64 `json:"matchPriceBQ"`
|
||||
Status string `json:"status"`
|
||||
OrderStatus string `json:"orderStatus"`
|
||||
Channel string `json:"channel"`
|
||||
Group string `json:"group"`
|
||||
CancelTime string `json:"cancelTime"`
|
||||
IsCancel float64 `json:"isCancel"`
|
||||
IsAmend float64 `json:"isAmend"`
|
||||
Info string `json:"info"`
|
||||
MaxPrice float64 `json:"maxPrice"`
|
||||
MatchValue float64 `json:"matchValue"`
|
||||
Quote string `json:"quote"`
|
||||
AutoType string `json:"autoType"`
|
||||
Product string `json:"product"`
|
||||
OrderType string `json:"orderType"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
// DerivativeNormalOrderPlaceResponse represents the response after placing a normal order.
|
||||
type DerivativeNormalOrderPlaceResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
ShareStatus string `json:"shareStatus"`
|
||||
Status string `json:"status"`
|
||||
MsgType string `json:"msg_type"`
|
||||
ShowPrice float64 `json:"showPrice"`
|
||||
OrderTime string `json:"orderTime"`
|
||||
Type string `json:"type"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Market string `json:"market"`
|
||||
MatchVolume float64 `json:"matchVolume"`
|
||||
Side string `json:"side"`
|
||||
Volume float64 `json:"volume"`
|
||||
PKOrderNo string `json:"pk_orderNo"`
|
||||
Channel string `json:"channel"`
|
||||
RefID string `json:"refID"`
|
||||
Group string `json:"group"`
|
||||
AccType string `json:"accType"`
|
||||
Quote string `json:"quote"`
|
||||
AutoType string `json:"autoType"`
|
||||
Product string `json:"product"`
|
||||
}
|
||||
|
||||
// --- Condition Orders ---
|
||||
|
||||
// DerivativeConditionOrderRequest represents a request to place a conditional order.
|
||||
type DerivativeConditionOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
Volume float64 `json:"volume"`
|
||||
Advance string `json:"advance,omitempty"`
|
||||
RefID string `json:"refId,omitempty"`
|
||||
OrderType string `json:"orderType"`
|
||||
Pin string `json:"pin,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Cmd string `json:"cmd,omitempty"`
|
||||
CallbackPoint float64 `json:"callbackPoint,omitempty"`
|
||||
ActivationPrice float64 `json:"activationPrice,omitempty"`
|
||||
SOPrice float64 `json:"soPrice,omitempty"`
|
||||
}
|
||||
|
||||
// DerivativeConditionOrderResponse represents a conditional order in list responses.
|
||||
type DerivativeConditionOrderResponse struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
GroupOrder string `json:"groupOrder"`
|
||||
PKOrderNo string `json:"pk_orderNo"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
ShowPrice float64 `json:"showPrice"`
|
||||
Volume float64 `json:"volume"`
|
||||
Condition string `json:"condition"`
|
||||
Result string `json:"result"`
|
||||
ActiveTime string `json:"active_time"`
|
||||
SendTime string `json:"send_time"`
|
||||
CancelTime string `json:"cancel_time"`
|
||||
Group string `json:"group"`
|
||||
Channel string `json:"channel"`
|
||||
MaxPrice string `json:"maxPrice"`
|
||||
SOPrice float64 `json:"soPrice"`
|
||||
OrderType string `json:"orderType"`
|
||||
FromTime string `json:"from_time"`
|
||||
ExpTime string `json:"exp_time"`
|
||||
Status string `json:"status"`
|
||||
Details string `json:"details"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
// DerivativeConditionOrderPlaceResponse represents the response after placing a condition order.
|
||||
type DerivativeConditionOrderPlaceResponse struct {
|
||||
Symbol string `json:"symbol"`
|
||||
ShareStatus string `json:"shareStatus"`
|
||||
Status string `json:"status"`
|
||||
MsgType string `json:"msg_type"`
|
||||
ShowPrice float64 `json:"showPrice"`
|
||||
OrderTime string `json:"orderTime"`
|
||||
Type string `json:"type"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
OrderNo int `json:"orderNo"`
|
||||
Market string `json:"market"`
|
||||
MatchVolume float64 `json:"matchVolume"`
|
||||
Side string `json:"side"`
|
||||
Volume float64 `json:"volume"`
|
||||
PKOrderNo string `json:"pk_orderNo"`
|
||||
Channel string `json:"channel"`
|
||||
RefID string `json:"refID"`
|
||||
Group string `json:"group"`
|
||||
AccType string `json:"accType"`
|
||||
Quote string `json:"quote"`
|
||||
AutoType string `json:"autoType"`
|
||||
Product string `json:"product"`
|
||||
}
|
||||
|
||||
// --- Edit Orders ---
|
||||
|
||||
// DerivativeChangeNormalOrderRequest represents a request to modify a normal order.
|
||||
type DerivativeChangeNormalOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
RefID string `json:"refId"`
|
||||
NVol float64 `json:"nvol"`
|
||||
NPrice float64 `json:"nprice"`
|
||||
}
|
||||
|
||||
// DerivativeChangeConditionOrderRequest represents a request to modify a conditional order.
|
||||
type DerivativeChangeConditionOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
PKOrderNo string `json:"pkOrderNo"`
|
||||
Type string `json:"type"`
|
||||
RefID string `json:"refId"`
|
||||
SOPrice float64 `json:"soPrice"`
|
||||
Cmd string `json:"cmd"`
|
||||
}
|
||||
|
||||
// --- Cancel Orders ---
|
||||
|
||||
// DerivativeCancelNormalOrderRequest represents a request to cancel a normal order.
|
||||
type DerivativeCancelNormalOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Cmd string `json:"cmd"`
|
||||
Pin string `json:"pin,omitempty"`
|
||||
RefID string `json:"refId,omitempty"`
|
||||
}
|
||||
|
||||
// DerivativeCancelNormalOrderResponse represents the response after cancelling a normal order.
|
||||
type DerivativeCancelNormalOrderResponse struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
MsgType string `json:"msg_type"`
|
||||
Status string `json:"status"`
|
||||
PKOrderNo string `json:"pk_orderNo"`
|
||||
CancelTime string `json:"cancelTime"`
|
||||
}
|
||||
|
||||
// DerivativeCancelConditionOrderRequest represents a request to cancel a conditional order.
|
||||
type DerivativeCancelConditionOrderRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
}
|
||||
|
||||
// --- Market Info ---
|
||||
|
||||
// DerivativeMarketInfo represents derivative contract pricing from REST API.
|
||||
type DerivativeMarketInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
CeilingPrice float64 `json:"ceilingPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
LowPrice float64 `json:"lowPrice"`
|
||||
LastPrice float64 `json:"lastPrice"`
|
||||
LastVol float64 `json:"lastVol"`
|
||||
TotalVol float64 `json:"totalVol"`
|
||||
OpenInterest float64 `json:"openInterest"`
|
||||
}
|
||||
|
||||
// OrderIDResponse represents a generic order ID response from derivative endpoints.
|
||||
type OrderIDResponse struct {
|
||||
OrderID string `json:"orderID"`
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package tcbs
|
||||
|
||||
// --- Stock Price (REST) ---
|
||||
|
||||
// MarketStockInfo represents stock ticker information from REST API.
|
||||
type MarketStockInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Exchange string `json:"exchange"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
CeilingPrice float64 `json:"ceilingPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
HighPrice float64 `json:"highPrice"`
|
||||
LowPrice float64 `json:"lowPrice"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
MatchQtty float64 `json:"matchQtty"`
|
||||
TotalMatchQtty float64 `json:"totalMatchQtty"`
|
||||
TotalMatchValue float64 `json:"totalMatchValue"`
|
||||
Best1BidPrice float64 `json:"best1BidPrice"`
|
||||
Best1BidQtty float64 `json:"best1BidQtty"`
|
||||
Best2BidPrice float64 `json:"best2BidPrice"`
|
||||
Best2BidQtty float64 `json:"best2BidQtty"`
|
||||
Best3BidPrice float64 `json:"best3BidPrice"`
|
||||
Best3BidQtty float64 `json:"best3BidQtty"`
|
||||
Best1OfferPrice float64 `json:"best1OfferPrice"`
|
||||
Best1OfferQtty float64 `json:"best1OfferQtty"`
|
||||
Best2OfferPrice float64 `json:"best2OfferPrice"`
|
||||
Best2OfferQtty float64 `json:"best2OfferQtty"`
|
||||
Best3OfferPrice float64 `json:"best3OfferPrice"`
|
||||
Best3OfferQtty float64 `json:"best3OfferQtty"`
|
||||
}
|
||||
|
||||
// --- Foreign Room (REST) ---
|
||||
|
||||
// ForeignRoomInfo represents foreign investor room information.
|
||||
type ForeignRoomInfo struct {
|
||||
Ticker string `json:"ticker"`
|
||||
TotalRoom float64 `json:"totalRoom"`
|
||||
CurrentRoom float64 `json:"currentRoom"`
|
||||
BuyVol float64 `json:"buyVol"`
|
||||
SellVol float64 `json:"sellVol"`
|
||||
}
|
||||
|
||||
// --- Put-Through (REST) ---
|
||||
|
||||
// PutThroughMatchInfo represents put-through match information.
|
||||
type PutThroughMatchInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
Vol float64 `json:"vol"`
|
||||
Val float64 `json:"val"`
|
||||
Time string `json:"time"`
|
||||
AccumulatedValue float64 `json:"accumulatedValue"`
|
||||
}
|
||||
|
||||
// PutThroughAdvertisementInfo represents put-through advertisement information.
|
||||
type PutThroughAdvertisementInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
Vol float64 `json:"vol"`
|
||||
Time string `json:"time"`
|
||||
Status int `json:"status"`
|
||||
Color int `json:"color"`
|
||||
OrderID string `json:"orderId"`
|
||||
Side string `json:"side"`
|
||||
}
|
||||
|
||||
// --- Intraday History ---
|
||||
|
||||
// IntradayHistoryResponse represents intraday price matching history.
|
||||
type IntradayHistoryResponse struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Data []IntradayHistoryItem `json:"data"`
|
||||
}
|
||||
|
||||
// IntradayHistoryItem represents a single intraday trade.
|
||||
type IntradayHistoryItem struct {
|
||||
P float64 `json:"p"`
|
||||
V float64 `json:"v"`
|
||||
CP float64 `json:"cp"`
|
||||
RCP float64 `json:"rcp"`
|
||||
A string `json:"a"`
|
||||
BA float64 `json:"ba"`
|
||||
SA float64 `json:"sa"`
|
||||
HL bool `json:"hl"`
|
||||
PCP float64 `json:"pcp"`
|
||||
T string `json:"t"`
|
||||
}
|
||||
|
||||
// --- Supply & Demand ---
|
||||
|
||||
// SupplyDemandResponse represents supply and demand data.
|
||||
type SupplyDemandResponse struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Data []SupplyDemandItem `json:"data"`
|
||||
}
|
||||
|
||||
// SupplyDemandItem represents a single supply/demand data point (bsa-month).
|
||||
type SupplyDemandItem struct {
|
||||
BUP float64 `json:"bup"`
|
||||
SDP float64 `json:"sdp"`
|
||||
BSR float64 `json:"bsr"`
|
||||
T string `json:"t"`
|
||||
}
|
||||
|
||||
// SupplyDemand15mItem represents a 15-minute supply/demand data point (bsa, bsa-ext).
|
||||
type SupplyDemand15mItem struct {
|
||||
BU float64 `json:"bu"`
|
||||
BMS float64 `json:"bms"`
|
||||
BUP float64 `json:"bup"`
|
||||
SD float64 `json:"sd"`
|
||||
SMS string `json:"sms"`
|
||||
SDP float64 `json:"sdp"`
|
||||
BSR float64 `json:"bsr"`
|
||||
T string `json:"t"`
|
||||
S int64 `json:"s"`
|
||||
}
|
||||
|
||||
// SupplyDemand15mResponse wraps a list of 15-minute supply/demand items.
|
||||
type SupplyDemand15mResponse struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Data []SupplyDemand15mItem `json:"data"`
|
||||
}
|
||||
|
||||
// --- WebSocket Market DTOs ---
|
||||
|
||||
// WSStockInfo represents stock information from WebSocket stream.
|
||||
type WSStockInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
CeilPrice float64 `json:"ceilPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
BidPrice01 float64 `json:"bidPrice01"`
|
||||
BidPrice02 float64 `json:"bidPrice02"`
|
||||
BidPrice03 float64 `json:"bidPrice03"`
|
||||
BidQtty01 float64 `json:"bidQtty01"`
|
||||
BidQtty02 float64 `json:"bidQtty02"`
|
||||
BidQtty03 float64 `json:"bidQtty03"`
|
||||
OfferPrice01 float64 `json:"offerPrice01"`
|
||||
OfferPrice02 float64 `json:"offerPrice02"`
|
||||
OfferPrice03 float64 `json:"offerPrice03"`
|
||||
OfferQtty01 float64 `json:"offerQtty01"`
|
||||
OfferQtty02 float64 `json:"offerQtty02"`
|
||||
OfferQtty03 float64 `json:"offerQtty03"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
MatchQtty float64 `json:"matchQtty"`
|
||||
Change float64 `json:"change"`
|
||||
ChangePercent float64 `json:"changePercent"`
|
||||
Open float64 `json:"open"`
|
||||
High float64 `json:"high"`
|
||||
Low float64 `json:"low"`
|
||||
TotalVol float64 `json:"totalVol"`
|
||||
TotalVal float64 `json:"totalVal"`
|
||||
OpenVol float64 `json:"openVol"`
|
||||
BuyForeignQtty float64 `json:"buyForeignQtty"`
|
||||
SellForeignQtty float64 `json:"sellForeignQtty"`
|
||||
Room string `json:"room"`
|
||||
Avg float64 `json:"avg"`
|
||||
IndexNumber float64 `json:"indexNumber"`
|
||||
}
|
||||
|
||||
// WSDerivativeInfo represents derivative information from WebSocket stream.
|
||||
type WSDerivativeInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
CeilPrice float64 `json:"ceilPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
BidPrice01 float64 `json:"bidPrice01"`
|
||||
BidPrice02 float64 `json:"bidPrice02"`
|
||||
BidPrice03 float64 `json:"bidPrice03"`
|
||||
BidQtty01 float64 `json:"bidQtty01"`
|
||||
BidQtty02 float64 `json:"bidQtty02"`
|
||||
BidQtty03 float64 `json:"bidQtty03"`
|
||||
OfferPrice01 float64 `json:"offerPrice01"`
|
||||
OfferPrice02 float64 `json:"offerPrice02"`
|
||||
OfferPrice03 float64 `json:"offerPrice03"`
|
||||
OfferQtty01 float64 `json:"offerQtty01"`
|
||||
OfferQtty02 float64 `json:"offerQtty02"`
|
||||
OfferQtty03 float64 `json:"offerQtty03"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
MatchQtty float64 `json:"matchQtty"`
|
||||
Change float64 `json:"change"`
|
||||
ChangePercent float64 `json:"changePercent"`
|
||||
Open float64 `json:"open"`
|
||||
High float64 `json:"high"`
|
||||
Low float64 `json:"low"`
|
||||
TotalVol float64 `json:"totalVol"`
|
||||
OpenVol float64 `json:"openVol"`
|
||||
BuyForeignQtty float64 `json:"buyForeignQtty"`
|
||||
SellForeignQtty float64 `json:"sellForeignQtty"`
|
||||
ExpiryDate string `json:"expiryDate"`
|
||||
Avg float64 `json:"avg"`
|
||||
}
|
||||
|
||||
// WSForeignIndexInfo represents foreign index information from WebSocket stream.
|
||||
type WSForeignIndexInfo struct {
|
||||
Symbol string `json:"symbol"`
|
||||
CeilPrice float64 `json:"ceilPrice"`
|
||||
FloorPrice float64 `json:"floorPrice"`
|
||||
RefPrice float64 `json:"refPrice"`
|
||||
BidPrice01 float64 `json:"bidPrice01"`
|
||||
BidPrice02 float64 `json:"bidPrice02"`
|
||||
BidPrice03 float64 `json:"bidPrice03"`
|
||||
BidQtty01 float64 `json:"bidQtty01"`
|
||||
BidQtty02 float64 `json:"bidQtty02"`
|
||||
BidQtty03 float64 `json:"bidQtty03"`
|
||||
OfferPrice01 float64 `json:"offerPrice01"`
|
||||
OfferPrice02 float64 `json:"offerPrice02"`
|
||||
OfferPrice03 float64 `json:"offerPrice03"`
|
||||
OfferQtty01 float64 `json:"offerQtty01"`
|
||||
OfferQtty02 float64 `json:"offerQtty02"`
|
||||
OfferQtty03 float64 `json:"offerQtty03"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
MatchQtty float64 `json:"matchQtty"`
|
||||
Change float64 `json:"change"`
|
||||
ChangePercent float64 `json:"changePercent"`
|
||||
Open float64 `json:"open"`
|
||||
High float64 `json:"high"`
|
||||
Low float64 `json:"low"`
|
||||
TotalVolume float64 `json:"totalVolume"`
|
||||
TotalValue float64 `json:"totalValue"`
|
||||
BuyForeignQtty float64 `json:"buyForeignQtty"`
|
||||
SellForeignQtty float64 `json:"sellForeignQtty"`
|
||||
Room string `json:"room"`
|
||||
Avg float64 `json:"avg"`
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package tcbs
|
||||
|
||||
// MoneyTransferRequest represents an internal money transfer request.
|
||||
type MoneyTransferRequest struct {
|
||||
SourceAccountNumber string `json:"sourceAccountNumber"`
|
||||
DestinationAccountNumber string `json:"destinationAccountNumber"`
|
||||
Amount float64 `json:"amount"`
|
||||
Description float64 `json:"description"` // number type per spec
|
||||
}
|
||||
|
||||
// MoneyTransferResponse represents the transfer response.
|
||||
type MoneyTransferResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// MarginDepositRequest represents a margin deposit request for derivative accounts.
|
||||
type MarginDepositRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Amount float64 `json:"amount"`
|
||||
PaymentContent float64 `json:"paymentContent"`
|
||||
}
|
||||
|
||||
// MarginWithdrawRequest represents a margin withdrawal request for derivative accounts.
|
||||
type MarginWithdrawRequest struct {
|
||||
AccountID string `json:"accountId"`
|
||||
SubAccountID string `json:"subAccountId"`
|
||||
Amount float64 `json:"amount"`
|
||||
PaymentContent float64 `json:"paymentContent"`
|
||||
}
|
||||
|
||||
// MarginDepositResponse represents the deposit response.
|
||||
type MarginDepositResponse struct {
|
||||
TransactionID string `json:"transactionId"`
|
||||
}
|
||||
|
||||
// MarginWithdrawResponse represents the withdrawal response.
|
||||
type MarginWithdrawResponse struct {
|
||||
TransactionID string `json:"transactionId"`
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
package tcbs
|
||||
|
||||
// --- Place Order ---
|
||||
|
||||
// PlaceOrderRequest represents a stock order placement request.
|
||||
type PlaceOrderRequest struct {
|
||||
ExecType string `json:"execType"`
|
||||
Price int `json:"price"`
|
||||
PriceType string `json:"priceType"`
|
||||
Quantity int `json:"quantity"`
|
||||
Symbol string `json:"symbol"`
|
||||
}
|
||||
|
||||
// PlaceOrderResponse represents the response after placing a stock order.
|
||||
type PlaceOrderResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
OrderID string `json:"orderId"`
|
||||
}
|
||||
|
||||
// --- Update Order ---
|
||||
|
||||
// UpdateOrderRequest represents a stock order update request.
|
||||
type UpdateOrderRequest struct {
|
||||
Price int `json:"price"`
|
||||
Quantity int `json:"quantity"`
|
||||
}
|
||||
|
||||
// UpdateOrderResponse represents the response after updating a stock order.
|
||||
type UpdateOrderResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
OrderID string `json:"orderId"`
|
||||
}
|
||||
|
||||
// --- Cancel Order ---
|
||||
|
||||
// CancelOrderRequest represents a stock order cancellation request.
|
||||
type CancelOrderRequest struct {
|
||||
OrdersList []OrderIDRef `json:"ordersList"`
|
||||
}
|
||||
|
||||
// OrderIDRef represents an order ID reference used in cancel requests.
|
||||
type OrderIDRef struct {
|
||||
OrderID string `json:"orderID"`
|
||||
}
|
||||
|
||||
// CancelOrderResponse represents the response after cancelling stock orders.
|
||||
type CancelOrderResponse struct {
|
||||
Object string `json:"object"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex int `json:"pageIndex"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Data []DataX `json:"data"`
|
||||
}
|
||||
|
||||
// DataX holds cancel order result details.
|
||||
type DataX struct {
|
||||
Object string `json:"object"`
|
||||
Details []Detail `json:"details"`
|
||||
}
|
||||
|
||||
// Detail holds a single order cancellation result.
|
||||
type Detail struct {
|
||||
Deleted string `json:"deleted"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMessage string `json:"errorMesage"` // note: typo in spec
|
||||
OrderID string `json:"orderID"`
|
||||
}
|
||||
|
||||
// --- Order Query ---
|
||||
|
||||
// OrderSearchResponse represents the order book response.
|
||||
type OrderSearchResponse struct {
|
||||
Object string `json:"object"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex int `json:"pageIndex"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Data []OrderInfo `json:"data"`
|
||||
}
|
||||
|
||||
// OrderInfo represents a single order in the order book.
|
||||
type OrderInfo struct {
|
||||
Object string `json:"object"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
OrderID string `json:"orderID"`
|
||||
ExecType string `json:"execType"`
|
||||
OrderQtty float64 `json:"orderQtty"`
|
||||
ExecQtty float64 `json:"execQtty"`
|
||||
CodeID string `json:"codeID"`
|
||||
Symbol string `json:"symbol"`
|
||||
PriceType string `json:"priceType"`
|
||||
TxTime string `json:"txtime"`
|
||||
TxDate string `json:"txdate"`
|
||||
ExpDate string `json:"expDate"`
|
||||
TimeType string `json:"timeType"`
|
||||
OrStatus string `json:"orStatus"`
|
||||
FeeAcr float64 `json:"feeAcr"`
|
||||
LimitPrice float64 `json:"limitPrice"`
|
||||
CancelQtty float64 `json:"cancelQtty"`
|
||||
RemainQtty float64 `json:"remainQtty"`
|
||||
Via string `json:"via"`
|
||||
QuotePrice float64 `json:"quotePrice"`
|
||||
MatchPrice float64 `json:"matchPrice"`
|
||||
TradePlace string `json:"tradePlace"`
|
||||
MatchType string `json:"matchType"`
|
||||
IsDisposal string `json:"isDisposal"`
|
||||
IsCancel string `json:"isCancel"`
|
||||
IsAmend string `json:"isAmend"`
|
||||
UserName string `json:"userName"`
|
||||
OrsOrderID string `json:"orsOrderID"`
|
||||
SecType string `json:"sectype"`
|
||||
IsFOOrder string `json:"isFOOrder"`
|
||||
OdTimeStamp string `json:"odTimeStamp"`
|
||||
MatchAmount float64 `json:"matchAmount"`
|
||||
MMType string `json:"mmType"`
|
||||
BRatio float64 `json:"bRatio"`
|
||||
TaxSellAmt float64 `json:"taxSellAmout"` // note: typo in spec
|
||||
}
|
||||
|
||||
// --- Match Information ---
|
||||
|
||||
// CommandMatchInformationResponse represents matching details.
|
||||
type CommandMatchInformationResponse struct {
|
||||
Object string `json:"object"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
PageSize int `json:"pageSize"`
|
||||
PageIndex int `json:"pageIndex"`
|
||||
Data []CommandMatchInformationDetail `json:"data"`
|
||||
}
|
||||
|
||||
// CommandMatchInformationDetail represents a single matching detail.
|
||||
type CommandMatchInformationDetail struct {
|
||||
OrderID string `json:"orderId"`
|
||||
Side string `json:"side"`
|
||||
Symbol string `json:"symbol"`
|
||||
QuoteQtty float64 `json:"quoteQtty"`
|
||||
QuotePrice float64 `json:"quotePrice"`
|
||||
TradeID string `json:"tradeId"`
|
||||
Qtty float64 `json:"qtty"`
|
||||
Price float64 `json:"price"`
|
||||
TimeExec float64 `json:"timeExec"`
|
||||
}
|
||||
|
||||
// --- Purchasing Power ---
|
||||
|
||||
// PurchasingPowerResponse represents purchasing power information.
|
||||
type PurchasingPowerResponse struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
CustodyID string `json:"custodyID"`
|
||||
Symbol string `json:"symbol"`
|
||||
Price float64 `json:"price"`
|
||||
PP0 float64 `json:"pp0"`
|
||||
PPSE float64 `json:"ppse"`
|
||||
PPSERef float64 `json:"ppseref"`
|
||||
MaxBuyQuantity float64 `json:"maxBuyQuantity"`
|
||||
RealMaxBuyQty float64 `json:"realMaxBuyQuantity"`
|
||||
MinBuyQuantity float64 `json:"minBuyQuantity"`
|
||||
MarginRatioLoan float64 `json:"marginRatioLoan"`
|
||||
MarginPriceLoan float64 `json:"marginPriceLoan"`
|
||||
RateBrkS string `json:"rateBrkS"`
|
||||
RateBrkB string `json:"rateBrkB"`
|
||||
}
|
||||
|
||||
// --- Margin ---
|
||||
|
||||
// MarginQuotaResponse represents margin quota information.
|
||||
type MarginQuotaResponse struct {
|
||||
CustodyID string `json:"custodyID"`
|
||||
AccountNo string `json:"accountNo"`
|
||||
AFType string `json:"aftype"`
|
||||
VSDStatus string `json:"vsdStatus"`
|
||||
AccountStatus string `json:"accountStatus"`
|
||||
MarginLimit float64 `json:"marginLimit"`
|
||||
IsIA string `json:"isIA"`
|
||||
BankName string `json:"bankName"`
|
||||
BankAccount string `json:"bankAccount"`
|
||||
AccountType string `json:"accountType"`
|
||||
}
|
||||
|
||||
// MarginAccountInfoResponse represents margin account details.
|
||||
type MarginAccountInfoResponse struct {
|
||||
AccountNo string `json:"accountNo"`
|
||||
RiskPolicy *RiskPolicy `json:"riskPolicy,omitempty"`
|
||||
RTT float64 `json:"rtt"`
|
||||
Outstanding float64 `json:"outstanding"`
|
||||
AccruedInterest float64 `json:"accruedInterest"`
|
||||
DueAmount float64 `json:"dueAmount"`
|
||||
OverdueAmount float64 `json:"overdueAmount"`
|
||||
RiskStatus *RiskStatus `json:"riskStatus,omitempty"`
|
||||
TotalFeeDebt float64 `json:"totalFeeDebt"`
|
||||
}
|
||||
|
||||
// RiskPolicy represents margin risk policy parameters.
|
||||
type RiskPolicy struct {
|
||||
MaintenanceMargin float64 `json:"maintenanceMargin"`
|
||||
InitialMargin float64 `json:"initialMargin"`
|
||||
LiquidationMargin float64 `json:"liquidationMargin"`
|
||||
}
|
||||
|
||||
// RiskStatus represents RTT status.
|
||||
type RiskStatus struct {
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
@@ -13,8 +13,8 @@ func (c *Client) TransferMoney(ctx context.Context, req *MoneyTransferRequest) (
|
||||
}
|
||||
|
||||
// WithdrawMargin withdraws margin for derivative accounts.
|
||||
func (c *Client) WithdrawMargin(ctx context.Context, req *MarginDepositWithdrawRequest) (*MarginDepositWithdrawResponse, error) {
|
||||
var resp MarginDepositWithdrawResponse
|
||||
func (c *Client) WithdrawMargin(ctx context.Context, req *MarginWithdrawRequest) (*MarginWithdrawResponse, error) {
|
||||
var resp MarginWithdrawResponse
|
||||
err := c.post(ctx, "/khronos/v1/cash/withdraw/update", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -23,8 +23,8 @@ func (c *Client) WithdrawMargin(ctx context.Context, req *MarginDepositWithdrawR
|
||||
}
|
||||
|
||||
// DepositMargin deposits margin for derivative accounts.
|
||||
func (c *Client) DepositMargin(ctx context.Context, req *MarginDepositWithdrawRequest) (*MarginDepositWithdrawResponse, error) {
|
||||
var resp MarginDepositWithdrawResponse
|
||||
func (c *Client) DepositMargin(ctx context.Context, req *MarginDepositRequest) (*MarginDepositResponse, error) {
|
||||
var resp MarginDepositResponse
|
||||
err := c.post(ctx, "/khronos/v1/cash/deposit/update", req, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransferMoney(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/physis/v1/stock/transfer" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
var req MoneyTransferRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("failed to decode: %v", err)
|
||||
}
|
||||
if req.SourceAccountNumber != "ACC1" || req.Amount != 1000000 {
|
||||
t.Errorf("unexpected request: %+v", req)
|
||||
}
|
||||
writeJSON(t, w, MoneyTransferResponse{Status: "ok"})
|
||||
})
|
||||
|
||||
resp, err := client.TransferMoney(context.Background(), &MoneyTransferRequest{
|
||||
SourceAccountNumber: "ACC1",
|
||||
DestinationAccountNumber: "ACC2",
|
||||
Amount: 1000000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Status != "ok" {
|
||||
t.Errorf("expected status 'ok', got %q", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDepositMargin(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/khronos/v1/cash/deposit/update" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, MarginDepositResponse{TransactionID: "TXN-1"})
|
||||
})
|
||||
|
||||
resp, err := client.DepositMargin(context.Background(), &MarginDepositRequest{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", Amount: 5000000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TransactionID != "TXN-1" {
|
||||
t.Errorf("expected TXN-1, got %q", resp.TransactionID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithdrawMargin(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/khronos/v1/cash/withdraw/update" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, MarginWithdrawResponse{TransactionID: "TXN-2"})
|
||||
})
|
||||
|
||||
resp, err := client.WithdrawMargin(context.Background(), &MarginWithdrawRequest{
|
||||
AccountID: "ACC1", SubAccountID: "SUB1", Amount: 3000000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TransactionID != "TXN-2" {
|
||||
t.Errorf("expected TXN-2, got %q", resp.TransactionID)
|
||||
}
|
||||
}
|
||||
+36
-9
@@ -8,7 +8,6 @@ import (
|
||||
)
|
||||
|
||||
// GetStockPrices retrieves stock ticker information and pricing.
|
||||
// tickers is a comma-separated list of stock symbols.
|
||||
func (c *Client) GetStockPrices(ctx context.Context, tickers []string) ([]MarketStockInfo, error) {
|
||||
query := url.Values{}
|
||||
query.Set("tickers", strings.Join(tickers, ","))
|
||||
@@ -22,7 +21,6 @@ func (c *Client) GetStockPrices(ctx context.Context, tickers []string) ([]Market
|
||||
}
|
||||
|
||||
// GetForeignRoom retrieves foreign investor room information.
|
||||
// tickers is a comma-separated list of stock symbols.
|
||||
func (c *Client) GetForeignRoom(ctx context.Context, tickers []string) ([]ForeignRoomInfo, error) {
|
||||
query := url.Values{}
|
||||
query.Set("tickers", strings.Join(tickers, ","))
|
||||
@@ -35,13 +33,12 @@ func (c *Client) GetForeignRoom(ctx context.Context, tickers []string) ([]Foreig
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetPutThroughInfo retrieves put-through agreement information.
|
||||
// tickers is a comma-separated list of stock symbols.
|
||||
func (c *Client) GetPutThroughInfo(ctx context.Context, tickers []string) ([]PutThroughInfo, error) {
|
||||
// GetPutThroughInfo retrieves put-through match information.
|
||||
func (c *Client) GetPutThroughInfo(ctx context.Context, tickers []string) ([]PutThroughMatchInfo, error) {
|
||||
query := url.Values{}
|
||||
query.Set("tickers", strings.Join(tickers, ","))
|
||||
|
||||
var resp []PutThroughInfo
|
||||
var resp []PutThroughMatchInfo
|
||||
err := c.get(ctx, "/tartarus/v1/putThroughSnaps", query, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -70,18 +67,48 @@ func (c *Client) GetIntradayHistory(ctx context.Context, params IntradayHistoryP
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetSupplyDemand retrieves supply and demand data for a ticker.
|
||||
// GetSupplyDemand retrieves supply and demand data for a ticker (15-minute intervals).
|
||||
// investorType is one of: "sheep", "wolf", "shark", "all".
|
||||
func (c *Client) GetSupplyDemand(ctx context.Context, ticker, investorType string) (*SupplyDemandResponse, error) {
|
||||
func (c *Client) GetSupplyDemand(ctx context.Context, ticker, investorType string) (*SupplyDemand15mResponse, error) {
|
||||
query := url.Values{}
|
||||
if investorType != "" {
|
||||
query.Set("type", investorType)
|
||||
}
|
||||
|
||||
var resp SupplyDemandResponse
|
||||
var resp SupplyDemand15mResponse
|
||||
err := c.get(ctx, fmt.Sprintf("/nyx/v1/intraday/%s/bsa", ticker), query, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetSupplyDemandExt retrieves extended supply and demand data for a ticker (15-minute intervals).
|
||||
func (c *Client) GetSupplyDemandExt(ctx context.Context, ticker, investorType string) (*SupplyDemand15mResponse, error) {
|
||||
query := url.Values{}
|
||||
if investorType != "" {
|
||||
query.Set("type", investorType)
|
||||
}
|
||||
|
||||
var resp SupplyDemand15mResponse
|
||||
err := c.get(ctx, fmt.Sprintf("/nyx/v1/intraday/%s/bsa-ext", ticker), query, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetSupplyDemandMonth retrieves monthly supply and demand data for a ticker.
|
||||
func (c *Client) GetSupplyDemandMonth(ctx context.Context, ticker, investorType string) (*SupplyDemandResponse, error) {
|
||||
query := url.Values{}
|
||||
if investorType != "" {
|
||||
query.Set("type", investorType)
|
||||
}
|
||||
|
||||
var resp SupplyDemandResponse
|
||||
err := c.get(ctx, fmt.Sprintf("/nyx/v1/intraday/%s/bsa-month", ticker), query, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetStockPrices(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/tartarus/v1/tickerCommons" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
tickers := r.URL.Query().Get("tickers")
|
||||
if tickers != "FPT,VNM" {
|
||||
t.Errorf("unexpected tickers: %s", tickers)
|
||||
}
|
||||
writeJSON(t, w, []MarketStockInfo{
|
||||
{Ticker: "FPT", MatchPrice: 120000},
|
||||
{Ticker: "VNM", MatchPrice: 80000},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetStockPrices(context.Background(), []string{"FPT", "VNM"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp) != 2 {
|
||||
t.Fatalf("expected 2 items, got %d", len(resp))
|
||||
}
|
||||
if resp[0].Ticker != "FPT" || resp[0].MatchPrice != 120000 {
|
||||
t.Errorf("unexpected FPT data: %+v", resp[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIntradayHistory(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/nyx/v1/intraday/FPT/his/paging" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, IntradayHistoryResponse{
|
||||
Ticker: "FPT",
|
||||
Data: []IntradayHistoryItem{{P: 120000, V: 100}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetIntradayHistory(context.Background(), IntradayHistoryParams{
|
||||
Ticker: "FPT", Page: 0, Size: 20,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Data) != 1 {
|
||||
t.Errorf("expected 1 item, got %d", len(resp.Data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSupplyDemand(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/nyx/v1/intraday/FPT/bsa" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, SupplyDemand15mResponse{
|
||||
Ticker: "FPT",
|
||||
Data: []SupplyDemand15mItem{{BU: 100, SD: 50}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetSupplyDemand(context.Background(), "FPT", "all")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Data) != 1 {
|
||||
t.Errorf("expected 1 item, got %d", len(resp.Data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSupplyDemandExt(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/nyx/v1/intraday/FPT/bsa-ext" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, SupplyDemand15mResponse{Ticker: "FPT"})
|
||||
})
|
||||
|
||||
_, err := client.GetSupplyDemandExt(context.Background(), "FPT", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSupplyDemandMonth(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/nyx/v1/intraday/FPT/bsa-month" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, SupplyDemandResponse{Ticker: "FPT"})
|
||||
})
|
||||
|
||||
_, err := client.GetSupplyDemandMonth(context.Background(), "FPT", "all")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetForeignRoom(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, []ForeignRoomInfo{{Ticker: "FPT", TotalRoom: 1000}})
|
||||
})
|
||||
|
||||
resp, err := client.GetForeignRoom(context.Background(), []string{"FPT"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp) != 1 || resp[0].Ticker != "FPT" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPutThroughInfo(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, []PutThroughMatchInfo{{Symbol: "FPT", Vol: 500}})
|
||||
})
|
||||
|
||||
resp, err := client.GetPutThroughInfo(context.Background(), []string{"FPT"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp) != 1 || resp[0].Symbol != "FPT" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -25,7 +25,7 @@ func (c *Client) UpdateOrder(ctx context.Context, accountNo, orderID string, req
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// CancelOrder cancels an existing stock order.
|
||||
// CancelOrder cancels existing stock orders.
|
||||
func (c *Client) CancelOrder(ctx context.Context, accountNo string, req *CancelOrderRequest) (*CancelOrderResponse, error) {
|
||||
var resp CancelOrderResponse
|
||||
err := c.put(ctx, fmt.Sprintf("/akhlys/v1/accounts/%s/cancel-orders", accountNo), req, &resp)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlaceOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/akhlys/v1/accounts/ACC001/orders" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
var req PlaceOrderRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("failed to decode request: %v", err)
|
||||
}
|
||||
if req.Symbol != "FPT" || req.Quantity != 100 {
|
||||
t.Errorf("unexpected request: %+v", req)
|
||||
}
|
||||
writeJSON(t, w, PlaceOrderResponse{OrderID: "ORD-1"})
|
||||
})
|
||||
|
||||
resp, err := client.PlaceOrder(context.Background(), "ACC001", &PlaceOrderRequest{
|
||||
Symbol: "FPT",
|
||||
ExecType: "NB",
|
||||
Quantity: 100,
|
||||
Price: 120000,
|
||||
PriceType: "LO",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.OrderID != "ORD-1" {
|
||||
t.Errorf("expected order ID 'ORD-1', got %q", resp.OrderID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
writeJSON(t, w, UpdateOrderResponse{OrderID: "ORD-1", Message: "ok"})
|
||||
})
|
||||
|
||||
resp, err := client.UpdateOrder(context.Background(), "ACC001", "ORD-1", &UpdateOrderRequest{
|
||||
Price: 125000, Quantity: 200,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.OrderID != "ORD-1" {
|
||||
t.Errorf("expected 'ORD-1', got %q", resp.OrderID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelOrder(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
writeJSON(t, w, CancelOrderResponse{TotalCount: 1})
|
||||
})
|
||||
|
||||
resp, err := client.CancelOrder(context.Background(), "ACC001", &CancelOrderRequest{
|
||||
OrdersList: []OrderIDRef{{OrderID: "ORD-1"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TotalCount != 1 {
|
||||
t.Errorf("expected totalCount 1, got %d", resp.TotalCount)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetOrders(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/aion/v1/accounts/ACC001/orders" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, OrderSearchResponse{TotalCount: 5})
|
||||
})
|
||||
|
||||
resp, err := client.GetOrders(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TotalCount != 5 {
|
||||
t.Errorf("expected 5, got %d", resp.TotalCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPurchasingPower(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, PurchasingPowerResponse{PP0: 100000000})
|
||||
})
|
||||
|
||||
resp, err := client.GetPurchasingPower(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.PP0 != 100000000 {
|
||||
t.Errorf("expected PP0=100000000, got %.0f", resp.PP0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarginQuota(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, []MarginQuotaResponse{{AccountNo: "ACC001", MarginLimit: 500000}})
|
||||
})
|
||||
|
||||
resp, err := client.GetMarginQuota(context.Background(), "CUS001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp) != 1 || resp[0].AccountNo != "ACC001" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStockAssets(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, SeInfoDTO{
|
||||
AccountNo: "ACC001",
|
||||
Stock: []StockHoldingInfo{{Symbol: "FPT", TotalQtty: 1000}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetStockAssets(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.Stock) != 1 || resp.Stock[0].Symbol != "FPT" {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderByID(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/aion/v1/accounts/ACC001/orders/ORD-1" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, OrderSearchResponse{TotalCount: 1})
|
||||
})
|
||||
|
||||
resp, err := client.GetOrderByID(context.Background(), "ACC001", "ORD-1")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TotalCount != 1 {
|
||||
t.Errorf("expected 1, got %d", resp.TotalCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatchingDetails(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, CommandMatchInformationResponse{TotalCount: 2})
|
||||
})
|
||||
|
||||
resp, err := client.GetMatchingDetails(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TotalCount != 2 {
|
||||
t.Errorf("expected 2, got %d", resp.TotalCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPurchasingPowerBySymbol(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, PurchasingPowerResponse{PP0: 50000000})
|
||||
})
|
||||
|
||||
resp, err := client.GetPurchasingPowerBySymbol(context.Background(), "ACC001", "FPT")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.PP0 != 50000000 {
|
||||
t.Errorf("unexpected PP0: %.0f", resp.PP0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPurchasingPowerBySymbolPrice(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, PurchasingPowerResponse{MaxBuyQuantity: 100})
|
||||
})
|
||||
|
||||
resp, err := client.GetPurchasingPowerBySymbolPrice(context.Background(), "ACC001", "FPT", "120000")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.MaxBuyQuantity != 100 {
|
||||
t.Errorf("unexpected MaxBuyQuantity: %.0f", resp.MaxBuyQuantity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarginAccountInfo(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, []MarginAccountInfoResponse{{AccountNo: "ACC001", RTT: 1.5}})
|
||||
})
|
||||
|
||||
resp, err := client.GetMarginAccountInfo(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp) != 1 || resp[0].RTT != 1.5 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSupplementaryLoanPackages(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, SupplementaryLoanPackageResponse{
|
||||
MarginSureViews: []MarginSureView{{Name: "pkg1"}},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetSupplementaryLoanPackages(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(resp.MarginSureViews) != 1 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLoans(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, LoanResponse{Size: 1, Content: []LoanItem{{Symbol: "FPT"}}})
|
||||
})
|
||||
|
||||
resp, err := client.GetLoans(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Size != 1 {
|
||||
t.Errorf("expected size 1, got %d", resp.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCashBalance(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, CashInvestmentResponse{TotalCount: 1})
|
||||
})
|
||||
|
||||
resp, err := client.GetCashBalance(context.Background(), "ACC001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.TotalCount != 1 {
|
||||
t.Errorf("expected 1, got %d", resp.TotalCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarginInfo(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(t, w, MarginInfoResponse{
|
||||
Response: &MarginInfoData{TotalRow: 3},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetMarginInfo(context.Background(), MarginInfoParams{
|
||||
AccountNo: "ACC001", FromDate: "2025-01-01", ToDate: "2025-01-31",
|
||||
Page: "0", Size: "10", CustodyCD: "CUS001",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Response == nil || resp.Response.TotalRow != 3 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCashStatements(t *testing.T) {
|
||||
client, _ := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/erebos/v2/digital/trans-hist-cashStatements" {
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
if r.URL.Query().Get("fromDate") != "2025-01-01" {
|
||||
t.Errorf("unexpected fromDate: %s", r.URL.Query().Get("fromDate"))
|
||||
}
|
||||
writeJSON(t, w, TransHistCashStatementsResponse{
|
||||
Response: &TransHistCashStatementsData{TotalCount: 3},
|
||||
})
|
||||
})
|
||||
|
||||
resp, err := client.GetCashStatements(context.Background(), CashStatementParams{
|
||||
AccountNo: "ACC001", FromDate: "2025-01-01", ToDate: "2025-01-31",
|
||||
PageSize: "10", PageIndex: "0", TransactionCode: "",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.Response == nil || resp.Response.TotalCount != 3 {
|
||||
t.Errorf("unexpected response: %+v", resp)
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
package tcbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"nhooyr.io/websocket"
|
||||
)
|
||||
|
||||
// WSEndpoint represents a known WebSocket endpoint.
|
||||
type WSEndpoint string
|
||||
|
||||
const (
|
||||
// WSStockMatch is the WebSocket endpoint for stock match information.
|
||||
WSStockMatch WSEndpoint = "/ws/aither"
|
||||
// WSDerivativeMatch is the WebSocket endpoint for derivative match information.
|
||||
WSDerivativeMatch WSEndpoint = "/ws/nesoi"
|
||||
// WSCenter is the general WebSocket center endpoint.
|
||||
WSCenter WSEndpoint = "/ws/ouranos/v1/stream"
|
||||
// WSStockPrice is the WebSocket endpoint for normal stock prices.
|
||||
WSStockPrice WSEndpoint = "/ws/thesis/v1/stream/normal"
|
||||
// WSDerivativePrice is the WebSocket endpoint for derivative prices.
|
||||
WSDerivativePrice WSEndpoint = "/ws/thesis/v1/stream/derivative"
|
||||
)
|
||||
|
||||
// MessageHandler is a callback invoked for each received WebSocket message.
|
||||
type MessageHandler func(msgType websocket.MessageType, data []byte)
|
||||
|
||||
// WSConn represents a managed WebSocket connection.
|
||||
type WSConn struct {
|
||||
conn *websocket.Conn
|
||||
cancel context.CancelFunc
|
||||
done chan struct{}
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Close gracefully closes the WebSocket connection.
|
||||
func (ws *WSConn) Close() error {
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
if ws.closed {
|
||||
return nil
|
||||
}
|
||||
ws.closed = true
|
||||
ws.cancel()
|
||||
<-ws.done
|
||||
return ws.conn.Close(websocket.StatusNormalClosure, "client closed")
|
||||
}
|
||||
|
||||
// Send sends a text message over the WebSocket connection.
|
||||
func (ws *WSConn) Send(ctx context.Context, msg []byte) error {
|
||||
return ws.conn.Write(ctx, websocket.MessageText, msg)
|
||||
}
|
||||
|
||||
// SendJSON marshals v to JSON and sends it as a text message.
|
||||
func (ws *WSConn) SendJSON(ctx context.Context, v any) error {
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tcbs: marshal ws message: %w", err)
|
||||
}
|
||||
return ws.conn.Write(ctx, websocket.MessageText, data)
|
||||
}
|
||||
|
||||
// ConnectWS establishes a WebSocket connection to the given endpoint.
|
||||
// The handler is called for each message received. The connection reads
|
||||
// messages in a background goroutine until the context is cancelled or
|
||||
// Close is called.
|
||||
func (c *Client) ConnectWS(ctx context.Context, endpoint WSEndpoint, handler MessageHandler) (*WSConn, error) {
|
||||
wsURL := c.baseURL + string(endpoint)
|
||||
wsURL = strings.Replace(wsURL, "https://", "wss://", 1)
|
||||
wsURL = strings.Replace(wsURL, "http://", "ws://", 1)
|
||||
|
||||
header := http.Header{}
|
||||
if token := c.currentToken(); token != "" {
|
||||
header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
|
||||
conn, _, err := websocket.Dial(ctx, wsURL, &websocket.DialOptions{
|
||||
HTTPHeader: header,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tcbs: ws dial %s: %w", endpoint, err)
|
||||
}
|
||||
|
||||
readCtx, cancel := context.WithCancel(ctx)
|
||||
done := make(chan struct{})
|
||||
|
||||
ws := &WSConn{
|
||||
conn: conn,
|
||||
cancel: cancel,
|
||||
done: done,
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
for {
|
||||
msgType, data, err := conn.Read(readCtx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
handler(msgType, data)
|
||||
}
|
||||
}()
|
||||
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
// ConnectStockMatch connects to the stock match information WebSocket.
|
||||
func (c *Client) ConnectStockMatch(ctx context.Context, handler MessageHandler) (*WSConn, error) {
|
||||
return c.ConnectWS(ctx, WSStockMatch, handler)
|
||||
}
|
||||
|
||||
// ConnectDerivativeMatch connects to the derivative match information WebSocket.
|
||||
func (c *Client) ConnectDerivativeMatch(ctx context.Context, handler MessageHandler) (*WSConn, error) {
|
||||
return c.ConnectWS(ctx, WSDerivativeMatch, handler)
|
||||
}
|
||||
|
||||
// ConnectCenter connects to the general WebSocket center.
|
||||
func (c *Client) ConnectCenter(ctx context.Context, handler MessageHandler) (*WSConn, error) {
|
||||
return c.ConnectWS(ctx, WSCenter, handler)
|
||||
}
|
||||
|
||||
// ConnectStockPrice connects to the normal stock price WebSocket.
|
||||
func (c *Client) ConnectStockPrice(ctx context.Context, handler MessageHandler) (*WSConn, error) {
|
||||
return c.ConnectWS(ctx, WSStockPrice, handler)
|
||||
}
|
||||
|
||||
// ConnectDerivativePrice connects to the derivative price WebSocket.
|
||||
func (c *Client) ConnectDerivativePrice(ctx context.Context, handler MessageHandler) (*WSConn, error) {
|
||||
return c.ConnectWS(ctx, WSDerivativePrice, handler)
|
||||
}
|
||||
Reference in New Issue
Block a user