From 79aea2866d0a9199b14fca7947b688b77bebf674 Mon Sep 17 00:00:00 2001 From: Alex Golec Date: Sun, 30 Oct 2022 08:15:27 -0400 Subject: [PATCH 1/3] Added draft equity stop order templates --- tda/contrib/orders.py | 9 +-- tda/orders/equities.py | 104 ++++++++++++++++++++++++++++++++++- tests/contrib/orders_test.py | 11 ++-- tests/orders_test.py | 82 +++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 15 deletions(-) diff --git a/tda/contrib/orders.py b/tda/contrib/orders.py index 54ad0f8a..3e361ed8 100644 --- a/tda/contrib/orders.py +++ b/tda/contrib/orders.py @@ -103,7 +103,7 @@ def construct_repeat_order(historical_order): # AST generation -def code_for_builder(builder, var_name=None): +def code_for_builder(builder): ''' Returns code that can be executed to construct the given builder, including import statements. @@ -127,15 +127,10 @@ def code_for_builder(builder, var_name=None): module, ',\n'.join(names)) import_lines.append(line) - if var_name: - var_prefix = f'{var_name} = ' - else: - var_prefix = '' - return autopep8.fix_code( '\n'.join(import_lines) + '\n\n' + - var_prefix + + 'order = ' + '\n'.join(lines)) diff --git a/tda/orders/equities.py b/tda/orders/equities.py index 248100d2..812bd1c1 100644 --- a/tda/orders/equities.py +++ b/tda/orders/equities.py @@ -1,6 +1,13 @@ from enum import Enum -from tda.orders.common import Duration, Session +from tda.orders.common import ( + Duration, + StopType, + Session, + StopType, + StopPriceLinkBasis, + StopPriceLinkType, +) ########################################################################## @@ -154,3 +161,98 @@ def equity_buy_to_cover_limit(symbol, quantity, price): .set_duration(Duration.DAY) .set_order_strategy_type(OrderStrategyType.SINGLE) .add_equity_leg(EquityInstruction.BUY_TO_COVER, symbol, quantity)) + + +########################################################################## +# Stop orders + + +def equity_sell_stop(symbol, quantity, stop_price, stop_type=StopType.MARK): + """ + Returns a pre-filled :class:`~tda.orders.generic.OrderBuilder` for an equity + sell stop order. + """ + from tda.orders.common import Duration, EquityInstruction + from tda.orders.common import OrderStrategyType, OrderType, Session + from tda.orders.generic import OrderBuilder + + return (OrderBuilder() + .set_order_type(OrderType.STOP) + .set_quantity(quantity) + .set_session(Session.NORMAL) + .set_stop_price(stop_price) + .set_stop_type(stop_type) + .set_duration(Duration.DAY) + .set_order_strategy_type(OrderStrategyType.SINGLE) + .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) + + +def equity_sell_stop_limit(symbol, quantity, limit_price, stop_price, + stop_type=StopType.MARK): + """ + Returns a pre-filled :class:`~tda.orders.generic.OrderBuilder` for an equity + sell stop limit order. + """ + + from tda.orders.common import Duration, EquityInstruction + from tda.orders.common import OrderStrategyType, OrderType, Session + from tda.orders.generic import OrderBuilder + + return (OrderBuilder() + .set_order_type(OrderType.STOP_LIMIT) + .set_quantity(quantity) + .set_session(Session.NORMAL) + .set_price(limit_price) + .set_stop_price(stop_price) + .set_stop_type(stop_type) + .set_duration(Duration.DAY) + .set_order_strategy_type(OrderStrategyType.SINGLE) + .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) + + +def equity_sell_trailing_stop( + symbol, quantity, trail_offset, + trail_offset_type=StopPriceLinkType.PERCENT, stop_type=StopType.MARK, + stop_price_link_basis=StopPriceLinkBasis.MARK): + """ + Returns a pre-filled :class:`~tda.orders.generic.OrderBuilder` for an equity + sell trailing stop order. + """ + + from tda.orders.common import Duration, EquityInstruction + from tda.orders.common import OrderStrategyType, OrderType, Session + from tda.orders.generic import OrderBuilder + + return (OrderBuilder() + .set_order_type(OrderType.TRAILING_STOP) + .set_quantity(quantity) + .set_session(Session.NORMAL) + .set_duration(Duration.DAY) + .set_stop_type(stop_type) + .set_stop_price_offset(trail_offset) + .set_stop_price_link_basis(stop_price_link_basis) + .set_stop_price_link_type(trail_offset_type) + .set_order_strategy_type(OrderStrategyType.SINGLE) + .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) + + +def equity_sell_trailing_stop_limit( + symbol, quantity, trail_offset, limit_price, + trail_offset_type=StopPriceLinkType.PERCENT, stop_type=StopType.MARK, + stop_price_link_basis=StopPriceLinkBasis.MARK): + from tda.orders.common import Duration, EquityInstruction + from tda.orders.common import OrderStrategyType, OrderType, Session + from tda.orders.generic import OrderBuilder + + return (OrderBuilder() + .set_order_type(OrderType.TRAILING_STOP_LIMIT) + .set_quantity(quantity) + .set_price(limit_price) + .set_session(Session.NORMAL) + .set_duration(Duration.DAY) + .set_stop_type(stop_type) + .set_stop_price_offset(trail_offset) + .set_stop_price_link_basis(stop_price_link_basis) + .set_stop_price_link_type(trail_offset_type) + .set_order_strategy_type(OrderStrategyType.SINGLE) + .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) diff --git a/tests/contrib/orders_test.py b/tests/contrib/orders_test.py index 6c881a17..9ce47c67 100644 --- a/tests/contrib/orders_test.py +++ b/tests/contrib/orders_test.py @@ -29,18 +29,15 @@ def validate_syntax(code, globalz): print(e) assert False, 'Syntax error from generated code' - # With a variable name, validate the syntax and expect the output - code = code_for_builder(builder, 'test_builder') + # Verify that the code is executable and that the order object that is + # created matches what we passed in + code = code_for_builder(builder) globalz = {} validate_syntax(code, globalz) self.assertEquals( json.dumps(expected_json, indent=4, sort_keys=True), json.dumps( - globalz['test_builder'].build(), indent=4, sort_keys=True)) - - # With no variable name, just validate the syntax - code = code_for_builder(builder) - validate_syntax(code, {}) + globalz['order'].build(), indent=4, sort_keys=True)) def test_market_equity_order(self): diff --git a/tests/orders_test.py b/tests/orders_test.py index 729f6a49..6af3a736 100644 --- a/tests/orders_test.py +++ b/tests/orders_test.py @@ -176,3 +176,85 @@ def test_equity_buy_to_cover_limit(self): } }] }, equity_buy_to_cover_limit('GOOG', 10, 199.99).build())) + + def test_equity_sell_stop(self): + self.assertFalse(has_diff({ + 'orderType': 'STOP', + 'quantity': 10, + 'session': 'NORMAL', + 'duration': 'DAY', + 'stopPrice': '199.99', + 'stopType': 'MARK', + 'orderStrategyType': 'SINGLE', + 'orderLegCollection': [{ + 'instruction': 'SELL', + 'quantity': 10, + 'instrument': { + 'symbol': 'GOOG', + 'assetType': 'EQUITY', + } + }] + }, equity_sell_stop('GOOG', 10, 199.99).build())) + + def test_equity_sell_stop_limit(self): + self.assertFalse(has_diff({ + 'orderType': 'STOP_LIMIT', + 'quantity': 10, + 'session': 'NORMAL', + 'duration': 'DAY', + 'price': '88.88', + 'stopPrice': '199.99', + 'stopType': 'MARK', + 'orderStrategyType': 'SINGLE', + 'orderLegCollection': [{ + 'instruction': 'SELL', + 'quantity': 10, + 'instrument': { + 'symbol': 'GOOG', + 'assetType': 'EQUITY', + } + }] + }, equity_sell_stop_limit('GOOG', 10, 88.88, 199.99).build())) + + def test_equity_sell_trailing_stop(self): + self.assertFalse(has_diff({ + 'orderType': 'TRAILING_STOP', + 'quantity': 10, + 'session': 'NORMAL', + 'duration': 'DAY', + 'stopType': 'MARK', + 'stopPriceOffset': 5, + 'stopPriceLinkBasis': 'MARK', + 'stopPriceLinkType': 'PERCENT', + 'orderStrategyType': 'SINGLE', + 'orderLegCollection': [{ + 'instruction': 'SELL', + 'quantity': 10, + 'instrument': { + 'symbol': 'GOOG', + 'assetType': 'EQUITY', + } + }] + }, equity_sell_trailing_stop('GOOG', 10, 5).build())) + + def test_equity_sell_trailing_stop_limit(self): + self.assertFalse(has_diff({ + 'orderType': 'TRAILING_STOP_LIMIT', + 'quantity': 10, + 'session': 'NORMAL', + 'duration': 'DAY', + 'price': '10.00', + 'stopType': 'MARK', + 'stopPriceOffset': 5, + 'stopPriceLinkBasis': 'MARK', + 'stopPriceLinkType': 'PERCENT', + 'orderStrategyType': 'SINGLE', + 'orderLegCollection': [{ + 'instruction': 'SELL', + 'quantity': 10, + 'instrument': { + 'symbol': 'GOOG', + 'assetType': 'EQUITY', + } + }] + }, equity_sell_trailing_stop_limit('GOOG', 10, 5, 10).build())) From 1cc2fea7f9e42b431b2bf2d4ecdbbd04b9d6e36e Mon Sep 17 00:00:00 2001 From: Alex Golec Date: Fri, 11 Nov 2022 20:04:43 -0500 Subject: [PATCH 2/3] Remove stop limit --- tda/orders/equities.py | 27 +++------------------------ tests/orders_test.py | 30 ++++-------------------------- 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/tda/orders/equities.py b/tda/orders/equities.py index 812bd1c1..46621bc5 100644 --- a/tda/orders/equities.py +++ b/tda/orders/equities.py @@ -167,7 +167,8 @@ def equity_buy_to_cover_limit(symbol, quantity, price): # Stop orders -def equity_sell_stop(symbol, quantity, stop_price, stop_type=StopType.MARK): +def equity_sell_stop_market( + symbol, quantity, stop_price, stop_type=StopType.MARK): """ Returns a pre-filled :class:`~tda.orders.generic.OrderBuilder` for an equity sell stop order. @@ -210,7 +211,7 @@ def equity_sell_stop_limit(symbol, quantity, limit_price, stop_price, .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) -def equity_sell_trailing_stop( +def equity_sell_trailing_stop_market( symbol, quantity, trail_offset, trail_offset_type=StopPriceLinkType.PERCENT, stop_type=StopType.MARK, stop_price_link_basis=StopPriceLinkBasis.MARK): @@ -234,25 +235,3 @@ def equity_sell_trailing_stop( .set_stop_price_link_type(trail_offset_type) .set_order_strategy_type(OrderStrategyType.SINGLE) .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) - - -def equity_sell_trailing_stop_limit( - symbol, quantity, trail_offset, limit_price, - trail_offset_type=StopPriceLinkType.PERCENT, stop_type=StopType.MARK, - stop_price_link_basis=StopPriceLinkBasis.MARK): - from tda.orders.common import Duration, EquityInstruction - from tda.orders.common import OrderStrategyType, OrderType, Session - from tda.orders.generic import OrderBuilder - - return (OrderBuilder() - .set_order_type(OrderType.TRAILING_STOP_LIMIT) - .set_quantity(quantity) - .set_price(limit_price) - .set_session(Session.NORMAL) - .set_duration(Duration.DAY) - .set_stop_type(stop_type) - .set_stop_price_offset(trail_offset) - .set_stop_price_link_basis(stop_price_link_basis) - .set_stop_price_link_type(trail_offset_type) - .set_order_strategy_type(OrderStrategyType.SINGLE) - .add_equity_leg(EquityInstruction.SELL, symbol, quantity)) diff --git a/tests/orders_test.py b/tests/orders_test.py index 6af3a736..5704b5d8 100644 --- a/tests/orders_test.py +++ b/tests/orders_test.py @@ -177,7 +177,7 @@ def test_equity_buy_to_cover_limit(self): }] }, equity_buy_to_cover_limit('GOOG', 10, 199.99).build())) - def test_equity_sell_stop(self): + def test_equity_sell_stop_market(self): self.assertFalse(has_diff({ 'orderType': 'STOP', 'quantity': 10, @@ -194,7 +194,7 @@ def test_equity_sell_stop(self): 'assetType': 'EQUITY', } }] - }, equity_sell_stop('GOOG', 10, 199.99).build())) + }, equity_sell_stop_market('GOOG', 10, 199.99).build())) def test_equity_sell_stop_limit(self): self.assertFalse(has_diff({ @@ -216,7 +216,7 @@ def test_equity_sell_stop_limit(self): }] }, equity_sell_stop_limit('GOOG', 10, 88.88, 199.99).build())) - def test_equity_sell_trailing_stop(self): + def test_equity_sell_trailing_stop_market(self): self.assertFalse(has_diff({ 'orderType': 'TRAILING_STOP', 'quantity': 10, @@ -235,26 +235,4 @@ def test_equity_sell_trailing_stop(self): 'assetType': 'EQUITY', } }] - }, equity_sell_trailing_stop('GOOG', 10, 5).build())) - - def test_equity_sell_trailing_stop_limit(self): - self.assertFalse(has_diff({ - 'orderType': 'TRAILING_STOP_LIMIT', - 'quantity': 10, - 'session': 'NORMAL', - 'duration': 'DAY', - 'price': '10.00', - 'stopType': 'MARK', - 'stopPriceOffset': 5, - 'stopPriceLinkBasis': 'MARK', - 'stopPriceLinkType': 'PERCENT', - 'orderStrategyType': 'SINGLE', - 'orderLegCollection': [{ - 'instruction': 'SELL', - 'quantity': 10, - 'instrument': { - 'symbol': 'GOOG', - 'assetType': 'EQUITY', - } - }] - }, equity_sell_trailing_stop_limit('GOOG', 10, 5, 10).build())) + }, equity_sell_trailing_stop_market('GOOG', 10, 5).build())) From 9f23a35246f19a5410764bab8fd581e5ea583346 Mon Sep 17 00:00:00 2001 From: Alex Golec Date: Fri, 11 Nov 2022 20:07:17 -0500 Subject: [PATCH 3/3] Removed accidentally added changes --- tda/contrib/orders.py | 9 +++++++-- tests/contrib/orders_test.py | 11 +++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tda/contrib/orders.py b/tda/contrib/orders.py index 3e361ed8..54ad0f8a 100644 --- a/tda/contrib/orders.py +++ b/tda/contrib/orders.py @@ -103,7 +103,7 @@ def construct_repeat_order(historical_order): # AST generation -def code_for_builder(builder): +def code_for_builder(builder, var_name=None): ''' Returns code that can be executed to construct the given builder, including import statements. @@ -127,10 +127,15 @@ def code_for_builder(builder): module, ',\n'.join(names)) import_lines.append(line) + if var_name: + var_prefix = f'{var_name} = ' + else: + var_prefix = '' + return autopep8.fix_code( '\n'.join(import_lines) + '\n\n' + - 'order = ' + + var_prefix + '\n'.join(lines)) diff --git a/tests/contrib/orders_test.py b/tests/contrib/orders_test.py index 9ce47c67..6c881a17 100644 --- a/tests/contrib/orders_test.py +++ b/tests/contrib/orders_test.py @@ -29,15 +29,18 @@ def validate_syntax(code, globalz): print(e) assert False, 'Syntax error from generated code' - # Verify that the code is executable and that the order object that is - # created matches what we passed in - code = code_for_builder(builder) + # With a variable name, validate the syntax and expect the output + code = code_for_builder(builder, 'test_builder') globalz = {} validate_syntax(code, globalz) self.assertEquals( json.dumps(expected_json, indent=4, sort_keys=True), json.dumps( - globalz['order'].build(), indent=4, sort_keys=True)) + globalz['test_builder'].build(), indent=4, sort_keys=True)) + + # With no variable name, just validate the syntax + code = code_for_builder(builder) + validate_syntax(code, {}) def test_market_equity_order(self):