diff --git a/codex/economy/flip-candidates.yaml b/codex/economy/flip-candidates.yaml index c32b0d1..22ab173 100644 --- a/codex/economy/flip-candidates.yaml +++ b/codex/economy/flip-candidates.yaml @@ -3,21 +3,25 @@ title: GE Flipping (margin trading) domain: economy tags: [flipping, merchanting, ge, money-making, hybrid] status: verified -version: 1 +version: 2 updated: "2026-08-27" # ISO dates must stay quoted — YAML 1.1 parses bare dates to datetime.date summary: > Buy-and-sell Grand Exchange margin flipping: place resting buy offers at the low edge of an item's price band and resting sell offers at the high edge, harvesting the spread on high-volume items. There are no skill or - quest requirements — capital breadth and slot discipline are the whole game. + quest requirements — capital breadth and slot discipline are the whole + game. Members accounts run 8 GE slots over the full candidate set; + F2P accounts get 3 slots and F2P-tradeable rows only (separate F2P path + in profit_drivers). accounts: - membership: hybrid # flipping works on F2P and members accounts alike + membership: hybrid # members: 8 slots/full set; F2P: 3 slots/F2P rows (see economy) min_level: 3 # post-Tutorial Island baseline; no skill requirement requires_quest: [] requires_other: - "Post-Tutorial Island account with Grand Exchange access (no skill or quest requirement)" + - "Members account: 8 GE slots, full candidate set (the modeled path below). F2P account: 3 GE slots and F2P-tradeable rows only — run the F2P path in profit_drivers." best_practice: equipment: @@ -48,22 +52,35 @@ best_practice: some items share a connected limit with related items — treat those as one bucket when filling slots." source: "OSRS wiki — Grand Exchange (Buy limits; Grand Exchange/Buying limits)" + - name: run the path that matches the account's slot set + why: "Members get eight GE slots and may trade any row; F2P accounts get three slots + and may only purchase free-to-play rows. The modeled economics below assume the + members set — a hybrid account must swap to the 3-slot F2P path (Iron bar / + Cosmic rune / Silver ore) instead of chasing members-only rows it cannot buy." + source: "OSRS wiki — Grand Exchange (Members get eight Grand Exchange slots ... free players are limited to three. Additionally, free players may only purchase free-to-play items, and not members' items)" economy: # Model (documented, reproducible from the snapshot): - # per slot: buy `limit` units at the low edge, sell at the high edge. - # net_pu = 0.98 * sell - buy (2% convenience fee on the sale; the 5M/item cap and - # the sub-50gp rounding exemption are not hit at these prices) - # 4h/slot = net_pu * limit (buy-limit window caps per-4h volume) - # Snapshot 2026-08-27: prices.runescape.wiki mapping + latest; limits cross-checked - # against Module:Exchange/ (all agree). Top 8 by 4h/slot below in profit_drivers. + # Members path (8 GE slots — the modeled economics below): + # per slot: buy `limit` units at the low edge, sell at the high edge. + # tax_pu = floor(0.02 * sell) # 2% convenience fee, rounded DOWN to + # # whole coins per item; the 5M/item cap + # # and the sub-50gp rounding exemption + # # are not hit at these prices + # net_pu = sell - tax_pu - buy # integer per-item net (floored tax) + # 4h/slot = net_pu * limit # buy-limit window caps per-4h volume + # F2P path (3 GE slots, F2P-tradeable rows only): same arithmetic, top-3 + # F2P rows — see profit_drivers for the table and rate. + # Snapshots 2026-08-27: prices.runescape.wiki mapping + latest; limits and + # members flags cross-checked against the wiki item pages (all agree). Top 8 + # by 4h/slot below in profit_drivers. item: Rune arrowtips # anchor: top candidate by modeled 4h/slot (members) item_id: 44 cost_per_cycle: 223 # buy @ low edge per unit (snapshot 2026-08-27) income_per_cycle: 244 # sell @ high edge per unit, gross (snapshot 2026-08-27) - net_per_cycle: 16.12 # 0.98 * 244 - 223 (2% fee applied to the sale) - gp_per_hour_low: 69000 # 50% of the top-8 modeled upper bound — partial fills/drift - gp_per_hour_high: 138000 # top-8 modeled upper bound: sum(4h/slot) / 4 (8 slots, full fills) + net_per_cycle: 17 # 244 - floor(244*0.02) - 223 = 244 - 4 - 223 (floored tax) + gp_per_hour_low: 74750 # 50% of the top-8 modeled upper bound — partial fills/drift + gp_per_hour_high: 149500 # top-8 modeled upper bound: sum(4h/slot) / 4 (8 slots, full fills) xp_per_hour: 0 # flipping awards no XP supplies: - item: Rune arrowtips # canonical wiki names per action-grammar conventions @@ -80,9 +97,10 @@ economy: ge_limit: 18000 profit_drivers: - "Spread per unit x trade volume; volume is the safety factor (high-volume items fill faster and drift less)" - - "Slot breadth: cover as many net-positive candidates as capital allows (standard 8 slots)" - - "Snapshot 2026-08-27 top 8 by modeled 4h/slot: Rune arrowtips 161,200 (buy 223 / sell 244 / limit 10000); Iron bar 88,600 (145/157/10000); Cosmic rune 61,920 (122/128/18000); Magic logs 61,680 (772/793/12000); Silver ore 57,460 (73/79/13000); Mud rune 53,640 (96/101/18000); Raw swordfish 41,100 (206/213/15000); Raw lobster 27,000 (106/110/15000)" - - "Post-2%-fee reality: 11 of 21 classic staples are net-positive at band edges; sub-2%-spread items (law/chaos/blood runes, dragon bones, adamantite/mithril bars) need sub-low buys or above-high sells" + - "Slot breadth: cover as many net-positive candidates as capital allows (members 8 slots / F2P 3 slots)" + - "Members path — snapshot 2026-08-27 top 8 by modeled 4h/slot (floored 2% tax): Rune arrowtips 170,000 (M, buy 223 / sell 244 / limit 10000); Iron bar 90,000 (F2P, 145/157/10000); Cosmic rune 72,000 (F2P, 122/128/18000); Magic logs 72,000 (M, 772/793/12000); Silver ore 65,000 (F2P, 73/79/13000); Mud rune 54,000 (M, 96/101/18000); Raw swordfish 45,000 (F2P, 206/213/15000); Raw lobster 30,000 (F2P, 106/110/15000) — total 598,000 per 4h = 149,500 gp/h on 8 slots" + - "F2P path — 3 slots, F2P-tradeable rows only: Iron bar 90,000 + Cosmic rune 72,000 + Silver ore 65,000 = 227,000 per 4h = 56,750 gp/h (alternates: Raw swordfish 45,000, Raw lobster 30,000)" + - "Post-2%-fee reality: 11 of 21 classic staples are net-positive at band edges for members; sub-2%-spread items (law/chaos/blood runes, dragon bones, adamantite/mithril bars) need sub-low buys or above-high sells" state_machine: initial: idle @@ -96,13 +114,16 @@ state_machine: next: [buy, idle] buy: goal: place one resting buy offer per slot at or below the low edge - next: [wait_fill, collect, idle] - wait_fill: - goal: hold offers; re-quote any approaching the 4h window lapse + next: [wait_buy_fill, collect, idle] + wait_buy_fill: + goal: hold BUY offers; re-quote any approaching the 4h window lapse next: [sell, buy, collect] sell: goal: place one sell offer per filled slot at or above the high edge - next: [wait_fill, collect] + next: [wait_sell_fill, collect] + wait_sell_fill: + goal: hold SELL offers; re-quote any approaching the 4h window lapse + next: [collect, sell, idle] collect: goal: collect proceeds, deposit coins, log the cycle next: [scan, complete, idle] @@ -119,16 +140,22 @@ state_machine: to: idle - from: buy when: "all slots quoted" - to: wait_fill - - from: wait_fill + to: wait_buy_fill + - from: wait_buy_fill when: "all pending buys filled" to: sell - - from: wait_fill - when: "4h window lapse with unfilled offers" + - from: wait_buy_fill + when: "4h window lapse with unfilled BUY offers" to: collect - from: sell when: "sells placed" - to: wait_fill + to: wait_sell_fill + - from: wait_sell_fill + when: "all pending sells filled" + to: collect + - from: wait_sell_fill + when: "4h window lapse with unfilled SELL offers" + to: sell - from: collect when: "session GP target reached" to: complete @@ -216,7 +243,7 @@ actions: delay: [600, 1400] run_control: - iteration: "one full buy -> fill -> sell -> collect rotation across all slots" + iteration: "one full buy -> buy-fill -> sell -> sell-fill -> collect rotation across all slots" max_iterations: null break: every: [6, 10] # rotations diff --git a/tools/validate.py b/tools/validate.py index 7667c3d..5dde203 100644 --- a/tools/validate.py +++ b/tools/validate.py @@ -11,6 +11,10 @@ Runs: => WARNING (entry should be re-verified, not dropped) - `best_practice` lines whose source starts with "researched" => WARNING (citation TODO — file a follow-up issue) + - GE-margin entries (tags include flipping/merchanting): the 2% GE + convenience fee is rounded DOWN to whole coins per item, so + net_per_cycle MUST equal income - floor(0.02*income) - cost. + Fractional-tax arithmetic (0.98*sell - buy) is an error. 3. Global id uniqueness across the whole codex tree. Exit codes: 0 = clean (warnings ok), 1 = validation errors. @@ -19,6 +23,7 @@ Usage: python3 tools/validate.py [--stale-days 30] """ import datetime import json +import math import re import sys from pathlib import Path @@ -107,6 +112,25 @@ def main(): if len(a_steps) != len(set(a_steps)): errors.append(f"{rel}: duplicate action step names") + # GE-margin floor-tax arithmetic. The wiki: "The 2% tax rounds down + # to the nearest whole number, so items sold for below 50 coins have + # a tax of less than one coin" — per-item net MUST use the floored + # tax, never 0.98*sell - buy. Scoped to GE-margin tags so alchemy + # (income is alch value, not a GE sale) is unaffected. + tags = {str(t).lower() for t in data.get("tags", [])} + eco = data.get("economy") or {} + if tags & {"flipping", "merchanting", "ge"} and all( + k in eco for k in ("income_per_cycle", "cost_per_cycle", "net_per_cycle") + ): + sell, buy = eco["income_per_cycle"], eco["cost_per_cycle"] + tax = math.floor(sell * 0.02) + expected = sell - tax - buy + if eco["net_per_cycle"] != expected: + errors.append( + f"{rel}: net_per_cycle {eco['net_per_cycle']} != floored-tax model " + f"{expected} (sell {sell} - floor({sell}*0.02)={tax} - buy {buy}); " + f"GE tax rounds down per item — fractional 0.98*sell - buy is invalid") + # 3. staleness / research TODOs if data.get("status") == "verified": try: