Skip to content

Commit 44aa610

Browse files
authored
Merge pull request #2 from scripbox/feature/fetch-funds-and-margin
Add funds and margins API
2 parents d284829 + e025a9b commit 44aa610

File tree

8 files changed

+256
-1
lines changed

8 files changed

+256
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ end
3333
- Cancel an Order
3434
- Retrieve orders
3535
- Retrieve holdings
36+
- Retrieve funds and margins
3637
- Retrieve positions
3738
- Postback
3839
- Logout

lib/kite_connect_ex.ex

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,14 @@ defmodule KiteConnectEx do
9494
"""
9595
@spec instruments(String.t(), String.t()) :: {:ok, List.t()} | Response.error()
9696
defdelegate instruments(access_token, exchange), to: Instrument
97+
98+
@doc """
99+
Get user's margins `funds_and_margins`
100+
101+
## Example
102+
103+
{:ok, funds_and_margins} = KiteConnectEx.funds_and_margins("access-token", "equity")
104+
"""
105+
@spec funds_and_margins(String.t(), String.t()) :: {:ok, List.t()} | Response.error()
106+
defdelegate funds_and_margins(access_token, segment_type), to: User
97107
end

lib/kite_connect_ex/response.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ defmodule KiteConnectEx.Response do
77
@success_status "success"
88
@error_status "error"
99

10-
1110
@csv_content_type {"Content-Type", "text/csv"}
1211

1312
@doc false

lib/kite_connect_ex/user.ex

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ defmodule KiteConnectEx.User do
66
"""
77

88
alias KiteConnectEx.Request
9+
alias KiteConnectEx.User.FundAndMargin
910

1011
@login_url "https://kite.zerodha.com/connect/login"
1112
@create_session_path "/session/token"
13+
@funds_and_margins_path "/user/margins"
1214

1315
@keys [
1416
access_token: nil,
@@ -71,6 +73,32 @@ defmodule KiteConnectEx.User do
7173
{:ok, login_url}
7274
end
7375

76+
@doc """
77+
Get user's funds and margins of segments: "equity" || "commodity"
78+
default segment_type: equity
79+
80+
## Example
81+
82+
{:ok, funds_and_margins} = KiteConnectEx.User.funds_and_margins("access-token")
83+
84+
{:ok, funds_and_margins} = KiteConnectEx.User.funds_and_margins("access-token", "commodity")
85+
"""
86+
@spec funds_and_margins(String.t(), String.t()) :: {:ok, %__MODULE__{}} | Response.error()
87+
def funds_and_margins(access_token, segment_type \\ "equity") when is_binary(access_token) do
88+
url = @funds_and_margins_path <> "/" <> segment_type
89+
90+
Request.get(url, nil, auth_header(access_token), KiteConnectEx.request_options())
91+
|> case do
92+
{:ok, data} ->
93+
funds_and_margins = FundAndMargin.new(data)
94+
95+
{:ok, funds_and_margins}
96+
97+
{:error, error} ->
98+
{:error, error}
99+
end
100+
end
101+
74102
@doc """
75103
Generate `access_token` using the `request_token`
76104
@@ -128,4 +156,8 @@ defmodule KiteConnectEx.User do
128156
defp new(_) do
129157
struct(__MODULE__, %{})
130158
end
159+
160+
defp auth_header(access_token) do
161+
[{"Authorization", "token " <> KiteConnectEx.api_key() <> ":" <> access_token}]
162+
end
131163
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
defmodule KiteConnectEx.User.FundAndMargin do
2+
alias KiteConnectEx.User.FundAndMargin.AvailableSegment
3+
alias KiteConnectEx.User.FundAndMargin.UtilisedSegment
4+
5+
@moduledoc """
6+
Module to represent an fund and margin resource
7+
8+
- [Docs](https://kite.trade/docs/connect/v3/user/#funds-and-margins)
9+
"""
10+
11+
@keys [
12+
enabled: false,
13+
net: nil,
14+
available: nil,
15+
utilised: nil
16+
]
17+
18+
@type t :: %__MODULE__{
19+
enabled: Bool.t(),
20+
net: Float.t(),
21+
available: %AvailableSegment{},
22+
utilised: %UtilisedSegment{}
23+
}
24+
25+
@derive {Jason.Encoder, only: Keyword.keys(@keys)}
26+
defstruct @keys
27+
28+
def new(attributes) when is_map(attributes) do
29+
struct(__MODULE__, %{
30+
enabled: attributes["enabled"],
31+
net: attributes["net"],
32+
available: AvailableSegment.new(attributes["available"]),
33+
utilised: UtilisedSegment.new(attributes["utilised"])
34+
})
35+
end
36+
37+
def new(_) do
38+
struct(__MODULE__, %{})
39+
end
40+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
defmodule KiteConnectEx.User.FundAndMargin.AvailableSegment do
2+
@moduledoc """
3+
Module to represent an available segment resource
4+
5+
- [Docs](https://kite.trade/docs/connect/v3/user/#funds-and-margins)
6+
"""
7+
8+
@keys [
9+
adhoc_margin: nil,
10+
cash: nil,
11+
collateral: nil,
12+
intraday_payin: nil
13+
]
14+
15+
@type t :: %__MODULE__{
16+
adhoc_margin: Float.t(),
17+
cash: Float.t(),
18+
collateral: Float.t(),
19+
intraday_payin: Float.t()
20+
}
21+
22+
@derive {Jason.Encoder, only: Keyword.keys(@keys)}
23+
defstruct @keys
24+
25+
def new(attributes) when is_map(attributes) do
26+
struct(__MODULE__, %{
27+
adhoc_margin: attributes["adhoc_margin"],
28+
cash: attributes["cash"],
29+
collateral: attributes["collateral"],
30+
intraday_payin: attributes["intraday_payin"]
31+
})
32+
end
33+
34+
def new(_) do
35+
struct(__MODULE__, %{})
36+
end
37+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
defmodule KiteConnectEx.User.FundAndMargin.UtilisedSegment do
2+
@moduledoc """
3+
Module to represent an available segment resource
4+
5+
- [Docs](https://kite.trade/docs/connect/v3/user/#funds-and-margins)
6+
"""
7+
8+
@keys [
9+
debits: nil,
10+
exposure: nil,
11+
m2m_realised: nil,
12+
m2m_unrealised: nil,
13+
option_premium: nil,
14+
payout: nil,
15+
span: nil,
16+
holding_sales: nil,
17+
turnover: nil
18+
]
19+
20+
@type t :: %__MODULE__{
21+
debits: Float.t(),
22+
exposure: Float.t(),
23+
m2m_realised: Float.t(),
24+
m2m_unrealised: Float.t(),
25+
option_premium: Float.t(),
26+
payout: Float.t(),
27+
span: Float.t(),
28+
holding_sales: Float.t(),
29+
turnover: Float.t()
30+
}
31+
32+
@derive {Jason.Encoder, only: Keyword.keys(@keys)}
33+
defstruct @keys
34+
35+
def new(attributes) when is_map(attributes) do
36+
struct(__MODULE__, %{
37+
debits: attributes["debits"],
38+
exposure: attributes["exposure"],
39+
m2m_realised: attributes["m2m_realised"],
40+
m2m_unrealised: attributes["m2m_unrealised"],
41+
option_premium: attributes["option_premium"],
42+
payout: attributes["payout"],
43+
span: attributes["span"],
44+
holding_sales: attributes["holding_sales"],
45+
turnover: attributes["turnover"]
46+
})
47+
end
48+
49+
def new(_) do
50+
struct(__MODULE__, %{})
51+
end
52+
end

test/kite_connect_ex_test.exs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,90 @@ defmodule KiteConnectExTest do
148148
"""
149149
end
150150

151+
describe "funds_and_margins/2" do
152+
test "returns funds and margins for equity with valid response", %{bypass: bypass} do
153+
Bypass.expect_once(bypass, "GET", "/user/margins/equity", fn conn ->
154+
Plug.Conn.resp(conn, 201, funds_and_margins_response())
155+
end)
156+
157+
assert KiteConnectEx.funds_and_margins("access-token", "equity") ==
158+
{:ok,
159+
%KiteConnectEx.User.FundAndMargin{
160+
available: %KiteConnectEx.User.FundAndMargin.AvailableSegment{
161+
adhoc_margin: 0,
162+
cash: 622.32,
163+
collateral: 0,
164+
intraday_payin: 0
165+
},
166+
enabled: true,
167+
net: 622.32,
168+
utilised: %KiteConnectEx.User.FundAndMargin.UtilisedSegment{
169+
debits: 0,
170+
exposure: 0,
171+
holding_sales: 0,
172+
m2m_realised: 0,
173+
m2m_unrealised: 0,
174+
option_premium: 0,
175+
payout: 0,
176+
span: 0,
177+
turnover: 0
178+
}
179+
}}
180+
end
181+
182+
test "returns error if API request fails", %{bypass: bypass} do
183+
Bypass.expect_once(bypass, "GET", "/user/margins/equity", fn conn ->
184+
Plug.Conn.resp(
185+
conn,
186+
403,
187+
~s<{"status": "error", "error_type": "TokenException", "message": "Invalid access_token"}>
188+
)
189+
end)
190+
191+
assert KiteConnectEx.funds_and_margins("access-token", "equity") ==
192+
{:error,
193+
%KiteConnectEx.Error{
194+
code: 403,
195+
error_type: "TokenException",
196+
message: "Invalid access_token"
197+
}}
198+
end
199+
200+
defp funds_and_margins_response do
201+
"""
202+
{
203+
"status": "success",
204+
"data": {
205+
"available": {
206+
"adhoc_margin": 0,
207+
"cash": 622.32,
208+
"collateral": 0,
209+
"intraday_payin": 0,
210+
"live_balance": 622.32,
211+
"opening_balance": 622.32
212+
},
213+
"enabled": true,
214+
"net": 622.32,
215+
"utilised": {
216+
"debits": 0,
217+
"delivery": 0,
218+
"exposure": 0,
219+
"holding_sales": 0,
220+
"liquid_collateral": 0,
221+
"m2m_realised": 0,
222+
"m2m_unrealised": 0,
223+
"option_premium": 0,
224+
"payout": 0,
225+
"span": 0,
226+
"stock_collateral": 0,
227+
"turnover": 0
228+
}
229+
}
230+
}
231+
"""
232+
end
233+
end
234+
151235
describe "holdings/1" do
152236
test "returns portfolio holdings with valid access_token", %{bypass: bypass} do
153237
Bypass.expect_once(bypass, "GET", "/portfolio/holdings", fn conn ->

0 commit comments

Comments
 (0)