Hormuz Crisis Simulator (2026)
A turn-based oil trading simulation set during the Iran War — playable in your terminal
!/usr/bin/env python3
"""
HORMUZ CRISIS SIMULATOR
========================
A turn-based economic simulation of the Iran War oil disruption (2026).
You play as the CEO of an independent oil trading firm.
Navigate 30 days of crisis: buy/sell cargoes, short/long positions,
avoid insurance lapses, survive the chaos.
Strategy: survive with positive capital. Score = final capital / 1,000,000.
"""
import random
import sys
import time
─── WORLD STATE ────────────────────────────────────────────────────────────
class World:
def init(self):
self.day = 1
self.brent = 108.0 # $/barrel
self.hormuz_open = 0.10 # 10% capacity (war ongoing)
self.insurance_multiplier = 4.5 # × normal premium
self.global_reserve_days = 58 # SPR + commercial stocks
self.us_military_posture = 0.7 # 0=withdraw, 1=full offensive
self.ceasefire_prob_base = 0.03 # per day
─── PLAYER ─────────────────────────────────────────────────────────────────
class Player:
def init(self):
self.capital = 50_000_000 # $50M starting capital
self.cargoes = [] # list of (barrels, buy_price, cargo_day)
self.shorts = [] # list of (barrels, short_price, expiry_day)
self.longs = [] # list of (barrels, long_price, expiry_day)
self.insurance_held = True
self.risk_level = "MEDIUM"
self.days_without_insurance = 0
─── DISPLAY ────────────────────────────────────────────────────────────────
COLORS = {
"RED": "\033[91m",
"GREEN": "\033[92m",
"YELLOW": "\033[93m",
"CYAN": "\033[96m",
"BOLD": "\033[1m",
"DIM": "\033[2m",
"RESET": "\033[0m",
}
def color(text, *codes):
prefix = "".join(COLORS[c] for c in codes)
return f"{prefix}{text}{COLORS['RESET']}"
def header(world, player):
nw = player.net_worth(world.brent)
nw_str = f"${nw/1e6:.2f}M"
nw_color = "GREEN" if nw > 50e6 else ("YELLOW" if nw > 20e6 else "RED")
def show_positions(player, world):
if player.cargoes:
print(color("\n PHYSICAL CARGOES:", "BOLD"))
for i, (b, bp, cd) in enumerate(player.cargoes):
pnl = b * (world.brent - bp)
pnl_s = color(f"+${pnl:,.0f}", "GREEN") if pnl >= 0 else color(f"-${abs(pnl):,.0f}", "RED")
print(f" [{i}] {b:,} bbl @ ${bp:.2f} PnL: {pnl_s} (day {cd})")
if player.shorts:
print(color("\n SHORT POSITIONS (profit if price falls):", "BOLD"))
for i, (b, sp, ed) in enumerate(player.shorts):
pnl = b * (sp - world.brent)
pnl_s = color(f"+${pnl:,.0f}", "GREEN") if pnl >= 0 else color(f"-${abs(pnl):,.0f}", "RED")
print(f" [{i}] {b:,} bbl short @ ${sp:.2f} PnL: {pnl_s} expires day {ed}")
if player.longs:
print(color("\n LONG POSITIONS (profit if price rises):", "BOLD"))
for i, (b, lp, ed) in enumerate(player.longs):
pnl = b * (world.brent - lp)
pnl_s = color(f"+${pnl:,.0f}", "GREEN") if pnl >= 0 else color(f"-${abs(pnl):,.0f}", "RED")
print(f" [{i}] {b:,} bbl long @ ${lp:.2f} PnL: {pnl_s} expires day {ed}")
─── ACTIONS ────────────────────────────────────────────────────────────────
def action_buy_cargo(player, world):
"""Buy physical oil cargo (you own actual barrels)."""
print(color("\n BUY PHYSICAL CARGO", "BOLD"))
print(f" Current Brent: ${world.brent:.2f}")
print(f" Available cash: ${player.capital/1e6:.2f}M")
print(" Cargo sizes: [1] 250,000 bbl [2] 500,000 bbl [3] 1,000,000 bbl")
sizes = {1: 250_000, 2: 500_000, 3: 1_000_000}
try:
ch = int(input(" Choose size (or 0 to cancel): "))
if ch == 0: return
barrels = sizes.get(ch, 0)
if not barrels: return
def action_sell_cargo(player, world):
"""Sell a physical cargo at current price."""
if not player.cargoes:
print(color(" No cargoes to sell.", "YELLOW"))
return
show_positions(player, world)
try:
idx = int(input(" Select cargo to sell (index): "))
if 0 <= idx < len(player.cargoes):
barrels, buy_price, cd = player.cargoes.pop(idx)
proceeds = barrels * world.brent * 0.98 # 2% spread
player.capital += proceeds
pnl = proceeds - barrels * buy_price * 1.02
pnl_s = color(f"+${pnl/1e6:.3f}M", "GREEN") if pnl >= 0 else color(f"-${abs(pnl)/1e6:.3f}M", "RED")
print(color(f" ✓ Sold {barrels:,} bbl @ ${world.brent:.2f} PnL: {pnl_s}", "GREEN" if pnl >= 0 else "RED"))
except (ValueError, EOFError, IndexError):
return
def action_short(player, world):
"""Open a short futures position (profit if price falls)."""
print(color("\n OPEN SHORT POSITION", "BOLD"))
print(f" Current Brent: ${world.brent:.2f}")
print(f" Margin required: 15% of position value")
print(" Position sizes: [1] 100,000 bbl [2] 500,000 bbl [3] 1,000,000 bbl")
sizes = {1: 100_000, 2: 500_000, 3: 1_000_000}
try:
ch = int(input(" Choose size (or 0 to cancel): "))
if ch == 0: return
barrels = sizes.get(ch, 0)
if not barrels: return
margin = barrels * world.brent * 0.15
if margin > player.capital:
print(color(f" ✗ Insufficient margin! Need ${margin/1e6:.2f}M", "RED"))
return
expiry = world.day + random.randint(5, 10)
player.capital -= margin
player.shorts.append((barrels, world.brent, expiry))
print(color(f" ✓ Short {barrels:,} bbl @ ${world.brent:.2f} Margin: ${margin/1e6:.2f}M Expires day {expiry}", "GREEN"))
except (ValueError, EOFError):
return
def action_long(player, world):
"""Open a long futures position (profit if price rises)."""
print(color("\n OPEN LONG POSITION", "BOLD"))
print(f" Current Brent: ${world.brent:.2f}")
print(f" Margin required: 15% of position value")
print(" Position sizes: [1] 100,000 bbl [2] 500,000 bbl [3] 1,000,000 bbl")
sizes = {1: 100_000, 2: 500_000, 3: 1_000_000}
try:
ch = int(input(" Choose size (or 0 to cancel): "))
if ch == 0: return
barrels = sizes.get(ch, 0)
if not barrels: return
margin = barrels * world.brent * 0.15
if margin > player.capital:
print(color(f" ✗ Insufficient margin! Need ${margin/1e6:.2f}M", "RED"))
return
expiry = world.day + random.randint(5, 10)
player.capital -= margin
player.longs.append((barrels, world.brent, expiry))
print(color(f" ✓ Long {barrels:,} bbl @ ${world.brent:.2f} Margin: ${margin/1e6:.2f}M Expires day {expiry}", "GREEN"))
except (ValueError, EOFError):
return
def action_close_position(player, world):
"""Close a short or long futures position."""
all_positions = []
print(color("\n CLOSE POSITION", "BOLD"))
for i, (b, sp, ed) in enumerate(player.shorts):
pnl = b * (sp - world.brent)
pnl_s = color(f"+${pnl:,.0f}", "GREEN") if pnl >= 0 else color(f"-${abs(pnl):,.0f}", "RED")
print(f" [S{i}] SHORT {b:,} bbl @ {sp:.2f} PnL: {pnl_s}")
all_positions.append(('S', i))
for i, (b, lp, ed) in enumerate(player.longs):
pnl = b * (world.brent - lp)
pnl_s = color(f"+${pnl:,.0f}", "GREEN") if pnl >= 0 else color(f"-${abs(pnl):,.0f}", "RED")
print(f" [L{i}] LONG {b:,} bbl @ {lp:.2f} PnL: {pnl_s}")
all_positions.append(('L', i))
if not all_positions:
print(color(" No open positions.", "YELLOW"))
return
try:
raw = input(" Enter position to close (e.g. S0 or L1, or 0 to cancel): ").strip().upper()
if raw == '0': return
kind = raw[0]
idx = int(raw[1:])
if kind == 'S' and 0 <= idx < len(player.shorts):
barrels, short_price, _ = player.shorts.pop(idx)
pnl = barrels * (short_price - world.brent)
margin_returned = barrels * short_price * 0.15
player.capital += margin_returned + pnl
pnl_s = color(f"+${pnl/1e6:.3f}M", "GREEN") if pnl >= 0 else color(f"-${abs(pnl)/1e6:.3f}M", "RED")
print(color(f" ✓ Short closed PnL: {pnl_s}", "GREEN" if pnl >= 0 else "RED"))
elif kind == 'L' and 0 <= idx < len(player.longs):
barrels, long_price, _ = player.longs.pop(idx)
pnl = barrels * (world.brent - long_price)
margin_returned = barrels * long_price * 0.15
player.capital += margin_returned + pnl
pnl_s = color(f"+${pnl/1e6:.3f}M", "GREEN") if pnl >= 0 else color(f"-${abs(pnl)/1e6:.3f}M", "RED")
print(color(f" ✓ Long closed PnL: {pnl_s}", "GREEN" if pnl >= 0 else "RED"))
else:
print(color(" Invalid selection.", "RED"))
except (ValueError, EOFError, IndexError):
return
def action_toggle_insurance(player, world):
"""Toggle war-risk insurance (expensive but required for cargo)."""
if player.insurance_held:
print(color("\n ⚠ CANCEL INSURANCE?", "YELLOW"))
daily = player.insure_cost(world)
print(f" Current daily cost: ${daily:,.0f}")
print(" Warning: Without insurance, any tanker hit = cargo lost!")
try:
confirm = input(" Confirm cancel? (yes/no): ").strip().lower()
if confirm == 'yes':
player.insurance_held = False
print(color(" ✓ Insurance cancelled. Sailing dark.", "YELLOW"))
except EOFError:
return
else:
cost = 500_000 # flat reinstatement fee
print(color(f"\n REINSTATE INSURANCE — flat fee ${cost:,}", "BOLD"))
if player.capital < cost:
print(color(" ✗ Insufficient capital!", "RED"))
return
try:
confirm = input(" Confirm? (yes/no): ").strip().lower()
if confirm == 'yes':
player.capital -= cost
player.insurance_held = True
player.days_without_insurance = 0
print(color(" ✓ War-risk insurance reinstated.", "GREEN"))
except EOFError:
return
def action_intel(world):
"""Pay for intelligence briefing on ceasefire probability."""
print(color("\n INTELLIGENCE BRIEFING", "BOLD"))
print(f" Hormuz throughput: {world.hormuz_open*100:.1f}% of normal")
print(f" Global reserves: {world.global_reserve_days:.0f} days (critical < 30)")
print(f" Insurance market: {world.insurance_multiplier:.1f}× normal premium")
print(f" US posture: {world.us_military_posture:.0%} (escalation index)")
─── TURN LOOP ───────────────────────────────────────────────────────────────
def process_day_end(player, world):
"""Handle end-of-day mechanics: insurance costs, position expiries, tanker risk."""
messages = []
def play():
world = World()
player = Player()
╔══════════════════════════════════════════════════════════╗
║ HORMUZ CRISIS SIMULATOR · 2026 ║
║ Iran War — Day 10. Hormuz at 10% capacity. ║
║ You have $50M. Survive 30 days. Grow it. ║
╚══════════════════════════════════════════════════════════╝""", "BOLD", "CYAN"))
print("""
You are CEO of Void Trading Ltd, an independent energy firm.
Navigate the most volatile oil market since 1973.
KEY MECHANICS:
• Physical cargoes: own real barrels, move with price
• Futures shorts: profit when price falls (war ends)
• Futures longs: profit when price rises (war escalates)
• Insurance: required for cargoes; ~$0.15/bbl/day × crisis multiplier
• Random world events change everything every day
• Ceasefire = price collapses instantly
Press ENTER to begin...
""")
try:
input()
except EOFError:
pass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | game_over = False
ceasefire = False
while world.day <= 30 and not game_over:
# ── Daily event ──
event = world.roll_event()
world.natural_drift()
if event:
name, desc, _ = event
world.apply_event(event)
# ── Display ──
header(world, player)
if event:
print(f"\n 📰 {color(name, 'YELLOW', 'BOLD')}")
print(f" {desc}")
print(f" → Brent now ${world.brent:.2f}")
else:
print(f"\n 📰 No major events today. Markets drift.")
print(f" → Brent now ${world.brent:.2f}")
# ── Ceasefire check ──
if world.check_ceasefire() and world.day > 5:
ceasefire = True
crash = -world.brent * 0.35 # 35% crash on ceasefire
world.brent += crash
world.brent = max(60, world.brent)
world.hormuz_open = min(1.0, world.hormuz_open + 0.6)
print(color(f"\n 🕊 CEASEFIRE DECLARED! War ends on day {world.day}!", "GREEN", "BOLD"))
print(f" Brent crashes to ${world.brent:.2f}. Hormuz reopening.")
print(" Settling all positions...")
# Force-settle everything
for barrels, bp, _ in player.cargoes:
proceeds = barrels * world.brent * 0.98
player.capital += proceeds
for barrels, sp, _ in player.shorts:
pnl = barrels * (sp - world.brent)
player.capital += barrels * sp * 0.15 + pnl
for barrels, lp, _ in player.longs:
pnl = barrels * (world.brent - lp)
player.capital += barrels * lp * 0.15 + pnl
player.cargoes = []
player.shorts = []
player.longs = []
break
# ── Player actions ──
print("\n ACTIONS:")
print(" [1] Buy physical cargo [2] Sell cargo")
print(" [3] Open short (bet on fall) [4] Open long (bet on rise)")
print(" [5] Close position [6] Toggle insurance")
print(" [7] Intel briefing (free) [8] View positions [P] Pass")
actions_taken = 0
max_actions = 2 # can do 2 actions per day
while actions_taken < max_actions:
remaining = max_actions - actions_taken
try:
choice = input(f"\n Action ({remaining} remaining, P to end turn): ").strip().upper()
except EOFError:
choice = 'P'
if choice == 'P' or choice == '':
break
elif choice == '1':
action_buy_cargo(player, world)
actions_taken += 1
elif choice == '2':
action_sell_cargo(player, world)
actions_taken += 1
elif choice == '3':
action_short(player, world)
actions_taken += 1
elif choice == '4':
action_long(player, world)
actions_taken += 1
elif choice == '5':
action_close_position(player, world)
actions_taken += 1
elif choice == '6':
action_toggle_insurance(player, world)
actions_taken += 1
elif choice == '7':
action_intel(world)
# intel is free, doesn't count
elif choice == '8':
show_positions(player, world)
# viewing is free
else:
print(color(" Unknown action.", "YELLOW"))
# ── End of day ──
eod_msgs = process_day_end(player, world)
if eod_msgs:
print("\n END OF DAY:")
for m in eod_msgs:
print(m)
# Bankruptcy check
if player.capital < 0 and not player.cargoes and not player.shorts and not player.longs:
print(color("\n ☠ BANKRUPT — Game Over", "RED", "BOLD"))
game_over = True
break
world.day += 1
if world.day <= 30:
try:
input("\n Press ENTER for next day...")
except EOFError:
pass
# ── Final score ──
final_nw = player.net_worth(world.brent)
print("\n" + "═" * 60)
print(color(" GAME OVER", "BOLD", "CYAN"))
print("═" * 60)
if ceasefire:
print(color(f" 🕊 War ended on Day {world.day}. Ceasefire!", "GREEN"))
elif game_over:
print(color(" You were wiped out.", "RED"))
else:
print(f" Survived all 30 days. Final Brent: ${world.brent:.2f}")
print(f"\n Final Net Worth: ${final_nw/1e6:.3f}M")
print(f" Starting Capital: $50.000M")
pnl = final_nw - 50_000_000
pnl_s = color(f"+${pnl/1e6:.3f}M", "GREEN") if pnl >= 0 else color(f"-${abs(pnl)/1e6:.3f}M", "RED")
print(f" Total PnL: {pnl_s}")
score = final_nw / 1_000_000
print(f"\n SCORE: {score:.1f}")
if score > 200:
print(color(" ★★★ LEGENDARY: You broke the market.", "YELLOW", "BOLD"))
elif score > 100:
print(color(" ★★☆ EXCELLENT: Soros-tier crisis trading.", "GREEN", "BOLD"))
elif score > 60:
print(color(" ★☆☆ GOOD: Solid returns in chaos.", "GREEN"))
elif score > 40:
print(color(" Survived. Capital preserved.", "YELLOW"))
else:
print(color(" Took losses. The market humbled you.", "RED"))
print("\n" + "═" * 60)
|
if name == "main":
play()