<NME> api_support.py
<BEF> #
# Copyright 2014 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import zipline.api
from zipline.utils.compat import wraps
from zipline.utils.algo_instance import get_algo_instance, set_algo_instance


class ZiplineAPI(object):
    """
    Context manager for making an algorithm instance available to zipline API
    functions within a scoped block.
    """

    def __init__(self, algo_instance):
        self.algo_instance = algo_instance

    def __enter__(self):
        """
        Set the given algo instance, storing any previously-existing instance.
        """
        self.old_algo_instance = get_algo_instance()
        set_algo_instance(self.algo_instance)

    def __exit__(self, _type, _value, _tb):
        """
        Restore the algo instance stored in __enter__.
        """
        set_algo_instance(self.old_algo_instance)


def api_method(f):
    # Decorator that adds the decorated class method as a callable
    # function (wrapped) to zipline.api
    @wraps(f)
    def wrapped(*args, **kwargs):
        # Get the instance and call the method
        algo_instance = get_algo_instance()
        if algo_instance is None:
            raise RuntimeError(
                'zipline api method %s must be called during a simulation.'
                % f.__name__
            )
        return getattr(algo_instance, f.__name__)(*args, **kwargs)
    # Add functor to zipline.api
    setattr(zipline.api, f.__name__, wrapped)
    zipline.api.__all__.append(f.__name__)
    f.is_api_method = True
    return f


def require_not_initialized(exception):
    """
    Decorator for API methods that should only be called during or before
    TradingAlgorithm.initialize.  `exception` will be raised if the method is
    called after initialize.

    Examples
    --------
    @require_not_initialized(SomeException("Don't do that!"))
    def method(self):
        # Do stuff that should only be allowed during initialize.
    """
    def decorator(method):
        @wraps(method)
        def wrapped_method(self, *args, **kwargs):
            if self.initialized:
                raise exception
            return method(self, *args, **kwargs)
        return wrapped_method
    return decorator


def require_initialized(exception):
    """
    Decorator for API methods that should only be called after
    TradingAlgorithm.initialize.  `exception` will be raised if the method is
    called before initialize has completed.

    Examples
    --------
    @require_initialized(SomeException("Don't do that!"))
    def method(self):
        # Do stuff that should only be allowed after initialize.
    """
            return method(self, *args, **kwargs)
        return wrapped_method
    return decorator
            if self._in_before_trading_start:
                raise exception
            return method(self, *args, **kwargs)
        return wrapped_method
    return decorator

<MSG> Merge pull request #1165 from quantopian/dont-order-in-bts

BUG: don't allow ordering in before_trading_start
<DFF> @@ -98,3 +98,25 @@ def require_initialized(exception):
             return method(self, *args, **kwargs)
         return wrapped_method
     return decorator
+
+
+def disallowed_in_before_trading_start(exception):
+    """
+    Decorator for API methods that cannot be called from within
+    TradingAlgorithm.before_trading_start.  `exception` will be raised if the
+    method is called inside `before_trading_start`.
+
+    Usage
+    -----
+    @disallowed_in_before_trading_start(SomeException("Don't do that!"))
+    def method(self):
+        # Do stuff that is not allowed inside before_trading_start.
+    """
+    def decorator(method):
+        @wraps(method)
+        def wrapped_method(self, *args, **kwargs):
+            if self._in_before_trading_start:
+                raise exception
+            return method(self, *args, **kwargs)
+        return wrapped_method
+    return decorator

{'commit': 'diff --git a/tests/test_algorithm.py b/tests/test_algorithm.py\nindex b19eb601..4c6b1965 100644\n--- a/tests/test_algorithm.py\n+++ b/tests/test_algorithm.py\n@@ -58,7 +58,15 @@ from zipline.errors import (\n     UnsupportedDatetimeFormat,\n     CannotOrderDelistedAsset,\n     SetCancelPolicyPostInit,\n-    UnsupportedCancelPolicy\n+    UnsupportedCancelPolicy,\n+    OrderInBeforeTradingStart)\n+from zipline.api import (\n+    order,\n+    order_value,\n+    order_percent,\n+    order_target,\n+    order_target_value,\n+    order_target_percent\n )\n \n from zipline.finance.commission import PerShare\n@@ -362,8 +370,8 @@ def handle_data(context, data):\n                 self.assertEqual(all_orders[2], orders_2)\n                 self.assertEqual(len(all_orders[2]), 3)\n \n-                for order in orders_2:\n-                    algo.cancel_order(order)\n+                for order_ in orders_2:\n+                    algo.cancel_order(order_)\n \n                 all_orders = algo.get_open_orders()\n                 self.assertEqual(all_orders, {})\n@@ -759,6 +767,32 @@ class TestTransformAlgorithm(WithLogger,\n         )\n         algo.run(self.data_portal)\n \n+    @parameterized.expand([\n+        (order, 1),\n+        (order_value, 1000),\n+        (order_target, 1),\n+        (order_target_value, 1000),\n+        (order_percent, 1),\n+        (order_target_percent, 1),\n+    ])\n+    def test_cannot_order_in_before_trading_start(self, order_method, amount):\n+        algotext = """\n+from zipline.api import sid\n+from zipline.api import {order_func}\n+\n+def initialize(context):\n+     context.asset = sid(133)\n+\n+def before_trading_start(context, data):\n+     {order_func}(context.asset, {arg})\n+     """.format(order_func=order_method.__name__, arg=amount)\n+\n+        algo = TradingAlgorithm(script=algotext, sim_params=self.sim_params,\n+                                data_frequency=\'daily\', env=self.env)\n+\n+        with self.assertRaises(OrderInBeforeTradingStart):\n+            algo.run(self.data_portal)\n+\n     def test_run_twice(self):\n         algo1 = TestRegisterTransformAlgorithm(\n             sim_params=self.sim_params,\ndiff --git a/zipline/algorithm.py b/zipline/algorithm.py\nindex dea5c74e..16ef2612 100644\n--- a/zipline/algorithm.py\n+++ b/zipline/algorithm.py\n@@ -51,7 +51,8 @@ from zipline.errors import (\n     UnsupportedDatetimeFormat,\n     UnsupportedOrderParameters,\n     UnsupportedSlippageModel,\n-    CannotOrderDelistedAsset, UnsupportedCancelPolicy, SetCancelPolicyPostInit)\n+    CannotOrderDelistedAsset, UnsupportedCancelPolicy, SetCancelPolicyPostInit,\n+    OrderInBeforeTradingStart)\n from zipline.finance.trading import TradingEnvironment\n from zipline.finance.blotter import Blotter\n from zipline.finance.commission import PerShare, PerTrade, PerDollar\n@@ -87,7 +88,7 @@ from zipline.utils.api_support import (\n     require_initialized,\n     require_not_initialized,\n     ZiplineAPI,\n-)\n+    disallowed_in_before_trading_start)\n from zipline.utils.input_validation import ensure_upper_case\n from zipline.utils.cache import CachedObject, Expired\n import zipline.utils.events\n@@ -968,6 +969,7 @@ class TradingAlgorithm(object):\n         return True\n \n     @api_method\n+    @disallowed_in_before_trading_start(OrderInBeforeTradingStart())\n     def order(self, asset, amount,\n               limit_price=None,\n               stop_price=None,\n@@ -1055,6 +1057,7 @@ class TradingAlgorithm(object):\n             return MarketOrder()\n \n     @api_method\n+    @disallowed_in_before_trading_start(OrderInBeforeTradingStart())\n     def order_value(self, asset, value,\n                     limit_price=None, stop_price=None, style=None):\n         """\n@@ -1205,6 +1208,7 @@ class TradingAlgorithm(object):\n         self.sim_params.data_frequency = value\n \n     @api_method\n+    @disallowed_in_before_trading_start(OrderInBeforeTradingStart())\n     def order_percent(self, asset, percent,\n                       limit_price=None, stop_price=None, style=None):\n         """\n@@ -1223,6 +1227,7 @@ class TradingAlgorithm(object):\n                                 style=style)\n \n     @api_method\n+    @disallowed_in_before_trading_start(OrderInBeforeTradingStart())\n     def order_target(self, asset, target,\n                      limit_price=None, stop_price=None, style=None):\n         """\n@@ -1249,6 +1254,7 @@ class TradingAlgorithm(object):\n                               style=style)\n \n     @api_method\n+    @disallowed_in_before_trading_start(OrderInBeforeTradingStart())\n     def order_target_value(self, asset, target,\n                            limit_price=None, stop_price=None, style=None):\n         """\n@@ -1270,6 +1276,7 @@ class TradingAlgorithm(object):\n                                  style=style)\n \n     @api_method\n+    @disallowed_in_before_trading_start(OrderInBeforeTradingStart())\n     def order_target_percent(self, asset, target,\n                              limit_price=None, stop_price=None, style=None):\n         """\ndiff --git a/zipline/errors.py b/zipline/errors.py\nindex e809d93f..675c3eac 100644\n--- a/zipline/errors.py\n+++ b/zipline/errors.py\n@@ -263,6 +263,13 @@ class HistoryInInitialize(ZiplineError):\n     msg = "history() should only be called in handle_data()"\n \n \n+class OrderInBeforeTradingStart(ZiplineError):\n+    """\n+    Raised when an algorithm calls an order method in before_trading_start.\n+    """\n+    msg = "Cannot place orders inside before_trading_start."\n+\n+\n class MultipleSymbolsFound(ZiplineError):\n     """\n     Raised when a symbol() call contains a symbol that changed over\ndiff --git a/zipline/utils/api_support.py b/zipline/utils/api_support.py\nindex 5096fc63..75444460 100644\n--- a/zipline/utils/api_support.py\n+++ b/zipline/utils/api_support.py\n@@ -98,3 +98,25 @@ def require_initialized(exception):\n             return method(self, *args, **kwargs)\n         return wrapped_method\n     return decorator\n+\n+\n+def disallowed_in_before_trading_start(exception):\n+    """\n+    Decorator for API methods that cannot be called from within\n+    TradingAlgorithm.before_trading_start.  `exception` will be raised if the\n+    method is called inside `before_trading_start`.\n+\n+    Usage\n+    -----\n+    @disallowed_in_before_trading_start(SomeException("Don\'t do that!"))\n+    def method(self):\n+        # Do stuff that is not allowed inside before_trading_start.\n+    """\n+    def decorator(method):\n+        @wraps(method)\n+        def wrapped_method(self, *args, **kwargs):\n+            if self._in_before_trading_start:\n+                raise exception\n+            return method(self, *args, **kwargs)\n+        return wrapped_method\n+    return decorator\n', 'commit_subject': 'Merge pull request #1165 from quantopian/dont-order-in-bts', 'repo_name': 'quantopian/zipline', 'committer': {'name': 'Jean Bredeche', 'email': '3d7453d8773ea2ba089a2c0af7c5c44ab9889562@gmail.com', 'time_sec': '1461682625', 'tz_offset': '-240', 'date': {'seconds': '1461682625'}}, 'commit_tree': '64226af72bc0070c9f81f6019fb6e3a0df630f5e', 'commit_message': "Merge pull request #1165 from quantopian/dont-order-in-bts\n\nBUG: don't allow ordering in before_trading_start", 'language_name': 'Shell', 'license': 'apache-2.0', 'hunks': '@@ -98,3 +98,25 @@ def require_initialized(exception):\n             return method(self, *args, **kwargs)\n         return wrapped_method\n     return decorator\n+\n+\n+def disallowed_in_before_trading_start(exception):\n+    """\n+    Decorator for API methods that cannot be called from within\n+    TradingAlgorithm.before_trading_start.  `exception` will be raised if the\n+    method is called inside `before_trading_start`.\n+\n+    Usage\n+    -----\n+    @disallowed_in_before_trading_start(SomeException("Don\'t do that!"))\n+    def method(self):\n+        # Do stuff that is not allowed inside before_trading_start.\n+    """\n+    def decorator(method):\n+        @wraps(method)\n+        def wrapped_method(self, *args, **kwargs):\n+            if self._in_before_trading_start:\n+                raise exception\n+            return method(self, *args, **kwargs)\n+        return wrapped_method\n+    return decorator\n', 'addition_count': 22, 'deletion_count': 0, 'src_file': 'a/zipline/utils/api_support.py', 'tgt_file': 'b/zipline/utils/api_support.py', 'file_extension': '.py', 'before_file': '#\n# Copyright 2014 Quantopian, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the "License");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an "AS IS" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport zipline.api\nfrom zipline.utils.compat import wraps\nfrom zipline.utils.algo_instance import get_algo_instance, set_algo_instance\n\n\nclass ZiplineAPI(object):\n    """\n    Context manager for making an algorithm instance available to zipline API\n    functions within a scoped block.\n    """\n\n    def __init__(self, algo_instance):\n        self.algo_instance = algo_instance\n\n    def __enter__(self):\n        """\n        Set the given algo instance, storing any previously-existing instance.\n        """\n        self.old_algo_instance = get_algo_instance()\n        set_algo_instance(self.algo_instance)\n\n    def __exit__(self, _type, _value, _tb):\n        """\n        Restore the algo instance stored in __enter__.\n        """\n        set_algo_instance(self.old_algo_instance)\n\n\ndef api_method(f):\n    # Decorator that adds the decorated class method as a callable\n    # function (wrapped) to zipline.api\n    @wraps(f)\n    def wrapped(*args, **kwargs):\n        # Get the instance and call the method\n        algo_instance = get_algo_instance()\n        if algo_instance is None:\n            raise RuntimeError(\n                \'zipline api method %s must be called during a simulation.\'\n                % f.__name__\n            )\n        return getattr(algo_instance, f.__name__)(*args, **kwargs)\n    # Add functor to zipline.api\n    setattr(zipline.api, f.__name__, wrapped)\n    zipline.api.__all__.append(f.__name__)\n    f.is_api_method = True\n    return f\n\n\ndef require_not_initialized(exception):\n    """\n    Decorator for API methods that should only be called during or before\n    TradingAlgorithm.initialize.  `exception` will be raised if the method is\n    called after initialize.\n\n    Examples\n    --------\n    @require_not_initialized(SomeException("Don\'t do that!"))\n    def method(self):\n        # Do stuff that should only be allowed during initialize.\n    """\n    def decorator(method):\n        @wraps(method)\n        def wrapped_method(self, *args, **kwargs):\n            if self.initialized:\n                raise exception\n            return method(self, *args, **kwargs)\n        return wrapped_method\n    return decorator\n\n\ndef require_initialized(exception):\n    """\n    Decorator for API methods that should only be called after\n    TradingAlgorithm.initialize.  `exception` will be raised if the method is\n    called before initialize has completed.\n\n    Examples\n    --------\n    @require_initialized(SomeException("Don\'t do that!"))\n    def method(self):\n        # Do stuff that should only be allowed after initialize.\n    """\n            return method(self, *args, **kwargs)\n        return wrapped_method\n    return decorator\n            if self._in_before_trading_start:\n                raise exception\n            return method(self, *args, **kwargs)\n        return wrapped_method\n    return decorator\n', 'message': "Merge pull request #1165 from quantopian/dont-order-in-bts\n\nBUG: don't allow ordering in before_trading_start", 'author_name': 'Jean Bredeche', 'lang': 'py'}