Skip to content

Commit

Permalink
refactor: cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
aarjaneiro committed Jan 17, 2025
1 parent b32d1ee commit 64175f0
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 72 deletions.
4 changes: 3 additions & 1 deletion alpaca/common/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,7 @@ def map_values(val: Any) -> Any:
# {trusted_contact: {}, contact: {}, identity: None, etc}
# so we do a simple list comprehension to filter out None and {}
return {
key: map_values(val) for key, val in d.items() if val is not None and val != {} and len(str(val)) > 0
key: map_values(val)
for key, val in d.items()
if val is not None and val != {} and len(str(val)) > 0
}
4 changes: 3 additions & 1 deletion alpaca/trading/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,9 @@ def root_validator(cls, data: dict) -> dict:
if "_is_sub_mleg" not in data or not data["_is_sub_mleg"]:
if "legs" not in data or data["legs"] == "":
raise ValueError("legs is required for mleg orders")
if data["legs"] is not None: # it is possible when querying individual legs that this is None
if (
data["legs"] is not None
): # it is possible when querying individual legs that this is None
if len(data["legs"]) < 1:
raise ValueError("legs must have at least one order")
for leg in data["legs"]:
Expand Down
15 changes: 11 additions & 4 deletions alpaca/trading/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class StopLossRequest(NonEmptyRequest):
stop_price: float
limit_price: Optional[float] = None


class OptionLegRequest(NonEmptyRequest):
"""
Used for providing details for a leg of a multi-leg order.
Expand Down Expand Up @@ -323,18 +324,24 @@ def root_validator(cls, values: dict) -> dict:
if "legs" not in values or values["legs"] is None:
raise ValueError("legs is required for the mleg order class.")
l_len = len(values["legs"])
if l_len> 4:
if l_len > 4:
raise ValueError("At most 4 legs are allowed for the mleg order class.")
if l_len < 2:
raise ValueError("At least 2 legs are required for the mleg order class.")
raise ValueError(
"At least 2 legs are required for the mleg order class."
)
n_unique = len(set([l.symbol for l in values["legs"]]))
if n_unique != l_len:
raise ValueError("All legs must have unique symbols.")
else:
if "symbol" not in values or values["symbol"] is None:
raise ValueError("symbol is required for all order classes other than mleg.")
raise ValueError(
"symbol is required for all order classes other than mleg."
)
if "side" not in values or values["side"] is None:
raise ValueError("side is required for all order classes other than mleg.")
raise ValueError(
"side is required for all order classes other than mleg."
)

return values

Expand Down
95 changes: 61 additions & 34 deletions tests/trading/test_trading_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,55 +80,71 @@ def test_trailing_stop_order_type():


def test_mleg_options() -> None:
symbols = ["AAPL250117P00200000",
"AAPL250117P00250000",
"AAPL250117P00300000",
"AAPL250117P00350000",
"AAPL250117P00400000"]
symbols = [
"AAPL250117P00200000",
"AAPL250117P00250000",
"AAPL250117P00300000",
"AAPL250117P00350000",
"AAPL250117P00400000",
]

def kwargs_as_string(**kwargs):
return ", ".join([f"{k}={v}" for k, v in kwargs.items()])

def order_request_factory(is_market: bool):
if is_market:

def factory(warn_validated: bool = True, **kwargs):
o = MarketOrderRequest(
**kwargs
)
o = MarketOrderRequest(**kwargs)
if warn_validated:
warnings.warn(f"MarketOrderRequest({kwargs_as_string(**kwargs)}) passed validation!")
warnings.warn(
f"MarketOrderRequest({kwargs_as_string(**kwargs)}) passed validation!"
)
return o

else:

def factory(warn_validated: bool = True, **kwargs):
o = LimitOrderRequest(
limit_price=1,
**kwargs
)
o = LimitOrderRequest(limit_price=1, **kwargs)
if warn_validated:
warnings.warn(f"LimitOrderRequest({kwargs_as_string(**kwargs)}) passed validation!")
warnings.warn(
f"LimitOrderRequest({kwargs_as_string(**kwargs)}) passed validation!"
)
return o

return factory

for is_mkt in [True, False]:
o_req = order_request_factory(is_mkt)

# Good requests
for sym_index in range(2, 5):
o_req(warn_validated=False,
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY) for symbol in
symbols[:sym_index]])
o_req(
warn_validated=False,
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[
OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY)
for symbol in symbols[:sym_index]
],
)

# Bad requests
with pytest.raises(ValueError) as e:
# Missing qty
o_req(
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY),
OptionLegRequest(symbol=symbols[1], ratio_qty=1, side=OrderSide.SELL)])
legs=[
OptionLegRequest(
symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY
),
OptionLegRequest(
symbol=symbols[1], ratio_qty=1, side=OrderSide.SELL
),
],
)
assert "At least one of qty or notional must be provided" in str(e.value)

with pytest.raises(ValueError) as e:
Expand All @@ -137,7 +153,10 @@ def factory(warn_validated: bool = True, **kwargs):
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)])
legs=[
OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)
],
)
assert "At least 2 legs are required for the mleg order class" in str(e.value)

with pytest.raises(ValueError) as e:
Expand All @@ -146,16 +165,16 @@ def factory(warn_validated: bool = True, **kwargs):
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY)
for symbol in symbols])
legs=[
OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY)
for symbol in symbols
],
)
assert "At most 4 legs are allowed for the mleg order class." in str(e.value)

with pytest.raises(ValueError) as e:
# Missing legs
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG)
o_req(qty=1, time_in_force=TimeInForce.DAY, order_class=OrderClass.MLEG)
assert "legs is required for the mleg order class." in str(e.value)

with pytest.raises(ValueError) as e:
Expand All @@ -164,16 +183,24 @@ def factory(warn_validated: bool = True, **kwargs):
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY),
OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.SELL)])
legs=[
OptionLegRequest(
symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY
),
OptionLegRequest(
symbol=symbols[0], ratio_qty=1, side=OrderSide.SELL
),
],
)

assert "All legs must have unique symbols." in str(e.value)


with pytest.raises(ValueError) as e:
# Legs in non-MLEG order
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)])

legs=[
OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)
],
)
93 changes: 61 additions & 32 deletions tests/trading/trading_client/test_order_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,92 +627,121 @@ def test_order_position_intent(reqmock, trading_client: TradingClient):


def test_mleg_request_validation() -> None:
symbols = ["AAPL250117P00200000",
"AAPL250117P00250000",
"AAPL250117P00300000",
"AAPL250117P00350000",
"AAPL250117P00400000"]
symbols = [
"AAPL250117P00200000",
"AAPL250117P00250000",
"AAPL250117P00300000",
"AAPL250117P00350000",
"AAPL250117P00400000",
]

def kwargs_as_string(**kwargs):
return ", ".join([f"{k}={v}" for k, v in kwargs.items()])

def order_request_factory(is_market: bool):
if is_market:

def factory(warn_validated: bool = True, **kwargs):
o = MarketOrderRequest(
**kwargs
)
o = MarketOrderRequest(**kwargs)
if warn_validated:
warnings.warn(f"MarketOrderRequest({kwargs_as_string(**kwargs)}) passed validation!")
warnings.warn(
f"MarketOrderRequest({kwargs_as_string(**kwargs)}) passed validation!"
)
return o

else:

def factory(warn_validated: bool = True, **kwargs):
o = LimitOrderRequest(
limit_price=1,
**kwargs
)
o = LimitOrderRequest(limit_price=1, **kwargs)
if warn_validated:
warnings.warn(f"LimitOrderRequest({kwargs_as_string(**kwargs)}) passed validation!")
warnings.warn(
f"LimitOrderRequest({kwargs_as_string(**kwargs)}) passed validation!"
)
return o

return factory

for is_mkt in [True, False]:
o_req = order_request_factory(is_mkt)

# Good requests
for sym_index in range(2, 5):
o_req(warn_validated=False,
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY) for symbol in
symbols[:sym_index]])
o_req(
warn_validated=False,
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[
OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY)
for symbol in symbols[:sym_index]
],
)

# Bad requests
with pytest.raises(ValueError):
# Missing qty
o_req(
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY),
OptionLegRequest(symbol=symbols[1], ratio_qty=1, side=OrderSide.SELL)])
legs=[
OptionLegRequest(
symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY
),
OptionLegRequest(
symbol=symbols[1], ratio_qty=1, side=OrderSide.SELL
),
],
)

with pytest.raises(ValueError):
# Too few legs
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)])
legs=[
OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)
],
)

with pytest.raises(ValueError):
# Too many legs
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY)
for symbol in symbols])
legs=[
OptionLegRequest(symbol=symbol, ratio_qty=1, side=OrderSide.BUY)
for symbol in symbols
],
)

with pytest.raises(ValueError):
# Missing legs
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG)
o_req(qty=1, time_in_force=TimeInForce.DAY, order_class=OrderClass.MLEG)

with pytest.raises(ValueError):
# Repeated symbols across legs
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
order_class=OrderClass.MLEG,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY),
OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.SELL)])
legs=[
OptionLegRequest(
symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY
),
OptionLegRequest(
symbol=symbols[0], ratio_qty=1, side=OrderSide.SELL
),
],
)

with pytest.raises(ValueError):
# Legs in non-MLEG order
o_req(
qty=1,
time_in_force=TimeInForce.DAY,
legs=[OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)])
legs=[
OptionLegRequest(symbol=symbols[0], ratio_qty=1, side=OrderSide.BUY)
],
)

0 comments on commit 64175f0

Please sign in to comment.