diff --git a/docs/order-templates.rst b/docs/order-templates.rst index d11b3a0..b025d5c 100644 --- a/docs/order-templates.rst +++ b/docs/order-templates.rst @@ -112,6 +112,9 @@ Note orders placed using these templates may be rejected, depending on the user's options trading authorization. + +.. _options_symbols: + ++++++++++++++++++++++++ Building Options Symbols ++++++++++++++++++++++++ @@ -135,7 +138,9 @@ and does not validate whether the symbol actually represents a traded option: 'TSLA', datetime.date(year=2020, month=11, day=20), 'P', '1360').build() .. autoclass:: schwab.orders.options.OptionSymbol - :special-members: + :special-members: + +.. automethod:: schwab.orders.options.OptionSymbol.build ++++++++++++++ diff --git a/docs/tda-transition.rst b/docs/tda-transition.rst index ba7daa6..bba4f70 100644 --- a/docs/tda-transition.rst +++ b/docs/tda-transition.rst @@ -94,6 +94,16 @@ Again, **this is not a choice by the library authors.** Please do not go to our Discord server asking to recover your data or add this functionality. ++++++++++++++++++++++++++++++++++++++++++ +Options symbols are formatted differently ++++++++++++++++++++++++++++++++++++++++++ + +Options symbols on Schwab use a different format than they did on TDAmeritrade. +Code that manipulates them may need to be updated. ``schwab-py`` provides a +:ref:`helper class` to make parsing and generating options +symbols easier. + + ++++++++++++++++++++++++++++++++++++++++++++++ ``schwab-py`` only supports python 3.10 and up ++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/schwab/orders/options.py b/schwab/orders/options.py index 756c491..8b63265 100644 --- a/schwab/orders/options.py +++ b/schwab/orders/options.py @@ -18,20 +18,16 @@ def _parse_expiration_date(expiration_date): class OptionSymbol: - '''Construct an option symbol from its constituent parts. Options symbols - have the following format: ``[Underlying left justified with spaces to 6 positions] - [Two digit year][Two digit month][Two digit day]['P' or 'C'][Strike price]`` + '''Construct an option symbol from its constituent parts. - The format of the strike price is modified based on its amount: - * If less than 1000, Strike Price is multiple by 1000 and pre-pended with - two zeroes - * If greater than 1000, it's prepended with one zero. - - Examples include: - * ``QQQ 240420P00500000``: QQQ Apr 20, 2024 500 Put (note the two zeroes - in front because strike is less than 1000) - * ``SPXW 240420C05040000``: SPX Weekly Apr 20, 2024 5040 Call (note the - one zero in front because strike is greater than 1000) + :param underlying_symbol: Symbol of the underlying. Not validated. + :param expiration_date: Expiration date. Accepts ``datetime.date``, + ``datetime.datetime``, or strings with the + format ``[Two digit year][Two digit month][Two + digit day]``. + :param contract_type: ``P`` or ``PUT`` for put and ``C`` or ``CALL`` for + call. + :param strike_price_as_string: Strike price represented as a decimal string. Note while each of the individual parts is validated by itself, the option symbol itself may not represent a traded option: @@ -44,15 +40,20 @@ class OptionSymbol: option symbols for an underlying, as well as extensive data in pricing, bid/ask spread, volume, etc. - :param underlying_symbol: Symbol of the underlying. Not validated. - :param expiration_date: Expiration date. Accepts ``datetime.date``, - ``datetime.datetime``, or strings with the - format ``[Two digit year][Two digit month][Two - digit day]``. - :param contract_type: ``P` or `PUT`` for put and ``C` or `CALL`` for call. - :param strike_price_as_string: Strike price, represented by a string as - you would see at the end of a real option - symbol. + For those interested in the details, options symbols have the following + format: ``[Underlying left justified with spaces to 6 positions] [Two digit + year][Two digit month][Two digit day]['P' or 'C'][Strike price]`` + + The format of the strike price is modified based on its amount: + * If less than 1000, Strike Price is multiple by 1000 and pre-pended with + two zeroes + * If greater than 1000, it's prepended with one zero. + + Examples include: + * ``QQQ 240420P00500000``: QQQ Apr 20, 2024 500 Put (note the two zeroes + in front because strike is less than 1000) + * ``SPXW 240420C05040000``: SPX Weekly Apr 20, 2024 5040 Call (note the + one zero in front because strike is greater than 1000) ''' diff --git a/tests/orders/options_test.py b/tests/orders/options_test.py index 42ffee3..3d0bc65 100644 --- a/tests/orders/options_test.py +++ b/tests/orders/options_test.py @@ -64,6 +64,33 @@ def test_strike_over_1000(self): self.assertEqual('BKNG 240510C02400000', op.build()) + def test_strike_ends_in_decimal_point(self): + op = OptionSymbol('AAPL', datetime.date(2024, 5, 10), 'C', '100.') + self.assertEqual('AAPL 240510C00100000', op.build()) + + def test_strike_ends_in_trailing_zeroes(self): + op = OptionSymbol('AAPL', datetime.date(2024, 5, 10), 'C', + '100.00000000') + self.assertEqual('AAPL 240510C00100000', op.build()) + + def test_CALL_as_delimiter(self): + op = OptionSymbol('AAPL', datetime.date(2024, 5, 10), 'CALL', '100.10') + self.assertEqual('AAPL 240510C00100100', op.build()) + + def test_PUT_as_delimiter(self): + op = OptionSymbol('AAPL', datetime.date(2024, 5, 10), 'CALL', '100.10') + self.assertEqual('AAPL 240510C00100100', op.build()) + + def test_invalid_strike(self): + with self.assertRaisesRegex( + ValueError, '.*option must have contract type.*'): + op = OptionSymbol.parse_symbol('BKNG 240510Q02400000') + + + def test_date_as_string(self): + op = OptionSymbol('AAPL', '261218', 'P', '350') + self.assertEqual('AAPL 261218P00350000', op.build()) + def test_strike_as_float(self): with self.assertRaisesRegex(