Skip to content

Commit

Permalink
feat: add exercise_option_contract
Browse files Browse the repository at this point in the history
  • Loading branch information
hiohiohio committed Mar 1, 2024
1 parent d1f7940 commit 6f95f8b
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 10 deletions.
22 changes: 22 additions & 0 deletions alpaca/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,28 @@ def validate_symbol_or_asset_id(
)


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 an contract id.
ValueError if neither type.
Args:
symbol_or_contractt_id: String representing a symbol name or a UUID representing an 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(
f"symbol_or_contract_id must be a UUID of an contract id or a string of a symbol."
)


def tz_aware(dt: datetime) -> bool:
"""
Returns if a given datetime is timezone aware
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_option_contract(
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)
response = 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_option_contract
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 option contract\n",
"# - this method does not return anything\n",
"trade_client.exercise_option_contract(\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_option_contract_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_option_contract(symbol_or_contract_id=symbol)

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


def test_exercise_option_contract_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_option_contract(symbol_or_contract_id=contract_id)

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

0 comments on commit 6f95f8b

Please sign in to comment.