Skip to content

Commit

Permalink
Add position endpoints
Browse files Browse the repository at this point in the history
Add the position endpoints to list all positions,
get a position by symbol, delete all positions, or delete
a position by symbol. Also refactor out the module
structs for plain json for now. This will speed up development
maybe add structs and marshalling in and out of them at
a later time
  • Loading branch information
jrusso1020 committed Jun 2, 2020
1 parent 06287ce commit 7f858ca
Show file tree
Hide file tree
Showing 16 changed files with 325 additions and 128 deletions.
31 changes: 31 additions & 0 deletions fixture/vcr_cassettes/position/delete_all_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"request": {
"body": "",
"headers": {
"APCA-API-KEY-ID": "***",
"APCA-API-SECRET-KEY": "***"
},
"method": "delete",
"options": {
"recv_timeout": 30000
},
"request_body": "",
"url": "https://paper-api.alpaca.markets/v2/positions"
},
"response": {
"binary": false,
"body": "[{\"symbol\":\"TSLA\",\"status\":200,\"body\":{\"id\":\"c47ff82f-fa83-4083-9121-3623f4b3cb8d\",\"client_order_id\":\"dec09b9d-64ef-4ec1-b5a1-a644280d2535\",\"created_at\":\"2020-06-02T23:54:00.592748Z\",\"updated_at\":\"2020-06-02T23:54:00.592748Z\",\"submitted_at\":\"2020-06-02T23:54:00.589548Z\",\"filled_at\":null,\"expired_at\":null,\"canceled_at\":null,\"failed_at\":null,\"replaced_at\":null,\"replaced_by\":null,\"replaces\":null,\"asset_id\":\"8ccae427-5dd0-45b3-b5fe-7ba5e422c766\",\"symbol\":\"TSLA\",\"asset_class\":\"us_equity\",\"qty\":\"5\",\"filled_qty\":\"0\",\"filled_avg_price\":null,\"order_class\":\"\",\"order_type\":\"market\",\"type\":\"market\",\"side\":\"sell\",\"time_in_force\":\"day\",\"limit_price\":null,\"stop_price\":null,\"status\":\"accepted\",\"extended_hours\":false,\"legs\":null}},{\"symbol\":\"GOOG\",\"status\":200,\"body\":{\"id\":\"3dd828ae-7f70-449f-a54b-28758db2323d\",\"client_order_id\":\"43cd5d6c-7cbd-4e6b-968b-aa08ea19e4c1\",\"created_at\":\"2020-06-02T23:54:00.610001Z\",\"updated_at\":\"2020-06-02T23:54:00.610001Z\",\"submitted_at\":\"2020-06-02T23:54:00.604825Z\",\"filled_at\":null,\"expired_at\":null,\"canceled_at\":null,\"failed_at\":null,\"replaced_at\":null,\"replaced_by\":null,\"replaces\":null,\"asset_id\":\"f30d734c-2806-4d0d-b145-f9fade61432b\",\"symbol\":\"GOOG\",\"asset_class\":\"us_equity\",\"qty\":\"5\",\"filled_qty\":\"0\",\"filled_avg_price\":null,\"order_class\":\"\",\"order_type\":\"market\",\"type\":\"market\",\"side\":\"sell\",\"time_in_force\":\"day\",\"limit_price\":null,\"stop_price\":null,\"status\":\"accepted\",\"extended_hours\":false,\"legs\":null}},{\"symbol\":\"AAPL\",\"status\":403,\"body\":{\"available\":\"0\",\"code\":40310000,\"existing_qty\":\"2\",\"held_for_orders\":\"2\",\"message\":\"insufficient qty available for order (requested: 2, available: 0)\",\"related_orders\":[\"888e4b05-b624-48b9-a6cd-a1edf680acbf\"],\"symbol\":\"AAPL\"}}]",
"headers": {
"Server": "nginx/1.16.1",
"Date": "Tue, 02 Jun 2020 23:54:00 GMT",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "1712",
"Connection": "keep-alive",
"Vary": "Origin"
},
"status_code": 207,
"type": "ok"
}
}
]
31 changes: 31 additions & 0 deletions fixture/vcr_cassettes/position/delete_not_found.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"request": {
"body": "",
"headers": {
"APCA-API-KEY-ID": "***",
"APCA-API-SECRET-KEY": "***"
},
"method": "delete",
"options": {
"recv_timeout": 30000
},
"request_body": "",
"url": "https://paper-api.alpaca.markets/v2/positions/F"
},
"response": {
"binary": false,
"body": "{\"code\":40410000,\"message\":\"position does not exist\"}",
"headers": {
"Server": "nginx/1.16.1",
"Date": "Tue, 02 Jun 2020 23:51:59 GMT",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "53",
"Connection": "keep-alive",
"Vary": "Origin"
},
"status_code": 404,
"type": "ok"
}
}
]
31 changes: 31 additions & 0 deletions fixture/vcr_cassettes/position/delete_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"request": {
"body": "",
"headers": {
"APCA-API-KEY-ID": "***",
"APCA-API-SECRET-KEY": "***"
},
"method": "delete",
"options": {
"recv_timeout": 30000
},
"request_body": "",
"url": "https://paper-api.alpaca.markets/v2/positions/AAPL"
},
"response": {
"binary": false,
"body": "{\"id\":\"888e4b05-b624-48b9-a6cd-a1edf680acbf\",\"client_order_id\":\"b126e527-3166-401f-8d82-6eb33a030ca5\",\"created_at\":\"2020-06-02T23:51:52.52428Z\",\"updated_at\":\"2020-06-02T23:51:52.52428Z\",\"submitted_at\":\"2020-06-02T23:51:52.521322Z\",\"filled_at\":null,\"expired_at\":null,\"canceled_at\":null,\"failed_at\":null,\"replaced_at\":null,\"replaced_by\":null,\"replaces\":null,\"asset_id\":\"b0b6dd9d-8b9b-48a9-ba46-b9d54906e415\",\"symbol\":\"AAPL\",\"asset_class\":\"us_equity\",\"qty\":\"2\",\"filled_qty\":\"0\",\"filled_avg_price\":null,\"order_class\":\"\",\"order_type\":\"market\",\"type\":\"market\",\"side\":\"sell\",\"time_in_force\":\"day\",\"limit_price\":null,\"stop_price\":null,\"status\":\"accepted\",\"extended_hours\":false,\"legs\":null}",
"headers": {
"Server": "nginx/1.16.1",
"Date": "Tue, 02 Jun 2020 23:51:52 GMT",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "682",
"Connection": "keep-alive",
"Vary": "Origin"
},
"status_code": 200,
"type": "ok"
}
}
]
31 changes: 31 additions & 0 deletions fixture/vcr_cassettes/position/get_not_found.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"request": {
"body": "",
"headers": {
"APCA-API-KEY-ID": "***",
"APCA-API-SECRET-KEY": "***"
},
"method": "get",
"options": {
"recv_timeout": 30000
},
"request_body": "",
"url": "https://paper-api.alpaca.markets/v2/positions/F"
},
"response": {
"binary": false,
"body": "{\"code\":40410000,\"message\":\"position does not exist\"}",
"headers": {
"Server": "nginx/1.16.1",
"Date": "Tue, 02 Jun 2020 23:49:51 GMT",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "53",
"Connection": "keep-alive",
"Vary": "Origin"
},
"status_code": 404,
"type": "ok"
}
}
]
31 changes: 31 additions & 0 deletions fixture/vcr_cassettes/position/get_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"request": {
"body": "",
"headers": {
"APCA-API-KEY-ID": "***",
"APCA-API-SECRET-KEY": "***"
},
"method": "get",
"options": {
"recv_timeout": 30000
},
"request_body": "",
"url": "https://paper-api.alpaca.markets/v2/positions/AAPL"
},
"response": {
"binary": false,
"body": "{\"asset_id\":\"b0b6dd9d-8b9b-48a9-ba46-b9d54906e415\",\"symbol\":\"AAPL\",\"exchange\":\"NASDAQ\",\"asset_class\":\"us_equity\",\"qty\":\"2\",\"avg_entry_price\":\"321.33\",\"side\":\"long\",\"market_value\":\"646.7\",\"cost_basis\":\"642.66\",\"unrealized_pl\":\"4.04\",\"unrealized_plpc\":\"0.0062863722652725\",\"unrealized_intraday_pl\":\"4.04\",\"unrealized_intraday_plpc\":\"0.0062863722652725\",\"current_price\":\"323.35\",\"lastday_price\":\"321.85\",\"change_today\":\"0.0046605561597017\"}",
"headers": {
"Server": "nginx/1.16.1",
"Date": "Tue, 02 Jun 2020 23:47:49 GMT",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "437",
"Connection": "keep-alive",
"Vary": "Origin"
},
"status_code": 200,
"type": "ok"
}
}
]
31 changes: 31 additions & 0 deletions fixture/vcr_cassettes/position/list_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"request": {
"body": "",
"headers": {
"APCA-API-KEY-ID": "***",
"APCA-API-SECRET-KEY": "***"
},
"method": "get",
"options": {
"recv_timeout": 30000
},
"request_body": "",
"url": "https://paper-api.alpaca.markets/v2/positions"
},
"response": {
"binary": false,
"body": "[{\"asset_id\":\"8ccae427-5dd0-45b3-b5fe-7ba5e422c766\",\"symbol\":\"TSLA\",\"exchange\":\"NASDAQ\",\"asset_class\":\"us_equity\",\"qty\":\"5\",\"avg_entry_price\":\"892.736\",\"side\":\"long\",\"market_value\":\"4411.95\",\"cost_basis\":\"4463.68\",\"unrealized_pl\":\"-51.73\",\"unrealized_plpc\":\"-0.0115890924080579\",\"unrealized_intraday_pl\":\"-51.73\",\"unrealized_intraday_plpc\":\"-0.0115890924080579\",\"current_price\":\"882.39\",\"lastday_price\":\"898.1\",\"change_today\":\"-0.01749248413317\"},{\"asset_id\":\"f30d734c-2806-4d0d-b145-f9fade61432b\",\"symbol\":\"GOOG\",\"exchange\":\"NASDAQ\",\"asset_class\":\"us_equity\",\"qty\":\"5\",\"avg_entry_price\":\"1431.01\",\"side\":\"long\",\"market_value\":\"7157.5\",\"cost_basis\":\"7155.05\",\"unrealized_pl\":\"2.45\",\"unrealized_plpc\":\"0.0003424154967471\",\"unrealized_intraday_pl\":\"2.45\",\"unrealized_intraday_plpc\":\"0.0003424154967471\",\"current_price\":\"1431.5\",\"lastday_price\":\"1431.82\",\"change_today\":\"-0.000223491779693\"},{\"asset_id\":\"b0b6dd9d-8b9b-48a9-ba46-b9d54906e415\",\"symbol\":\"AAPL\",\"exchange\":\"NASDAQ\",\"asset_class\":\"us_equity\",\"qty\":\"2\",\"avg_entry_price\":\"321.33\",\"side\":\"long\",\"market_value\":\"646.4\",\"cost_basis\":\"642.66\",\"unrealized_pl\":\"3.74\",\"unrealized_plpc\":\"0.0058195624435938\",\"unrealized_intraday_pl\":\"3.74\",\"unrealized_intraday_plpc\":\"0.0058195624435938\",\"current_price\":\"323.2\",\"lastday_price\":\"321.85\",\"change_today\":\"0.0041945005437316\"}]",
"headers": {
"Server": "nginx/1.16.1",
"Date": "Tue, 02 Jun 2020 23:45:50 GMT",
"Content-Type": "application/json; charset=UTF-8",
"Content-Length": "1326",
"Connection": "keep-alive",
"Vary": "Accept-Encoding"
},
"status_code": 200,
"type": "ok"
}
}
]
21 changes: 21 additions & 0 deletions lib/alpaca/models/account.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule Alpaca.Account do
@moduledoc """
A resource that allows us to query an Account from Alpaca
"""

alias Alpaca.Client

@doc """
Retrieve your Alpaca trading account info
## Example
iex> {:ok, %{} = account} = Account.get()
Allows us to retrieve our own account information as a result tuple {:ok, %{}}
if successful. If not success we will get back a result tuple {:error, {status: http_status_code, body: http_response_body}}
"""
@spec get() :: {:ok, map()} | {:error, map()}
def get() do
Client.get("/v2/account")
end
end
48 changes: 5 additions & 43 deletions lib/alpaca/resources/order.ex → lib/alpaca/models/order.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,64 +30,26 @@ defmodule Alpaca.Order do
The `delete_all/0` method allows us to delete all open orders by calling `Alpaca.Order.delete_all()`.
The `delete/1` method allows us to delete a specific order by calling `Alpaca.Order.delete(id)`.
Where id is the id of the order we want to delete.
Where `id` is the id of the order we want to delete.
The `get_by_client_order_id/1` method allows us to get a specific order by the client order id by calling
`Alpaca.Order.get_by_client_order_id(client_order_id)`. Where client_order_id is the client order id on the order.
"""
use Alpaca.Resource, endpoint: "orders"
use TypedStruct

@typedoc "An Order"
typedstruct do
field(:id, String.t())
field(:client_order_id, String.t())
field(:created_at, String.t())
field(:updated_at, String.t())
field(:submitted_at, String.t())
field(:filled_at, String.t())
field(:expired_at, String.t())
field(:canceled_at, String.t())
field(:failed_at, String.t())
field(:replaced_at, String.t())
field(:replaced_by, String.t())
field(:replaces, String.t())
field(:asset_id, String.t())
field(:symbol, String.t())
field(:asset_class, String.t())
field(:qty, String.t())
field(:filled_qty, String.t())
field(:type, String.t())
field(:side, String.t())
field(:time_in_force, String.t())
field(:limit_price, String.t())
field(:stop_price, String.t())
field(:filled_avg_price, String.t())
field(:status, String.t())
field(:extended_hours, boolean())
# TODO: investigate best way for nested objects
# field(:legs, [Order.t()])
end

@doc """
Retrieve an order by client order id
### Example
```
iex> {:ok, %Order{} = order} = Order.get_by_client_order_id(client_order_id)
iex> {:ok, %{} = order} = Order.get_by_client_order_id(client_order_id)
```
Allows us to retrieve our an order as a result tuple `{:ok, %Alpaca.Order{}}`
Allows us to retrieve our an order as a result tuple `{:ok, %{}}`
if successful. If not success we will get back a result tuple `{:error, {status: http_status_code, body: http_response_body}}`
"""
@spec get_by_client_order_id(String.t()) :: {:ok, __MODULE__.t()} | {:error, map()}
@spec get_by_client_order_id(String.t()) :: {:ok, map()} | {:error, map()}
def get_by_client_order_id(client_order_id) do
case Client.get("/v2/orders:by_client_order_id", %{client_order_id: client_order_id}) do
{:ok, order} ->
{:ok, struct(__MODULE__, order)}

{:error, error} ->
{:error, error}
end
Client.get("/v2/orders:by_client_order_id", %{client_order_id: client_order_id})
end
end
24 changes: 24 additions & 0 deletions lib/alpaca/models/position.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule Alpaca.Position do
@moduledoc """
A resource that allows us to perform operations on an Position
An position has the following methods we can call on it
```
get/1
list/1
delete_all/0
delete/1
```
The `get/1` method allows us to get a singular position by calling `Alpaca.Position.get(symbol)`.
Where `symbol` is the symbol of the position to get.
The `list/1` method allows us to list all positions by calling `Alpaca.Position.list()`.
The `delete_all/0` method allows us to close all open positions by calling `Alpaca.Position.delete_all()`.
The `delete/1` method allows us to close a specific position by calling `Alpaca.Position.delete(symbol)`.
Where `symbol` is the symbol of the position we want to close.
"""
use Alpaca.Resource, endpoint: "positions", exclude: [:create, :edit]
end
32 changes: 10 additions & 22 deletions lib/alpaca/resource.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,47 +42,39 @@ defmodule Alpaca.Resource do
@doc """
A function to list all resources from the Alpaca API
"""
@spec list(map()) :: {:ok, [__MODULE__.t()]} | {:error, map()}
@spec list(map()) :: {:ok, [map()]} | {:error, map()}
def list(params \\ %{}) do
with {:ok, resources} <- Client.get(base_url(), params) do
{:ok, Enum.map(resources, fn resource -> struct(__MODULE__, resource) end)}
end
Client.get(base_url(), params)
end
end

unless :get in unquote(exclude) do
@doc """
A function to get a singlular resource from the Alpaca API
"""
@spec get(String.t(), map()) :: {:ok, __MODULE__.t()} | {:error, map()}
@spec get(String.t(), map()) :: {:ok, map()} | {:error, map()}
def get(id, params \\ %{}) do
with {:ok, resource} <- Client.get(resource_url(id), params) do
{:ok, struct(__MODULE__, resource)}
end
Client.get(resource_url(id), params)
end
end

unless :create in unquote(exclude) do
@doc """
A function to create a new resource from the Alpaca API
"""
@spec create(map()) :: {:ok, __MODULE__.t()} | {:error, map()}
@spec create(map()) :: {:ok, map()} | {:error, map()}
def create(params) do
with {:ok, resource} <- Client.post(base_url(), params) do
{:ok, struct(__MODULE__, resource)}
end
Client.post(base_url(), params)
end
end

unless :edit in unquote(exclude) do
@doc """
A function to edit an existing resource using the Alpaca API
"""
@spec edit(String.t(), map()) :: {:ok, __MODULE__.t()} | {:error, map()}
@spec edit(String.t(), map()) :: {:ok, map()} | {:error, map()}
def edit(id, params) do
with {:ok, resource} <- Client.patch(resource_url(id), params) do
{:ok, struct(__MODULE__, resource)}
end
Client.patch(resource_url(id), params)
end
end

Expand All @@ -97,8 +89,8 @@ defmodule Alpaca.Resource do
Enum.map(response_body, fn item ->
%{
status: item.status,
id: item.id,
resource: delete_all_resource_body(item.body)
id: item[:id] || item[:symbol],
resource: item.body
}
end)}
end
Expand All @@ -117,10 +109,6 @@ defmodule Alpaca.Resource do
end
end

defp delete_all_resource_body(nil), do: nil

defp delete_all_resource_body(resource), do: struct(__MODULE__, resource)

@spec base_url :: String.t()
defp base_url, do: "/v2/#{unquote(endpoint)}"

Expand Down
Loading

0 comments on commit 7f858ca

Please sign in to comment.