whatif.py: add --remove for teardown/demolish scenarios
Mirrors --add - removes AMOUNT (default: all) of a building from the copied spec. Used this to check whether BHP's HWP is still worth running after its price drop (still +19.8k-28.4k/day marginal, nowhere near a teardown call) without hand-writing two spec files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,18 @@ def test_apply_add_appends_new_building_with_recipe():
|
|||||||
assert spec["buildings"] == [{"building": "CHP", "amount": 1, "recipes": ["MG,S,CU=>IND"]}]
|
assert spec["buildings"] == [{"building": "CHP", "amount": 1, "recipes": ["MG,S,CU=>IND"]}]
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_remove_drops_building_entirely_by_default():
|
||||||
|
spec = {"buildings": [{"building": "HWP", "amount": 1, "recipes": ["AL,STL,HE=>BHP"]}]}
|
||||||
|
whatif.apply_remove(spec, "HWP")
|
||||||
|
assert spec["buildings"] == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_remove_partial_amount():
|
||||||
|
spec = {"buildings": [{"building": "CHP", "amount": 3, "recipes": ["MG,S,CU=>IND"]}]}
|
||||||
|
whatif.apply_remove(spec, "CHP:1")
|
||||||
|
assert spec["buildings"] == [{"building": "CHP", "amount": 2, "recipes": ["MG,S,CU=>IND"]}]
|
||||||
|
|
||||||
|
|
||||||
def test_apply_infra_adds_delta():
|
def test_apply_infra_adds_delta():
|
||||||
spec = {"infrastructure": {"HB1": 4}}
|
spec = {"infrastructure": {"HB1": 4}}
|
||||||
whatif.apply_infra(spec, "HB1:+1,HB2:+2")
|
whatif.apply_infra(spec, "HB1:+1,HB2:+2")
|
||||||
|
|||||||
+19
-2
@@ -37,6 +37,20 @@ def apply_add(spec: dict, add: str):
|
|||||||
spec["buildings"].append({"building": ticker, "amount": amount, "recipes": recipes})
|
spec["buildings"].append({"building": ticker, "amount": amount, "recipes": recipes})
|
||||||
|
|
||||||
|
|
||||||
|
def apply_remove(spec: dict, remove: str):
|
||||||
|
"""BUILDING[:AMOUNT] - a teardown scenario: removes AMOUNT (default: all) of that building
|
||||||
|
from the plan. For "should we demolish this" questions - pass the current full base as
|
||||||
|
--after and the --remove result as the plain positional arg (or vice versa; whatif always
|
||||||
|
reports AFTER minus BEFORE, so order which one is which by what you want the sign to mean)."""
|
||||||
|
parts = remove.split(":", 1)
|
||||||
|
ticker = parts[0]
|
||||||
|
amount = int(parts[1]) if len(parts) == 2 else None
|
||||||
|
for b in spec.get("buildings", []):
|
||||||
|
if b["building"] == ticker:
|
||||||
|
b["amount"] = max(0, b["amount"] - amount) if amount else 0
|
||||||
|
spec["buildings"] = [b for b in spec.get("buildings", []) if b["amount"] > 0]
|
||||||
|
|
||||||
|
|
||||||
def apply_infra(spec: dict, infra: str):
|
def apply_infra(spec: dict, infra: str):
|
||||||
"""HB1:+1,HB2:+2 - deltas added to spec['infrastructure']."""
|
"""HB1:+1,HB2:+2 - deltas added to spec['infrastructure']."""
|
||||||
inf = spec.setdefault("infrastructure", {})
|
inf = spec.setdefault("infrastructure", {})
|
||||||
@@ -56,6 +70,7 @@ def main():
|
|||||||
ap.add_argument("before", help="full-base plan spec as it stands (or as a baseline you want to compare from)")
|
ap.add_argument("before", help="full-base plan spec as it stands (or as a baseline you want to compare from)")
|
||||||
ap.add_argument("--after", help="full-base plan spec with the addition already in it; skips --add/--infra")
|
ap.add_argument("--after", help="full-base plan spec with the addition already in it; skips --add/--infra")
|
||||||
ap.add_argument("--add", action="append", default=[], help="BUILDING:AMOUNT[:RECIPE[|RECIPE]], repeatable")
|
ap.add_argument("--add", action="append", default=[], help="BUILDING:AMOUNT[:RECIPE[|RECIPE]], repeatable")
|
||||||
|
ap.add_argument("--remove", action="append", default=[], help="BUILDING[:AMOUNT] - teardown scenario, removes AMOUNT (default: all), repeatable")
|
||||||
ap.add_argument("--infra", action="append", default=[], help="HB1:+1,HB2:+2, repeatable")
|
ap.add_argument("--infra", action="append", default=[], help="HB1:+1,HB2:+2, repeatable")
|
||||||
ap.add_argument("--cx", default=config.DEFAULT_CX)
|
ap.add_argument("--cx", default=config.DEFAULT_CX)
|
||||||
ap.add_argument("--basis", default="uni30", choices=["real", "uni30", "vwap30", "vwap7", "ask", "bid", "mid"])
|
ap.add_argument("--basis", default="uni30", choices=["real", "uni30", "vwap30", "vwap7", "ask", "bid", "mid"])
|
||||||
@@ -66,11 +81,13 @@ def main():
|
|||||||
if a.after:
|
if a.after:
|
||||||
after_spec = yaml.safe_load(Path(a.after).read_text())
|
after_spec = yaml.safe_load(Path(a.after).read_text())
|
||||||
else:
|
else:
|
||||||
if not a.add and not a.infra:
|
if not a.add and not a.remove and not a.infra:
|
||||||
sys.exit("give --after, or at least one --add/--infra")
|
sys.exit("give --after, or at least one --add/--remove/--infra")
|
||||||
after_spec = copy.deepcopy(before_spec)
|
after_spec = copy.deepcopy(before_spec)
|
||||||
for add in a.add:
|
for add in a.add:
|
||||||
apply_add(after_spec, add)
|
apply_add(after_spec, add)
|
||||||
|
for remove in a.remove:
|
||||||
|
apply_remove(after_spec, remove)
|
||||||
for infra in a.infra:
|
for infra in a.infra:
|
||||||
apply_infra(after_spec, infra)
|
apply_infra(after_spec, infra)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user