Coflnet.Sky.Bazaar.Flipper.Client 0.9.0

dotnet add package Coflnet.Sky.Bazaar.Flipper.Client --version 0.9.0
                    
NuGet\Install-Package Coflnet.Sky.Bazaar.Flipper.Client -Version 0.9.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Coflnet.Sky.Bazaar.Flipper.Client" Version="0.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Coflnet.Sky.Bazaar.Flipper.Client" Version="0.9.0" />
                    
Directory.Packages.props
<PackageReference Include="Coflnet.Sky.Bazaar.Flipper.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Coflnet.Sky.Bazaar.Flipper.Client --version 0.9.0
                    
#r "nuget: Coflnet.Sky.Bazaar.Flipper.Client, 0.9.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Coflnet.Sky.Bazaar.Flipper.Client@0.9.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Coflnet.Sky.Bazaar.Flipper.Client&version=0.9.0
                    
Install as a Cake Addin
#tool nuget:?package=Coflnet.Sky.Bazaar.Flipper.Client&version=0.9.0
                    
Install as a Cake Tool

BazaarFlipper template for new microservices

Demand Flip Methodology

The demand flip score uses a short rolling history of bazaar movement instead of ranking by spread alone.

  1. The service keeps a 3 minute lookback window of ordered GraphResult updates.
  2. For each adjacent pair it computes positive-only changes in BuyMovingWeek and SellMovingWeek.
  3. Older intervals inside the active window are half-weighted so the score leans toward more recent momentum.
  4. Each side gets a sustained-demand weight from the share of positive intervals, raised to 2.5.
  5. The directional signals are built as:
    • buySignal = avgBuyIncrease * buyDemandWeight * 1.0
    • sellSignal = avgSellIncrease * sellDemandWeight
  6. The round-trip fill score is asymmetric and currently tuned to:
    • roundTripSignal = sellSignal^0.8 * buySignal^0.2
  7. After-fee spread is then applied to convert that fill score into profit and capacity estimates.

In code this becomes:

spreadAfterOrder = (Buy - 0.1) * (1 - 0.01125) - (Sell + 0.1)   // sell-leg tax only; buy orders untaxed
currentProfitPerHour = spreadAfterOrder * roundTripSignal * 30
hourlyVolume = min(sellSignal, buySignal) * 30
exitHourlyVolume = buySignal * 30

The demand API exposes those results in two ways:

  • Volume is the estimated daily turnover at the current bottleneck pace.
  • SuggestedOrderVolume is the lot size for the requested hold window.
  • EstimatedSellTimeMinutes estimates how long that suggested lot should take to exit, or null when there is not enough current demand to produce a reliable estimate.
  • TotalProfit is the spread profit for that suggested lot at current prices.

Demand Benchmarking

The current constants were selected from a five item historical benchmark using:

  • BOX_OF_SEEDS
  • SHARD_NESSIE
  • BOOSTER_COOKIE
  • SHARD_BAMBULEAF
  • REFINED_UMBER

Each benchmark sample evaluates a 3 minute window and compares the chosen item against a weighted future round-trip oracle using these horizons:

  • 1 minute at weight 0.34
  • 5 minutes at weight 0.26
  • 10 minutes at weight 0.18
  • 20 minutes at weight 0.12
  • 30 minutes at weight 0.10

The tuned configuration changed from:

  • InstaBuyDemandWeight = 2.0
  • SustainedDemandExponent = 2.0
  • ProfitWeightedSellExponent = 0.4
  • ProfitWeightedBuyExponent = 0.6

to:

  • InstaBuyDemandWeight = 1.0
  • SustainedDemandExponent = 2.5
  • ProfitWeightedSellExponent = 0.8
  • ProfitWeightedBuyExponent = 0.2

That update improved weighted oracle capture from roughly 94% to roughly 95% in the sweep used to select the current constants. In exchange, the exact top-pick hit rate settles at roughly 89.9% on the current benchmark sample set.

Tax correction (2026-07-12): those figures were computed with a wrong ~2.4% round-trip tax (the buy leg was taxed too). The bazaar taxes only the sell leg — 1.125% round trip; the spread formula is now (Buy - 0.1) * (1 - 0.01125) - (Sell + 0.1). Under the corrected tax this 5-item set no longer favors the profit-weighted score on oracle capture (the momentum exponents were tuned under the wrong tax and warrant a re-tune). The authoritative, tax-corrected evidence is the 80-item and historical-window pools below, where RankMode.Throughput robustly wins — and the production sort now uses it, so the 0.8/0.2 profit-weighting exponents no longer affect ordering.

Realistic-profit research (fill simulator + factor experiments)

The statistical benchmark above is nearly saturated, but its oracle is built from the same ingredients as the score (future moving-week deltas × top-of-book spread), so it cannot see order competition, being undercut, price drift during the hold, or capital lock time. To measure those, the scoring math was extracted into a single reusable core and graded against a mechanism-level ground truth.

  • Services/DemandFlipScoring.cs — the one scoring implementation used by DemandFlipService, the controller and the backtests. ScoringOptions.Default reproduces the historical formula bit-for-bit (guarded by RefactoredServiceMatchesLegacyFormulaOnAllHistories). Optional factor knobs are all off by default: SpreadMode (H2 spread durability), ChurnBeta (H3 undercut churn), CrowdingAlpha (H1 order competition), OverhangGamma (H4 queue overhang).
  • Services/OrderFillSimulator.cs — simulates actually placing the buy order (Sell + 0.1), accruing fills from insta-sell flow while at the top of book, being outbid/undercut, re-listing, timeouts and final liquidation. Fill share is optimistic, count-growth, or (with order-book depth) DepthLevel1. It is the independent oracle.
  • Services/DemandFlipBenchmark.Tests.cs — 5-item baseline + oracle-disagreement table.
  • Services/DemandFlipWideBenchmark.Tests.cs — the reliable run: 12 diverse items × 14 days of 20s snapshots with order-book depth, pulled from the SkyBazaar history service into Mock/depth/ (gitignored). Factor grids vs two sim oracles, walk-forward split, and leave-one-item-out stability. Assert.Ignores when the depth data is not present locally.

Pull the depth data (needs the SkyBazaar history service reachable, e.g. kubectl port-forward -n sky svc/sky-bazaar 5011:8000) via GET /api/bazaar/{item}/data?start&end&smallestResolution=true&includeArchivedOrderbook=true.

Key findings (14-day, 12-item, 1557 samples)

  • The statistical oracle and the fill-simulator oracle disagree on the best item about half the time. The shipped model captures ~84% of statistical-oracle profit but only ~60% of realistic (depth-aware fill-sim) profit — the realistic gap is real and larger than the statistical benchmark implies.
  • Modeling competition at your own price level (depth-aware queue share) lowers realized profit materially vs the optimistic bound, confirming undercutting is a first-order effect.
  • Among the interpretable factors, the near-exit supply wall (H6, depth) and a mild undercut-churn penalty (H3, β≈0.5) improve realistic-profit ranking on the held-out fold without hurting the statistical benchmark, and are stable across leave-one-item-out. The drift-adjusted spread (H2) captures the most realized profit but degrades the statistical metrics — a higher-variance bet. Order-count crowding (H1) and level-1 order count alone did not help.

None of the factors is a large enough, clean enough winner to flip on blindly, so defaults are unchanged. They can be enabled for a live A/B without a redeploy via the DEMAND config section (DEMAND:SPREADMODE, DEMAND:CHURNBETA, DEMAND:CROWDINGALPHA, DEMAND:OVERHANGGAMMA).

Top-N basket capture — the product-relevant metric (biggest finding)

The demand API ranks a top-N list, so "did we pick the single absolute best item" understates the product. Services/PortfolioCapture.Tests.cs measures how much realized profit our top-N set captures vs the best-achievable top-N, over an 80-item liquid pool (Mock/wide/, gitignored).

  • On a realistic 80-item pool our current top-10 captures only ~47% of the best-possible top-10 (and ~33% of all market profit vs a 66% ceiling for a perfect top-10) — real headroom that the 12-item toy pool hid (there top-10-of-12 is trivially ~98%).
  • The micro-structure factors (H1-H4, H6) do not move the top-10 basket capture — noise.
  • The lever is the ranking formulation: the momentum score is a geometric-mean signal, but the oracle grades realized coins/hour, so ranking by a throughput/volume estimate helps.

Multi-window confirmation (2026-07-12) — sqrt-volume was overfit, Throughput is the winner

The +8pp for spread × √volume came from a single 14-day window. Confirming it against four disjoint, all-older-than-14-day windows of 5-min archived history (top-40 liquid items, Mock/histwin/, gitignored; Services/HistoricalWindows.Tests.cs) reversed it, and two isolation tests on the existing 80-item/20s pool explain why:

  • spread × √volume (SpreadSqrtVolume) fails to generalize: 0/4 historical windows, −11.9pp avg. It is a pool-breadth artifact — FormulationDependsOnPoolBreadthWidePool shows it only helps when the pool is padded with low-volume items to deprioritize (broad 80-item: 55.5% vs momentum 46.9%); on the all-liquid top-40 subset momentum jumps to 82.1% and √volume stays 57.1%.
  • The apparent edge is not a lookback effect: MomentumLookbackVsFormulationOnWidePool shows widening the momentum lookback (3→60 min) does not help (46.6%→44.3%).
  • Throughput (spread × momentum-derived hourly fill rate) beats momentum in every regime: broad 80-item +3.8pp, all-liquid top-40 +5.5pp, and 4/4 historical windows (+1.2pp avg). It keeps momentum's timing while scaling by realistic fill volume, so it avoids thin-spread mega-volume traps. It is the recommended ranking if the default is changed.

This is exposed as ScoringOptions.RankMode (Momentum, Throughput, SpreadSqrtVolume, SpreadVolume). It changes only the sort key, not the displayed profit/hour. The production default (resolved in Startup.BuildDemandScoringOptions) is now Throughput, since it beat the momentum sort 6/6 across pool breadth and time. ScoringOptions.Default itself is left as the momentum formula (the equivalence-guard and benchmark baseline); the live default is the DI-provided value. Override via DEMAND:RANKMODE (e.g. Momentum to revert, for an A/B control). The historical margin is small (~1pp) and every oracle shares the moving-week fill assumption, so a live A/B — or wiring in real per-user fills as an independent oracle — is still the way to settle it precisely.

Run the experiments: dotnet test --filter "Category=Benchmark" (wide runs need Mock/depth/, Mock/wide/; the multi-window run needs Mock/histwin/ — pull via scratchpad/pull_histwin.py).

Fleet allocation — coordinating contending traders (2026-08-17)

The ranking above is single-trader. A pool of K traders (nominal 20, range 10–30) each holding ≤ M=10 distinct items contend for the same finite market flow: the fill simulator only fills an order while it is at top of book, so N traders piled on one item split that one ΔmovingWeek flow. Per-item group profit is therefore concave in traders-per-item (linear while capital-limited, flat once the flow is saturated), and the profit-maximising allocation is greedy water-filling — Services/FleetAllocator.cs (Allocate(items, opts)): hand each next trader-slot to the item with the highest marginal gain, cap n_i ≤ K, stop when idle capital beats a starved order, then pack the counts into K baskets of ≤ M distinct items (Gale–Ryser).

Services/FleetCapture.Tests.cs grades this walk-forward on the four Mock/histwin/ windows against the same OrderFillSimulator, comparing four allocations (all scored by the identical simulator): pileTopM (today's uncoordinated fleet — every trader takes the top-M by RankScore, n_i = K), spreadTopKM (naive 1-per-item diversification), capacityAware (the allocator), and oracle (foresight-greedy on the realized simulator objective — a reference ceiling).

  • capacityAware beats the best naive baseline by +70% at K=20 / 10M-coins-per-order (538M vs 317M aggregate realized coins), robust in every window (+62→79%) and every fleet size (K=10 +36%, K=20 +70%, K=30 +105% — more contenders, more coordination gain). It reaches ~51% of the perfect-foresight ceiling, so a smarter allocator still has room.
  • Uplift is capital-regime-dependent (per-order capital sweep): +32% at 2M, +70% at 10M, +12% at 50M. With large orders thin books overload — the profitable ceiling drops below K·M slots and naive piling collapses (pile falls 238M→84M as K grows at 50M).

Caveats held in view (not tuned away): graded by the moving-week fill simulator, not yet by independent real fills; pileTopM is flattered (intra-fleet undercutting unmodelled, so the real tax is likely larger); the oracle is foresight-greedy, not a proven optimum.

Heterogeneous / tiered allocation (Services/TieredFleetAllocator.cs). Real traders aren't a homogeneous fleet: different tiers (premium+ > premium > …), budgets, and fast/patient modes, with real users preferred over bots and only ACTIVE users counted. The allocator does priority-ordered, budget-bounded, return-on-capital greedy filling that drains shared per-item capacity and emits the leftover as a residual feed (for untracked website users). It carries a per-item maxCoinExposurePerItem cap — a required risk control: without it the greedy allocator over-commits the fleet to a single thin, mis-estimated item and lost to production by −44% on real data.

Services/TieredAllocation.Tests.cs grades it against a FAITHFUL model of today's production (SkyModCommands.BazaarFlipService: items deduped across same-tier users via assignedTags, one order/user/cycle, fixed 64/4/1 order sizes from BazaarOrderAmountHelper — NOT budget-scaled). Honest result: the edge is capital-dependent. The dominant lever is budget-aware order sizing — production deploys fixed tiny stacks that leave large purses idle. Swept over budget scale × steady-state slots/user S: at 1x budgets tiered wins +26→+151%; at 0.5x budget with production fully ramped (S=10) it loses −19% (reported, not tuned away). Takeaway: size orders off purse + capacity (floored at production's fixed size so low-purse users never do worse), and the biggest, lowest-risk production win is that sizing change, not the routing. Priority fidelity holds (premium+ > premium > starter realized profit/trader). Patient-mode scoring is a later step (all benchmark traders are Mode=Fast for now).

Real-fill persistence (the independent oracle, accumulating): SkyUserState emits every completed flip to the sky-bazaar-flip Kafka topic (TOPICS:BAZAAR_FLIP). Services/ObservedBazaarFillBackgroundService.cs now consumes it (fresh group, AutoOffsetReset.Earliest to backfill) and persists to ObservedBazaarFill (EF/MySQL) via Services/ObservedFillService.cs (dedupes on player+item+soldAt). Schema is additive and guarded behind MIGRATE_ON_STARTUP (default off; a normal startup does zero DDL). This is what lets the fleet study eventually be re-graded against measured realized profit rather than the moving-week sim.

Deploying

This project should be deployed within a container.

Configuration

See appsettings.json

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.0 622 8/21/2026
0.8.0 127 8/18/2026
0.7.0 1,431 3/21/2026
0.6.0 304 2/10/2026
0.5.0 947 11/5/2025
0.4.2 473 9/10/2025
0.4.1 217 9/9/2025
0.4.0 219 9/9/2025
0.3.2 358 8/23/2025
0.3.1 810 7/21/2025
0.3.0 589 7/21/2025
0.2.0 453 6/29/2025
0.1.2 2,706 10/31/2024
0.1.1 3,293 4/24/2024
0.1.0 481 3/29/2024
0.0.2 907 12/23/2023
0.0.1 286 12/23/2023

Minor update