Skip to content

Commit

Permalink
feat: add exercise_options_position (#414)
Browse files Browse the repository at this point in the history
* feat: add exercise_option_contract

* fix: typo

Co-authored-by: Neal Patel <[email protected]>

* fix: remove unused response

* fix: remove f-string

* fix: typo

* fix: typo

* fix: rename to exercise_options_position

* fix: rename exercise_options_position

* fix: rename exercise_options_position

---------

Co-authored-by: Neal Patel <[email protected]>
  • Loading branch information
hiohiohio and neal authored Mar 5, 2024
1 parent d1f7940 commit f588e3d
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 11 deletions.
24 changes: 23 additions & 1 deletion alpaca/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,29 @@ def validate_symbol_or_asset_id(
if isinstance(symbol_or_asset_id, (UUID, str)):
return symbol_or_asset_id
raise ValueError(
f"symbol_or_asset_id must be a UUID of an asset id or a string of a symbol."
"symbol_or_asset_id must be a UUID of an asset id or a string of a symbol."
)


def validate_symbol_or_contract_id(
symbol_or_contract_id: Union[UUID, str]
) -> Union[UUID, str]:
"""
A helper function to eliminate duplicate checks of symbols or contract id.
If the argument given is a string, assumed to be a symbol name. If a UUID object, assumed to be a contract id.
ValueError if neither type.
Args:
symbol_or_contract_id: String representing a symbol name or a UUID representing a contract id.
Returns:
String if symbol, UUID if contract id.
"""
if isinstance(symbol_or_contract_id, (UUID, str)):
return symbol_or_contract_id
raise ValueError(
"symbol_or_contract_id must be a UUID of a contract id or a string of a symbol."
)


Expand Down
28 changes: 27 additions & 1 deletion alpaca/trading/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import json

from alpaca.common import RawData
from alpaca.common.utils import validate_uuid_id_param, validate_symbol_or_asset_id
from alpaca.common.utils import (
validate_symbol_or_contract_id,
validate_uuid_id_param,
validate_symbol_or_asset_id,
)
from alpaca.common.rest import RESTClient
from typing import Optional, List, Union
from alpaca.common.enums import BaseURL
Expand Down Expand Up @@ -317,6 +321,28 @@ def close_position(

return Order(**response)

def exercise_options_position(
self,
symbol_or_contract_id: Union[UUID, str],
) -> None:
"""
This endpoint enables users to exercise a held option contract, converting it into the underlying asset based on the specified terms.
All available held shares of this option contract will be exercised.
By default, Alpaca will automatically exercise in-the-money (ITM) contracts at expiry.
Exercise requests will be processed immediately once received. Exercise requests submitted outside market hours will be rejected.
To cancel an exercise request or to submit a Do-not-exercise (DNE) instruction, please contact our support team.
Args:
symbol_or_contract_id (Union[UUID, str]): Option contract symbol or ID.
Returns:
None
"""
symbol_or_contract_id = validate_symbol_or_contract_id(symbol_or_contract_id)
self.post(
f"/positions/{symbol_or_contract_id}/exercise",
)

# ############################## Assets ################################# #

def get_all_assets(
Expand Down
5 changes: 5 additions & 0 deletions docs/api_reference/trading/positions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ Close A Position
----------------

.. automethod:: alpaca.trading.client.TradingClient.close_position

Exercise An Option Contract
---------------------------

.. automethod:: alpaca.trading.client.TradingClient.exercise_options_position
15 changes: 14 additions & 1 deletion examples/options-trading-basic.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,19 @@
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# exercise the options position\n",
"# - this method does not return anything\n",
"trade_client.exercise_options_position(\n",
" symbol_or_contract_id=high_open_interest_contract.symbol\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -507,7 +520,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
"version": "3.11.8"
}
},
"nbformat": 4,
Expand Down
47 changes: 39 additions & 8 deletions tests/trading/trading_client/test_position_routes.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from typing import Iterator
from uuid import UUID

from requests_mock import Mocker

from alpaca.common.enums import BaseURL
from alpaca.trading.requests import (
ClosePositionRequest,
)
from alpaca.trading.models import (
Position,
ClosePositionResponse,
Order,
)
from alpaca.trading.client import TradingClient
from alpaca.trading.models import ClosePositionResponse, Order, Position
from alpaca.trading.requests import ClosePositionRequest


def test_get_all_positions(reqmock, trading_client: TradingClient):
Expand Down Expand Up @@ -433,3 +430,37 @@ def test_close_position_with_percentage(reqmock, trading_client: TradingClient):
assert reqmock.called_once
assert reqmock.request_history[0].qs == {"percentage": ["0.5"]}
assert isinstance(close_order, Order)


def test_exercise_options_position_with_symbol(
reqmock: Mocker, trading_client: TradingClient
) -> None:
symbol = "SPY240304P00480000"

reqmock.post(
f"{BaseURL.TRADING_PAPER.value}/v2/positions/{symbol}/exercise",
text="",
)

res = trading_client.exercise_options_position(symbol_or_contract_id=symbol)

assert reqmock.called_once
assert reqmock.request_history[0].qs == {}
assert res is None


def test_exercise_options_position_with_id(
reqmock: Mocker, trading_client: TradingClient
) -> None:
contract_id = UUID("fb37307e-0f6a-4b02-8dd2-10fc16b1d9e9")

reqmock.post(
f"{BaseURL.TRADING_PAPER.value}/v2/positions/{contract_id}/exercise",
text="",
)

res = trading_client.exercise_options_position(symbol_or_contract_id=contract_id)

assert reqmock.called_once
assert reqmock.request_history[0].qs == {}
assert res is None

0 comments on commit f588e3d

Please sign in to comment.