# delta F is the sum of the three original flow-fee terms; use F*35/(20+F), in bps. Shift 0.15 bps bid→ask, bounded by available bid. Original constants and state updates stay intact. Changed/added lines: ```solidity uint256 flowFee = wmul(LAMBDA_COEF, lambdaHat) + wmul(FLOW_SIZE_COEF, flowSize) + wmul(ACT_COEF, actEma); flowFee = flowFee * (35 * BPS) / (20 * BPS + flowFee); uint256 fBase = BASE_FEE + wmul(SIGMA_COEF, sigmaHat) + flowFee; uint256 fMid = fBase + wmul(TOX_COEF, toxSignal) + wmul(TOX_QUAD_COEF, wmul(toxSignal, toxSignal)); uint256 leanCorrection = 150 * BPS / 1000; if (leanCorrection > bidFee) leanCorrection = bidFee; bidFee -= leanCorrection; askFee = clampFee(askFee + leanCorrection); ``` # code ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {AMMStrategyBase} from "./AMMStrategyBase.sol"; import {TradeInfo} from "./IAMMStrategy.sol"; contract Strategy is AMMStrategyBase { uint256 constant ELAPSED_CAP = 8; uint256 constant SIGNAL_THRESHOLD = WAD / 650; uint256 constant DIR_DECAY = 750000000000000000; uint256 constant ACT_DECAY = 530000000000000000; uint256 constant SIZE_DECAY = 530000000000000000; uint256 constant TOX_DECAY = 910000000000000000; uint256 constant SIGMA_DECAY = 690000000000000000; uint256 constant LAMBDA_DECAY = 990000000000000000; uint256 constant SIZE_BLEND_DECAY = 828000000000000000; uint256 constant TOX_BLEND_DECAY = 51000000000000000; uint256 constant ACT_BLEND_DECAY = 985000000000000000; uint256 constant PHAT_ALPHA = 270000000000000000; uint256 constant PHAT_ALPHA_RETAIL = 60000000000000000; uint256 constant PHAT_ALPHA_SAME = 120000000000000000; uint256 constant REVERT_U = 0; uint256 constant DIR_IMPACT_MULT = 4; uint256 constant GATE_SIGMA_MULT = 7200000000000000000; uint256 constant MIN_GATE = 30000000000000000; uint256 constant TRADE_TOX_BOOST = 3500 * BPS; uint256 constant STALE_ATTRACT_FRAC = 1050000000000000000; uint256 constant RET_CAP = WAD / 35; uint256 constant TOX_CAP = WAD / 5; uint256 constant TRADE_RATIO_CAP = WAD / 5; uint256 constant LAMBDA_CAP = 1820000000000000000; uint256 constant STEP_COUNT_CAP = 64; uint256 constant BASE_FEE = 4 * BPS; uint256 constant SIGMA_COEF = 200000000000000000; uint256 constant LAMBDA_COEF = 12 * BPS; uint256 constant FLOW_SIZE_COEF = 4842 * BPS; uint256 constant TOX_COEF = 300 * BPS; uint256 constant TOX_QUAD_COEF = 15000 * BPS; uint256 constant ACT_COEF = 91843 * BPS; uint256 constant DIR_COEF = 20 * BPS; uint256 constant DIR_TOX_COEF = 100 * BPS; uint256 constant STALE_DIR_COEF = 6850 * BPS; uint256 constant SIGMA_TOX_COEF = 500 * BPS; uint256 constant TAIL_KNEE = 600 * BPS; uint256 constant TAIL_SLOPE_PROTECT = 930000000000000000; uint256 constant TAIL_SLOPE_ATTRACT = 850000000000000000; function afterInitialize(uint256 initialX, uint256 initialY) external override returns (uint256, uint256) { slots[0] = BASE_FEE; slots[1] = BASE_FEE; slots[2] = 0; slots[3] = WAD; slots[4] = 0; slots[5] = initialX > 0 ? wdiv(initialY, initialX) : 100 * WAD; slots[6] = 950000000000000; slots[7] = 800000000000000000; slots[8] = 2000000000000000; slots[9] = 0; slots[10] = 0; slots[11] = 0; slots[12] = 0; return (BASE_FEE, BASE_FEE); } function afterSwap(TradeInfo calldata trade) external override returns (uint256, uint256) { uint256 prevBidFee = slots[0]; uint256 prevAskFee = slots[1]; uint256 lastTs = slots[2]; uint256 dirState = slots[3]; uint256 actEma = slots[4]; uint256 pHat = slots[5]; uint256 sigmaHat = slots[6]; uint256 lambdaHat = slots[7]; uint256 sizeHat = slots[8]; uint256 toxEma = slots[9]; uint256 stepTradeCount = slots[10]; bool isNewStep = trade.timestamp > lastTs; if (isNewStep) { uint256 elapsedRaw = trade.timestamp - lastTs; uint256 elapsed = elapsedRaw > ELAPSED_CAP ? ELAPSED_CAP : elapsedRaw; dirState = _decayCentered(dirState, DIR_DECAY, elapsed); actEma = wmul(actEma, _powWad(ACT_DECAY, elapsed)); sizeHat = wmul(sizeHat, _powWad(SIZE_DECAY, elapsed)); toxEma = wmul(toxEma, _powWad(TOX_DECAY, elapsed)); if (stepTradeCount > 0 && elapsedRaw > 0) { uint256 lambdaInst = (stepTradeCount * WAD) / elapsedRaw; if (lambdaInst > LAMBDA_CAP) lambdaInst = LAMBDA_CAP; lambdaHat = wmul(lambdaHat, LAMBDA_DECAY) + wmul(lambdaInst, WAD - LAMBDA_DECAY); } stepTradeCount = 0; } bool firstInStep = stepTradeCount == 0; uint256 spot = trade.reserveX > 0 ? wdiv(trade.reserveY, trade.reserveX) : pHat; if (pHat == 0) pHat = spot; uint256 feeUsed = trade.isBuy ? prevBidFee : prevAskFee; uint256 gamma = feeUsed < WAD ? WAD - feeUsed : 0; uint256 pImplied; if (gamma == 0) { pImplied = spot; } else { pImplied = trade.isBuy ? wmul(spot, gamma) : wdiv(spot, gamma); } { uint256 ret = pHat > 0 ? wdiv(absDiff(pImplied, pHat), pHat) : 0; uint256 alpha = firstInStep ? PHAT_ALPHA : PHAT_ALPHA_RETAIL; uint256 prePhat = pHat; bool secondPrint = !firstInStep && stepTradeCount == 1; bool sameDir = false; bool oppDir = false; if (secondPrint) { bool up2 = pImplied >= pHat; uint256 d1 = slots[12]; if (d1 != 0) { sameDir = (d1 == 1) == up2; oppDir = !sameDir; } if (sameDir) alpha = PHAT_ALPHA_SAME; } if (firstInStep) { slots[11] = pHat; slots[12] = pImplied >= pHat ? 1 : 2; } uint256 adaptiveGate = wmul(sigmaHat, GATE_SIGMA_MULT); if (adaptiveGate < MIN_GATE) adaptiveGate = MIN_GATE; if (ret <= adaptiveGate) { pHat = wmul(pHat, WAD - alpha) + wmul(pImplied, alpha); } if (oppDir && REVERT_U > 0) { uint256 pre = slots[11]; if (pre > pHat) pHat = pHat + wmul(pre - pHat, REVERT_U); else pHat = pHat - wmul(pHat - pre, REVERT_U); } if (firstInStep) { if (ret > RET_CAP) ret = RET_CAP; sigmaHat = wmul(sigmaHat, SIGMA_DECAY) + wmul(ret, WAD - SIGMA_DECAY); } } // Interior retail routing equalizes fee-adjusted marginal prices. // Subsequent trades in a step cannot be the initial arbitrage trade. if (!firstInStep && feeUsed > 30 * BPS && gamma > 0) { uint256 normalSpot = trade.isBuy ? wdiv(wmul(spot, gamma), WAD - 30 * BPS) : wdiv(wmul(spot, WAD - 30 * BPS), gamma); pHat = wmul(pHat, 90 * WAD / 100) + wmul(normalSpot, 10 * WAD / 100); } uint256 tradeRatio = trade.reserveY > 0 ? wdiv(trade.amountY, trade.reserveY) : 0; if (tradeRatio > TRADE_RATIO_CAP) tradeRatio = TRADE_RATIO_CAP; if (tradeRatio > SIGNAL_THRESHOLD) { uint256 push = tradeRatio * DIR_IMPACT_MULT; if (push > WAD / 4) push = WAD / 4; if (trade.isBuy) { dirState = dirState + push; if (dirState > 2 * WAD) dirState = 2 * WAD; } else { dirState = dirState > push ? dirState - push : 0; } actEma = wmul(actEma, ACT_BLEND_DECAY) + wmul(tradeRatio, WAD - ACT_BLEND_DECAY); sizeHat = wmul(sizeHat, SIZE_BLEND_DECAY) + wmul(tradeRatio, WAD - SIZE_BLEND_DECAY); if (sizeHat > WAD) sizeHat = WAD; } uint256 tox = pHat > 0 ? wdiv(absDiff(spot, pHat), pHat) : 0; if (tox > TOX_CAP) tox = TOX_CAP; toxEma = wmul(toxEma, TOX_BLEND_DECAY) + wmul(tox, WAD - TOX_BLEND_DECAY); uint256 toxSignal = toxEma; stepTradeCount = stepTradeCount + 1; if (stepTradeCount > STEP_COUNT_CAP) stepTradeCount = STEP_COUNT_CAP; uint256 flowSize = wmul(lambdaHat, sizeHat); uint256 flowFee = wmul(LAMBDA_COEF, lambdaHat) + wmul(FLOW_SIZE_COEF, flowSize) + wmul(ACT_COEF, actEma); flowFee = flowFee * (35 * BPS) / (20 * BPS + flowFee); uint256 fBase = BASE_FEE + wmul(SIGMA_COEF, sigmaHat) + flowFee; uint256 fMid = fBase + wmul(TOX_COEF, toxSignal) + wmul(TOX_QUAD_COEF, wmul(toxSignal, toxSignal)); fMid = fMid + wmul(SIGMA_TOX_COEF, wmul(sigmaHat, toxSignal)); uint256 dirDev; bool sellPressure; if (dirState >= WAD) { dirDev = dirState - WAD; sellPressure = true; } else { dirDev = WAD - dirState; sellPressure = false; } uint256 skew = wmul(DIR_COEF, dirDev) + wmul(DIR_TOX_COEF, wmul(dirDev, toxSignal)); uint256 bidFee; uint256 askFee; if (sellPressure) { bidFee = fMid + skew; askFee = fMid > skew ? fMid - skew : 0; } else { askFee = fMid + skew; bidFee = fMid > skew ? fMid - skew : 0; } { uint256 staleShift = wmul(STALE_DIR_COEF, toxSignal); uint256 attractShift = wmul(staleShift, STALE_ATTRACT_FRAC); if (spot >= pHat) { bidFee = bidFee + staleShift; askFee = askFee > attractShift ? askFee - attractShift : 0; } else { askFee = askFee + staleShift; bidFee = bidFee > attractShift ? bidFee - attractShift : 0; } } { bool tradeAligned = (trade.isBuy && spot >= pHat) || (!trade.isBuy && spot < pHat); if (tradeAligned) { uint256 tradeBoost = wmul(TRADE_TOX_BOOST, tradeRatio); if (trade.isBuy) { bidFee = bidFee + tradeBoost; } else { askFee = askFee + tradeBoost; } } } if (sellPressure) { bidFee = clampFee(_compressTailWithSlope(bidFee, TAIL_SLOPE_PROTECT)); askFee = clampFee(_compressTailWithSlope(askFee, TAIL_SLOPE_ATTRACT)); } else { askFee = clampFee(_compressTailWithSlope(askFee, TAIL_SLOPE_PROTECT)); bidFee = clampFee(_compressTailWithSlope(bidFee, TAIL_SLOPE_ATTRACT)); } uint256 leanCorrection = 150 * BPS / 1000; if (leanCorrection > bidFee) leanCorrection = bidFee; bidFee -= leanCorrection; askFee = clampFee(askFee + leanCorrection); slots[0] = bidFee; slots[1] = askFee; slots[2] = trade.timestamp; slots[3] = dirState; slots[4] = actEma; slots[5] = pHat; slots[6] = sigmaHat; slots[7] = lambdaHat; slots[8] = sizeHat; slots[9] = toxEma; slots[10] = stepTradeCount; return (bidFee, askFee); } function _compressTailWithSlope(uint256 fee, uint256 slope) internal pure returns (uint256) { if (fee <= TAIL_KNEE) return fee; return TAIL_KNEE + wmul(fee - TAIL_KNEE, slope); } function _powWad(uint256 factor, uint256 exp) internal pure returns (uint256 result) { result = WAD; while (exp > 0) { if (exp & 1 == 1) result = wmul(result, factor); factor = wmul(factor, factor); exp >>= 1; } } function _decayCentered(uint256 centered, uint256 decayFactor, uint256 elapsed) internal pure returns (uint256) { uint256 mul = _powWad(decayFactor, elapsed); if (centered >= WAD) { return WAD + wmul(centered - WAD, mul); } uint256 below = wmul(WAD - centered, mul); return below < WAD ? WAD - below : 0; } function getName() external pure override returns (string memory) { return "no_cubic"; } } ``` # sha256 `8663d7bc37ec94f4c175f297ef27ccf5e8381aa4d081d709c4376d8525845523` UTF-8 code block contents, including its final newline. Reconstructed NP2: `51f1c1a9d4999ee460eb8bbc66880da8074b173399a4f5d71f96671482e18ac0`. # tuning **This is a fee-shape experiment, not a recommended leaderboard replacement.** The requested shared-market paired difference and the separate Vanilla-opponent diagnostic measure different effects. The candidate loses against NP2 in the latter diagnostic; a large positive shared-market difference must not be read as a board-score improvement. All values below are local measurements using simulator commit `6b6921f8d463314aa7c96234e1be25a25245f704`; external sources establish the inputs and simulator semantics, not the experimental results. Baseline checks against Vanilla, seeds 0–999, 10,000 steps each: | Strategy | Measured mean submission edge | Requested check | |---|---:|---:| | CH | 525.525882178083 | 525.525882178 | | NP2 | 526.115572245986 | 526.1156 | Setup script ran once successfully; StarterStrategy and the delivered candidate passed `amm-match validate`. The simulator was not modified. Solidity compiler: 0.8.24, optimizer 200, viaIR, Paris EVM, as in its compiler service. Parameterization: original aggregate flow fee `F = LAMBDA_COEF*lambdaHat + FLOW_SIZE_COEF*(lambdaHat*sizeHat) + ACT_COEF*actEma`, with original WAD multiplication. Transform `T(F)=F*(K+P)/(K+F)` with K and P in bps. For K>0 it is increasing and concave, T(P)=P, T′(P)=K/(K+P). L is the per-side centering shift in bps, applied after tail compression. K=0 in the tables means no transform. Original constants are untouched. Clipping limits the shift to the available bid, preserving the mid except at the fee cap. Initial grid and refinement, each on exactly seeds 10000–10127. The same tuning seeds were reused across points, as permitted; no confirmation seed was inspected during tuning. | K, P, L (bps) | n | shared mean_diff | SE | candidate mean bid−ask (bps) | candidate mid slope /10k Y | |---|---:|---:|---:|---:|---:| | 40, 25, 0 | 128 | -4.383786 | 0.707352 | 0.799012 | 1.574244 | | 40, 25, 0.51 | 128 | -3.251230 | 0.722264 | -1.821149 | 1.563332 | | 0, 0, 0.51 | 128 | 0.831703 | 0.096320 | -1.717573 | 1.846933 | | 20, 15, 0 | 128 | 34.326615 | 1.386012 | 0.773693 | 1.156353 | | 20, 25, 0 | 128 | -13.546535 | 0.801651 | 0.752425 | 1.420230 | | 20, 35, 0 | 128 | -64.421149 | 0.863033 | 0.960603 | 1.699583 | | 40, 15, 0 | 128 | 28.449717 | 1.170052 | 0.798684 | 1.361730 | | 40, 35, 0 | 128 | -37.523131 | 0.630628 | 0.878671 | 1.789647 | | 80, 15, 0 | 128 | 21.411921 | 0.898395 | 0.790580 | 1.545822 | | 80, 25, 0 | 128 | 0.874169 | 0.592076 | 0.807833 | 1.690052 | | 80, 35, 0 | 128 | -19.696148 | 0.449856 | 0.780640 | 1.832447 | | 0, 0, 0.255 | 128 | 0.344505 | 0.069825 | -0.509074 | 1.848797 | | 0, 0, 1.02 | 128 | 1.573653 | 0.161306 | -4.137108 | 1.846896 | | 20, 15, 0.15 | 128 | 34.562652 | 1.386609 | 0.050140 | 1.156185 | | 20, 15, 0.2 | 128 | 34.634047 | 1.384372 | -0.207488 | 1.156299 | | 20, 15, 0.255 | 128 | 34.638554 | 1.383965 | -0.475565 | 1.157664 | | 0, 0, 0.15 | 128 | 0.145752 | 0.069900 | 0.011288 | 1.848414 | | 0, 0, 0.2 | 128 | 0.249423 | 0.074764 | -0.233139 | 1.849676 | | 10, 15, 0 | 128 | 38.437754 | 1.569910 | 0.757571 | 0.962779 | | 15, 15, 0 | 128 | 36.210755 | 1.457659 | 0.760278 | 1.069317 | | 20, 12, 0 | 128 | 45.939288 | 1.534503 | 0.792869 | 1.085177 | | 20, 18, 0 | 128 | 20.948299 | 1.218658 | 0.761570 | 1.225693 | Expanded ablations on seeds 12000–12511 (512 seeds each): | Variant | n | shared mean_diff | SE | mean bid−ask (bps) | mid slope /10k Y | |---|---:|---:|---:|---:|---:| | a: flatten only | 512 | 33.838648 | 0.660653 | 0.811828 | 1.071301 | | b: zero lean only | 512 | 0.272634 | 0.022263 | 0.067721 | 1.732352 | | both: delivered | 512 | 33.883402 | 0.649905 | 0.089721 | 1.071389 | Chosen: **K=20, P=15, L=0.15 bps**, to match the requested approximately 1.1 slope and near-zero mean lean. The selection is a mechanism test; neither the constrained fee shape nor the shared-market metric establishes leaderboard improvement. The 0.51-bps nominal half-gap shift overshot because subsequent state/routing feedback amplifies its effect. Additional diagnostic, all on tuning seeds 11000–11255: candidate and NP2 each separately face the same Vanilla contract, then their submission edges are subtracted per seed. This diagnostic follows the comparison described in the NP source artifact. It is **not** the requested confirmation pairing and has not been independently confirmed. | Variant (K,P,L bps) | n | Vanilla-opponent paired mean_diff | SE | |---|---:|---:|---:| | 20,15,0 | 256 | -2.552338 | 0.072579 | | 20,15,0.15 | 256 | -2.588483 | 0.073524 | | 0,0,0.2 | 256 | -0.047406 | 0.009981 | | 10,20,0.15 | 256 | -4.041931 | 0.081289 | | 20,20,0.15 | 256 | -2.620187 | 0.065815 | | 20,25,0.15 | 256 | -3.851032 | 0.083044 | | 40,25,0.15 | 256 | -1.970718 | 0.063123 | | 10,22,0.15 | 256 | -4.901036 | 0.086406 | | 10,25,0.15 | 256 | -6.546778 | 0.103766 | # paired Single frozen-candidate confirmation, seeds **2490000–2491999**, never used for tuning or rerun. Literal requested pairing: `amm_sim_rs.run_batch(candidate_bytecode, np2_bytecode, configs, 8)`. Per-seed difference is `edges["submission"] - edges["normalizer"]` from the **same shared market**. ```json { "n": 2000, "mean_diff": 35.0176519435666, "se": 0.34638199246117773, "worst": { "seed": 2490846, "diff": 6.275863060173492, "a": 326.58103258600846, "b": 320.30516952583497 } } ``` `worst` is the smallest individual seed difference; `a` and `b` are candidate and NP2 edges respectively. SE is sample standard deviation (ddof=1) divided by sqrt(n). This is not a separate-Vanilla paired board-score estimate. In particular, the inherited interior-routing inference uses a 30-bps normalizer assumption (S1/S2); a dynamic NP2 opponent changes that context. # fee_stats Fees are bps. Each run averages its 10,000 step-end quotes, including steps with no trade; the table averages these per-run means. Slope is univariate OLS with an intercept across runs: run-average mid fee regressed on that pool's cumulative retail Y volume divided by 10,000. “Runs bid>40” means **run-average** bid>40 bps, not any instantaneous quote. Retail volume is endogenous and differs between pools. | Sample / strategy | n | mean bid | mean ask | bid−ask | mid slope /10k retail Y | runs bid>40 | |---|---:|---:|---:|---:|---:|---:| | confirmation / NP2 | 2000 | 36.674369 | 35.488598 | 1.185771 | 1.862536 | 286 | | confirmation / both | 2000 | 33.958572 | 34.024576 | -0.066004 | 1.068226 | 8 | | 0–999 vs Vanilla / NP2 | 1000 | 36.478405 | 35.394115 | 1.084290 | 1.687607 | 103 | | 11000–11255 vs Vanilla / NP2 | 256 | 36.314540 | 35.044120 | 1.270420 | 1.701156 | 25 | | 11000–11255 vs Vanilla / both | 256 | 33.802941 | 33.656497 | 0.146444 | 1.043621 | 1 | The supplied NP2 slope/lean figures were not exact calibration targets for this seed sample or opponent: the reproduced 0–999 score is exact, while the measured slope/lean are those shown above. No rank-1 contract or current leaderboard measurement was used. ## Reproduction and evidence NP2 was reconstructed from S1 by inserting S2's substring beginning `// Interior retail routing` and ending immediately before `uint256 tradeRatio =` before S1's corresponding `uint256 tradeRatio =`. The resulting hash and baseline score both match the brief. CH comes from S8. S3–S7 define the simulation and comparator. Sources were fetched and their quoted passages checked against URL response text. For each seed s, use `rng = numpy.random.default_rng(s)`; draw size `uniform(19,21)`, arrival rate `uniform(0.6,1.0)`, then sigma `uniform(0.000882,0.001008)`, in that order. Config: n_steps=10000, initial_price=100, initial_x=100, initial_y=10000, gbm_mu=0, gbm_dt=1, retail_size_sigma=1.2, retail_buy_prob=0.5, and seed=s. The engine uses price seed s and retail seed s+1. Compile through `SolidityCompiler`; run batches of eight with eight workers. Edge, `average_fees`, and `retail_volume_y` are read directly from each Rust result. Baseline score is the arithmetic mean of submission edge, **not** the submission-minus-Vanilla difference. - [S1](https://api.imd.fun/artifacts/528a6da78583c6142644a552c86670f9d25c20e39e92f4de44deae05ddc8cecd): TWO source artifact. - [S2](https://api.imd.fun/artifacts/da313379a2e69e9286668865e06b18dfcae25732c82b934b0fd9c2ef0d8c2ff5): NP source artifact. - [S3](https://raw.githubusercontent.com/benedictbrady/amm-challenge/6b6921f8d463314aa7c96234e1be25a25245f704/amm_competition/competition/match.py): per-seed draw construction. - [S4](https://raw.githubusercontent.com/benedictbrady/amm-challenge/6b6921f8d463314aa7c96234e1be25a25245f704/amm_competition/competition/config.py): canonical simulation configuration. - [S5](https://raw.githubusercontent.com/benedictbrady/amm-challenge/6b6921f8d463314aa7c96234e1be25a25245f704/amm_sim_rs/src/simulation/engine.rs): price/retail seeds and step-end averaging. - [S6](https://raw.githubusercontent.com/benedictbrady/amm-challenge/6b6921f8d463314aa7c96234e1be25a25245f704/amm_sim_rs/src/lib.rs): Rust batch API. - [S7](https://raw.githubusercontent.com/benedictbrady/amm-challenge/6b6921f8d463314aa7c96234e1be25a25245f704/contracts/src/VanillaStrategy.sol): 30-bps Vanilla comparator. - [S8](https://raw.githubusercontent.com/ChengHua926/amm-challenge/d34a5199c59a4c44d5336a25f1436b4ae1f21104/contracts/src/Strategy.sol): CH source. - [S9](https://raw.githubusercontent.com/benedictbrady/amm-challenge/6b6921f8d463314aa7c96234e1be25a25245f704/amm_competition/evm/compiler.py): Solidity compiler settings. # blocked ```json null ``` No unresolved setup, compilation, validation, or simulation failure. No confirmation seed was reused. # next Keep NP2 for leaderboard use. The requested fee shape is achievable but loses in the Vanilla diagnostic. Evaluate future mechanisms with separate common-seed Vanilla runs; shared-market advantage alone is insufficient evidence of board transfer.