diff --git a/oandapyV20/contrib/requests/onfill.py b/oandapyV20/contrib/requests/onfill.py index c8caafb..c7f4997 100644 --- a/oandapyV20/contrib/requests/onfill.py +++ b/oandapyV20/contrib/requests/onfill.py @@ -129,7 +129,8 @@ class StopLossDetails(OnFill): """ def __init__(self, - price, + price=None, + distance=None, timeInForce=OD.TimeInForce.GTC, gtdTime=None, clientExtensions=None): @@ -138,9 +139,12 @@ def __init__(self, Parameters ---------- - price : float or string (required) + price : float or string (either price or distance is required) the price to trigger take profit order + distance : float or string (either price or distance is required) + the distance to trigger take profit order + timeInForce : TimeInForce (required), default TimeInForce.GTC the time in force @@ -185,7 +189,18 @@ def __init__(self, timeInForce=timeInForce, gtdTime=gtdTime, clientExtensions=clientExtensions) - self._data.update({"price": PriceValue(price).value}) + + if price is not None and distance is not None: + raise ValueError("price or distance is required") + + if price is not None: + self._data.update({"price": PriceValue(price).value}) + + elif distance is not None: + self._data.update({"distance": PriceValue(distance).value}) + + else: + raise ValueError("price or distance is required") class TrailingStopLossDetails(OnFill): diff --git a/oandapyV20/contrib/requests/stoplossorder.py b/oandapyV20/contrib/requests/stoplossorder.py index b4ba677..b5c6697 100644 --- a/oandapyV20/contrib/requests/stoplossorder.py +++ b/oandapyV20/contrib/requests/stoplossorder.py @@ -14,7 +14,8 @@ class StopLossOrderRequest(BaseRequest): def __init__(self, tradeID, - price, + price=None, + distance=None, clientTradeID=None, timeInForce=TimeInForce.GTC, gtdTime=None, @@ -71,7 +72,18 @@ def __init__(self, # required self._data.update({"tradeID": TradeID(tradeID).value}) - self._data.update({"price": PriceValue(price).value}) + + if price is not None and distance is not None: + raise ValueError("price or distance is required") + + if price is not None: + self._data.update({"price": PriceValue(price).value}) + + elif distance is not None: + self._data.update({"distance": str(distance)}) + + else: + raise ValueError("price or distance is required") # optional self._data.update({"clientExtensions": clientExtensions}) diff --git a/tests/test_contrib_orders.py b/tests/test_contrib_orders.py index 3a948fa..1f6d36f 100644 --- a/tests/test_contrib_orders.py +++ b/tests/test_contrib_orders.py @@ -170,6 +170,33 @@ class TestContribRequests(unittest.TestCase): 'type': 'STOP_LOSS'}, ValueError ), + # SLO: distance instead of price + (req.StopLossOrderRequest, + {"tradeID": "1234", + "timeInForce": "GTC", + "distance": 50}, + {'timeInForce': 'GTC', + 'type': 'STOP_LOSS', + 'distance': '50'}, + ), + # ... no price and no distance, should raise a ValueError + (req.StopLossOrderRequest, + {"tradeID": "1234"}, + {'timeInForce': 'GTC', + 'type': 'STOP_LOSS'}, + ValueError + ), + # ... price and distance, should raise a ValueError + (req.StopLossOrderRequest, + {"tradeID": "1234", + "price": 1.10, + "distance": 40}, + {'timeInForce': 'GTC', + 'price': 1.10, + 'distance': 40, + 'type': 'STOP_LOSS'}, + ValueError + ), # ... FOK, should raise a ValueError (req.StopLossOrderRequest, {"tradeID": "1234", @@ -239,6 +266,9 @@ def test__orders(self, cls, inpar, refpar, exc=None): if not exc: r = cls(**inpar) + print("*************") + print(r.data) + print(refpar) self.assertTrue(r.data == reference) else: with self.assertRaises(exc): @@ -318,6 +348,27 @@ def test__orders(self, cls, inpar, refpar, exc=None): {'timeInForce': 'GTC', 'price': '1.10000'} ), + # ... distance instead of price + (req.StopLossDetails, + {"distance": 40}, + {'timeInForce': 'GTC', + 'distance': '40.00000'} + ), + # .. raises ValueError because price and distance + (req.StopLossDetails, + {"distance": 40, + "price": 1.10}, + {'timeInForce': 'GTC', + 'price': '1.10000', + 'distance': '40.00000'}, + ValueError + ), + # .. raises ValueError because no price and no distance + (req.StopLossDetails, + {"timeInForce": OD.TimeInForce.GTC}, + {'timeInForce': 'GTC'}, + ValueError + ), # .. raises ValueError because GTD required gtdTime (req.StopLossDetails, {"price": 1.10, @@ -380,6 +431,9 @@ def test__anonymous_body(self, cls, inpar, refpar, exc=None): if not exc: r = cls(**inpar) if inpar else cls() + print("*************") + print(r.data) + print(refpar) self.assertTrue(r.data == refpar) else: with self.assertRaises(exc):