Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.11"]

steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ nexa-bidkit/

- Update tests to include new/changed work, aim for >80% code coverage, but prioritise good tests
- Run tests and ensure they pass
- Run the ruff formatter to reformat code
- Linters (like black or mypy) show no issues
- Update README and/or docs to document the new behaviour/feature
- Add anything needed in @.gitignore to avoid checking in secrets, or temp files/logs
38 changes: 9 additions & 29 deletions src/nexa_bidkit/bids.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,7 @@ def validate_bid_id(cls, v: str) -> str:
def validate_min_acceptance_ratio(cls, v: Decimal) -> Decimal:
"""Ensure min_acceptance_ratio is between 0.0 and 1.0."""
if v < Decimal("0.0") or v > Decimal("1.0"):
raise ValueError(
f"min_acceptance_ratio must be between 0.0 and 1.0, got {v}"
)
raise ValueError(f"min_acceptance_ratio must be between 0.0 and 1.0, got {v}")
return v

@property
Expand Down Expand Up @@ -198,9 +196,7 @@ def validate_ids(cls, v: str) -> str:
def validate_min_acceptance_ratio(cls, v: Decimal) -> Decimal:
"""Ensure min_acceptance_ratio is between 0.0 and 1.0."""
if v < Decimal("0.0") or v > Decimal("1.0"):
raise ValueError(
f"min_acceptance_ratio must be between 0.0 and 1.0, got {v}"
)
raise ValueError(f"min_acceptance_ratio must be between 0.0 and 1.0, got {v}")
return v

@model_validator(mode="after")
Expand Down Expand Up @@ -266,9 +262,7 @@ def validate_group_id(cls, v: str) -> str:
def validate_minimum_blocks(cls, v: list[BlockBid]) -> list[BlockBid]:
"""Ensure at least 2 block bids are provided."""
if len(v) < 2:
raise ValueError(
f"ExclusiveGroupBid requires at least 2 block bids, got {len(v)}"
)
raise ValueError(f"ExclusiveGroupBid requires at least 2 block bids, got {len(v)}")
return v

@model_validator(mode="after")
Expand All @@ -277,9 +271,7 @@ def validate_consistency(self) -> ExclusiveGroupBid:
# Check all blocks share same bidding zone
zones = {block.bidding_zone for block in self.block_bids}
if len(zones) > 1:
raise ValueError(
f"All block bids must have the same bidding zone, got {zones}"
)
raise ValueError(f"All block bids must have the same bidding zone, got {zones}")
if zones and zones.pop() != self.bidding_zone:
raise ValueError(
f"Block bids zone {zones} does not match group zone {self.bidding_zone}"
Expand All @@ -288,9 +280,7 @@ def validate_consistency(self) -> ExclusiveGroupBid:
# Check all blocks share same direction
directions = {block.direction for block in self.block_bids}
if len(directions) > 1:
raise ValueError(
f"All block bids must have the same direction, got {directions}"
)
raise ValueError(f"All block bids must have the same direction, got {directions}")
if directions and directions.pop() != self.direction:
raise ValueError(
f"Block bids direction does not match group direction {self.direction}"
Expand All @@ -303,11 +293,7 @@ def validate_consistency(self) -> ExclusiveGroupBid:
raise ValueError(f"Duplicate bid_ids found in group: {duplicates}")

# Check all blocks are pure BlockBid (bid_type == BLOCK)
non_block = [
block.bid_id
for block in self.block_bids
if block.bid_type != BidType.BLOCK
]
non_block = [block.bid_id for block in self.block_bids if block.bid_type != BidType.BLOCK]
if non_block:
raise ValueError(
f"ExclusiveGroupBid can only contain pure BlockBid instances, "
Expand Down Expand Up @@ -509,9 +495,7 @@ def exclusive_group(
ValueError: If fewer than 2 block_bids provided.
"""
if len(block_bids) < 2:
raise ValueError(
f"exclusive_group requires at least 2 block bids, got {len(block_bids)}"
)
raise ValueError(f"exclusive_group requires at least 2 block bids, got {len(block_bids)}")

first_block = block_bids[0]
return ExclusiveGroupBid(
Expand Down Expand Up @@ -596,12 +580,8 @@ def has_cycle(bid_id: str, visited: set[str], rec_stack: set[str]) -> bool:

visited: set[str] = set()
for linked_bid in linked_bids:
if linked_bid.bid_id not in visited and has_cycle(
linked_bid.bid_id, visited, set()
):
raise ValueError(
f"Circular dependency detected involving bid {linked_bid.bid_id}"
)
if linked_bid.bid_id not in visited and has_cycle(linked_bid.bid_id, visited, set()):
raise ValueError(f"Circular dependency detected involving bid {linked_bid.bid_id}")


__all__ = [
Expand Down
28 changes: 7 additions & 21 deletions src/nexa_bidkit/curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ def _to_decimal(value: Any) -> Decimal:
raise ValueError(f"Cannot convert {type(value)} to Decimal")


def _sort_steps(
steps: list[PriceQuantityStep], curve_type: CurveType
) -> list[PriceQuantityStep]:
def _sort_steps(steps: list[PriceQuantityStep], curve_type: CurveType) -> list[PriceQuantityStep]:
"""Sort steps according to curve type convention.

Args:
Expand Down Expand Up @@ -251,9 +249,7 @@ def from_series_pair(

steps = []
for price, volume in zip(prices, volumes, strict=False):
steps.append(
PriceQuantityStep(price=_to_decimal(price), volume=_to_decimal(volume))
)
steps.append(PriceQuantityStep(price=_to_decimal(price), volume=_to_decimal(volume)))

# Sort steps according to curve type
steps = _sort_steps(steps, curve_type)
Expand Down Expand Up @@ -338,9 +334,7 @@ def scale_curve(curve: PriceQuantityCurve, factor: Decimal) -> PriceQuantityCurv
if factor < 0:
raise ValueError("Scaling factor must be non-negative")

scaled_steps = [
PriceQuantityStep(price=s.price, volume=s.volume * factor) for s in curve.steps
]
scaled_steps = [PriceQuantityStep(price=s.price, volume=s.volume * factor) for s in curve.steps]
return PriceQuantityCurve(
curve_type=curve.curve_type,
steps=scaled_steps,
Expand Down Expand Up @@ -386,9 +380,7 @@ def clip_curve(
cumulative += step.volume
else:
# Partial step
truncated_steps.append(
PriceQuantityStep(price=step.price, volume=remaining)
)
truncated_steps.append(PriceQuantityStep(price=step.price, volume=remaining))
break
steps = truncated_steps

Expand Down Expand Up @@ -418,8 +410,7 @@ def aggregate_by_price(curve: PriceQuantityCurve) -> PriceQuantityCurve:

# Create new steps
steps = [
PriceQuantityStep(price=price, volume=volume)
for price, volume in price_volumes.items()
PriceQuantityStep(price=price, volume=volume) for price, volume in price_volumes.items()
]

# Sort according to curve type
Expand Down Expand Up @@ -468,10 +459,7 @@ def merge_curves(
f"{first.curve_type} vs {curve.curve_type}"
)
if curve.mtu != first.mtu:
raise ValueError(
f"Cannot merge curves with different MTUs: "
f"{first.mtu} vs {curve.mtu}"
)
raise ValueError(f"Cannot merge curves with different MTUs: {first.mtu} vs {curve.mtu}")

# Collect all steps
all_steps = []
Expand Down Expand Up @@ -557,9 +545,7 @@ def get_curve_summary(curve: PriceQuantityCurve) -> dict[str, Any]:

# Calculate volume-weighted average price
if curve.steps and curve.total_volume > 0:
weighted_sum = sum(
s.price * s.volume for s in curve.steps
)
weighted_sum = sum(s.price * s.volume for s in curve.steps)
summary["avg_price"] = weighted_sum / curve.total_volume
else:
summary["avg_price"] = None
Expand Down
3 changes: 1 addition & 2 deletions src/nexa_bidkit/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,7 @@ def validate_period(self) -> DeliveryPeriod:
mtu_seconds = int(mtu_td.total_seconds())
if total_seconds % mtu_seconds != 0:
raise ValueError(
f"DeliveryPeriod span {span} is not a whole number of "
f"{self.duration.value} MTUs"
f"DeliveryPeriod span {span} is not a whole number of {self.duration.value} MTUs"
)
return self

Expand Down
Loading
Loading