Trading¶
The economic backbone. Players move commodities between stations whose buy/sell patterns and prices respond to local supply.
Stations (ports)¶
Codebase calls them stations (models/station.py); user-facing docs call them ports. Same thing. Each station has a class (0–11) that fixes its trading pattern.
Station classes¶
Source: services/trading_service.py, models/station.py:get_trading_pattern, Resources.aispec.
| Class | Buys | Sells |
|---|---|---|
| 0 (Sol) | special goods + colonists | special goods + colonists |
| 1 | ore | organics, equipment |
| 2 | organics | ore, equipment |
| 3 | equipment | ore, organics |
| 4 (Distribution) | — | ore, organics, equipment, fuel |
| 5 (Collection) | ore, organics, equipment, fuel | — |
| 6 | ore, organics | equipment, fuel |
| 7 | equipment, fuel | ore, organics |
| 8 (Black Hole) | ore, organics, equipment, fuel | — (premium prices) |
| 9 (Nova) | — | ore, organics, equipment, fuel (premium) |
| 10 | gourmet_food | luxury_goods, exotic_technology |
| 11 (Premium Tech Specialist) | exotic_technology, luxury_goods | exotic_technology, luxury_goods (premium prices both directions) |
The Capital Sector of every region holds a Class-0 station that also issues pioneer migration contracts (one cargo unit = one pioneer in cryosleep transit) — see planets/colonization.md. Per-pioneer fee clamp is 30–80 cr (COMMODITY_PRICE_RANGES["colonists"]). The Capital is fixed at sector 1 in Terran Space, randomized within the Federation Zone in player regions, and anchored in the Gateway Plaza cluster of the Central Nexus.
Class 8 / Class 9 premium pricing¶
Class 8 (Black Hole) buys all commodities at +20% over the standard formula. Class 9 (Nova) sells all commodities at +25% over the standard formula. These multipliers are applied after the base supply/demand calculation but before reputation/rank modifiers.
Status: ✅ Shipped —
is_premium_buyer/is_premium_sellerflags onStation(models/station.py) drive the multiplier logic inservices/trading_service.py:apply_modifiers. The numbers above are the design target.
Class 11 (Premium Tech Specialist)¶
Class 11 stations are premium tech-specialist ports — they buy and sell only exotic_technology and luxury_goods, with premium pricing in both directions (+25% buy / +25% sell over the standard formula). Functionally they're the high-end equivalent of Class 8/9 generalists, restricted to the most valuable commodities. The legacy advanced_components commodity is not in COMMODITY_PRICE_RANGES and is dropped from the catalog.
Status: ✅ Shipped —
services/trading_service.py:apply_modifiersnow handles Class 11 via a non-exclusive gate (the Class 8/9 exclusive checks would suppress it). Both directions useget_class_premium(StationClass.CLASS_11, "buy"/"sell")fromcore/station_class_map.py, applying the +25% / +25% target forexotic_technologyandluxury_goods. (confirmed in code 2026-06-16)
Destruction & recovery¶
A destroyed station enters a 24-hour automated rebuild cycle. During recovery:
- The station is non-functional (no docking, no trading, no services).
- Defenses are inactive.
- Cargo, drones, and credits in storage are preserved.
After 24 hours the station auto-rebuilds at reduced capacity (50% of pre-destruction commodity inventory; defenses must be re-purchased).
Status: 📐 Design-only — auto-recovery logic does not exist in code.
Bang generator commodity coverage¶
Status: ✅ Shipped — all 9 gameserver-canonical commodities (
ore,organics,equipment,fuel,precious_metals,gourmet_food,exotic_technology,luxury_goods,colonists) are distributed across procedural port classes 0-8 viasw2102-bang'sTRADING_PATTERNStable (src/content.ts), enforced at generation time by validator rule B-200. Landed via the 9-commodity expansion (2c1b05a) and thefuel_ore→orerename (44950fa), both 2026-05-10 — two months before this section's original bug note was written; the note was stale, not the code.bang_import_service.py's translator also carries default entries for all 9 commodities on import. (re-verified 2026-08-16)
Pricing model¶
services/trading_service.py:calculate_dynamic_price runs a supply/demand formula plus a fixed spread:
supply_ratio = current_quantity / capacity ∈ [0,1]
midpoint = base_price × (1.5 - supply_ratio)
sell_price = midpoint × 1.15 # station charges player
buy_price = midpoint × 0.85 # station pays player
Result is clamped to commodity-specific ranges:
| Commodity | min | max |
|---|---|---|
| ore | 15 | 45 |
| organics | 8 | 25 |
| gourmet_food | 30 | 70 |
| fuel | 20 | 60 |
| equipment | 50 | 120 |
| precious_metals | 80 | 180 |
| exotic_technology | 150 | 300 |
| luxury_goods | 75 | 200 |
| colonists | 30 | 80 |
The 15% spread guarantees sell_price > buy_price (with a final buy_price = sell_price - 1 fallback if rounding ever inverts). This is the structural source of all arbitrage.
Stock regen¶
Passive tick_production survives only as a thin baseline floor — it must not fully refill station stock toward capacity. Tip-shipped on origin/feat 46bce720 via PR #704 (trading_service.py: TICK_PRODUCTION_THIN_FLOOR_RATIO=0.15, TICK_PRODUCTION_RATE_SCALE=0.25, both gameserver-tunable NO-CANON):
- Cap: regen never fills past
capacity * TICK_PRODUCTION_THIN_FLOOR_RATIO; commodities already at/above that floor skip passive regen. - Rate: while below the floor,
production_rateis scaled byTICK_PRODUCTION_RATE_SCALEso invisible fill cannot substitute for real product movement. - Advance-on-read via
update_market_prices(canonical hours elapsed), not a separate scheduler pulse.
Real stock movement is dominated by player and NPC transactions — especially visible supply-delivery restock. See npc-traders.md § Restock by delivery. Production rates remain per-station JSONB on Station.commodities. (re-verified 2026-08-21 vs Sectorwars2102 46bce720.)
Market history¶
Per-(station, commodity) MarketPrice rows track:
- buy_price, sell_price
- previous_buy_price, previous_sell_price
- price_trend — relative change since last tick
- supply_level, demand_level — for analytics
- volatility — derived from price_variance
Drives realtime feeds in services/realtime_market_service.py and ARIA market intelligence.
Trade transaction flow¶
- Player navigates ship to station's sector.
- Dock (1 turn). Sets
Player.is_docked = True,current_port_id. - Trade as many commodities as desired — each transaction is 0 turns.
- Buy: credits debited, cargo credited, station stock decremented.
- Sell: cargo debited, credits credited, station stock incremented.
- Undock (1 turn). Free to move.
TradingService.can_player_trade enforces docking + same-sector before any transaction.
API: services/gameserver/src/api/routes/trading.py.
Faction price modifiers¶
Player's faction reputation modifies the station's effective price (see factions-and-teams.md) — ✅ Shipped (trading_service.compute_player_price_multiplier, wired into both buy and sell paths in api/routes/trading.py):
| Reputation value | Trade multiplier |
|---|---|
| ≥ +700 (EXALTED) | ×0.85 |
| +500 to +699 | ×0.90 |
| +300 to +499 | ×0.95 |
| +100 to +299 | ×0.97 |
| −99 to +99 (NEUTRAL band) | ×1.00 |
| −299 to −100 | ×1.05 |
| −499 to −300 | ×1.15 |
| −699 to −500 | ×1.30 |
| ≤ −700 (PUBLIC ENEMY) | ×1.50 |
Personal reputation also adjusts pricing: ≤ −500 = +20% markup, ≥ +500 = −10% discount (personal_reputation_service.get_reputation_info). ✅ Shipped — trading_service.compute_player_price_multiplier maps Player.reputation_tier through the per-tier band (Villain ×1.20 .. Legendary ×0.90).
Military rank stacks on top with the rank-defined trading bonus (0% to +50% from Recruit to Fleet Admiral).
Price-stacking order¶
Modifiers apply in a fixed order, and the commodity hard bands are the final clamp (canon, blessed by Max 2026-06-14). ✅ Shipped:
- supply/demand
midpoint+ the 15% spread (sell_price/buy_price); - multiplicative modifiers — faction reputation × personal reputation × military
rank × Class-8/9/11 premium × region tariff (
compute_region_tariff_multiplier) × station lever (compute_station_lever_multiplier) — stacked on the spread price, in that order pertrading_service.py:381; - then clamp to the commodity-specific
[min, max]ranges above.
The clamp runs after the premium multiplier, not before it: premium pricing can push a price toward its band edge, but no stack of modifiers may carry a price outside the commodity's hard band. This keeps the bands an absolute floor/ceiling regardless of how modifiers combine.
Haggling¶
Two negotiation paths exist: numerical haggling (back-and-forth offers, max 4 rounds) and narrative haggling (AI-evaluated persuasive statements with anti-exploitation safeguards). See haggling.md for the full design, evaluation rubric, and anti-gaming controls.
Black market¶
Off-the-books trades available at certain stations and via specific NPCs — illicit commodities, stolen goods, and reputation-gated routes. See black-market.md.
Trade contracts¶
Cargo-delivery contracts, escort contracts, and standing orders that pay out on completion. See contracts.md.
TradeDock & shipyard¶
TradeDocks are rare premium stations combining a high-end trading hub with a 12-slip player shipyard (rentable per-day construction of all ship classes including the Warp Jumper). See tradedock-shipyard.md.
Port ownership¶
Players meeting trade-volume and faction-standing thresholds can buy a port and earn tariff income, supply priority, and configure pricing/defenses. See port-ownership.md.
Player-facing affordances¶
- ✅ Trading UI: buy/sell mode switch + resource cards with current prices, trend glyphs, quantity controls, and expandable sparkline (
TradingInterface.tsx/TradingVenue.tsxon origin/feat46bce720). 📐 Two-column "Port Selling" vs "Your Cargo" labels are not on tip (grep empty). - 🚧 Profit/loss context + per-trade inline confirm (
confirm-trade-btn) — not a named confirmation modal. - 🚧 Per-commodity sparkline from REST/
marketStream— not a full market-history chart surface via realtime WS. - 📐 ARIA recommendations overlay (which commodities, which ports) — Design-only.
RoutePlannerPanelis a commerce optimizer, not this overlay. - 📐 Cargo hold visualization with drag-and-drop arrangement — Design-only (
onDrag/ drag-and-drop grep empty on player-client/src).
Source map¶
| Topic | Path |
|---|---|
| Trading service (pricing, ticks) | services/gameserver/src/services/trading_service.py |
| Realtime market | services/gameserver/src/services/realtime_market_service.py |
| Market prediction | services/gameserver/src/services/market_prediction_engine.py |
| Station model + class patterns | services/gameserver/src/models/station.py |
| Market price/transaction models | services/gameserver/src/models/market_transaction.py (canonical), resource.py (catalog metadata) |
| Trade API | services/gameserver/src/api/routes/trading.py |
| Admin economy tools | services/gameserver/src/api/routes/admin_economy.py, admin_comprehensive.py |
| Economy analytics | services/gameserver/src/services/economy_analytics_service.py |
| Faction trade modifier | services/gameserver/src/services/faction_service.py |
| Personal-rep modifier | services/gameserver/src/services/personal_reputation_service.py |
Status: ✅ Shipped — the canonical transaction table (
enhanced_market_transactions, defined inmodels/market_transaction.py) is the sole live transaction ledger; every read/write across the codebase targets it (trading.py,ai_trading_service.py,realtime_market_service.py,ranking_service.py,medal_service.py,contract_dispute.py, all grepped asenhanced_market_transactionsreaders/writers). The legacy resource-enum-keyedmarket_transactionstable previously described here no longer exists inmodels/resource.py— that file today defines onlyResourceType,ResourceQuality,Resource, andMarket— the migrate-off-legacy work this note described has completed.