Skip to content

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_seller flags on Station (models/station.py) drive the multiplier logic in services/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_modifiers now handles Class 11 via a non-exclusive gate (the Class 8/9 exclusive checks would suppress it). Both directions use get_class_premium(StationClass.CLASS_11, "buy"/"sell") from core/station_class_map.py, applying the +25% / +25% target for exotic_technology and luxury_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 via sw2102-bang's TRADING_PATTERNS table (src/content.ts), enforced at generation time by validator rule B-200. Landed via the 9-commodity expansion (2c1b05a) and the fuel_oreore rename (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_rate is scaled by TICK_PRODUCTION_RATE_SCALE so 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

  1. Player navigates ship to station's sector.
  2. Dock (1 turn). Sets Player.is_docked = True, current_port_id.
  3. Trade as many commodities as desired — each transaction is 0 turns.
  4. Buy: credits debited, cargo credited, station stock decremented.
  5. Sell: cargo debited, credits credited, station stock incremented.
  6. 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:

  1. supply/demand midpoint + the 15% spread (sell_price / buy_price);
  2. 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 per trading_service.py:381;
  3. 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.tsx on origin/feat 46bce720). 📐 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. RoutePlannerPanel is 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 in models/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 as enhanced_market_transactions readers/writers). The legacy resource-enum-keyed market_transactions table previously described here no longer exists in models/resource.py — that file today defines only ResourceType, ResourceQuality, Resource, and Market — the migrate-off-legacy work this note described has completed.