Overview
These models are for manual observations and offline analysis. They must not be connected to unauthorized scraping, APIs, bots, or automatic purchases. Their lookup values are calibration priors from the reviewed material—not discovered OSM probabilities.
16.1 Transfer efficiency model
Simple gross-efficiency score
Gross TE = (target resale − acquisition cost)
/ (acquisition cost × adjusted holding days)
Where:
Target resale = min(live in-game maximum, player value × target multiple)
Adjusted holding days = baseline holding days / (age factor × event factor)
This score ranks capital velocity but assumes the target sale occurs. It is therefore useful for screening, not sufficient for a final purchase.
Provisional lookup table
| Rating | Target multiple | Baseline hold |
|---|---|---|
| ≤80 | 2.45×; 2.50× if age ≤21 | 1.5 days |
| 81–90 | 2.25× | 2.0 days |
| 91–99 | 2.10× | 3.5 days |
| 100–109 | 1.90× | 6.0 days |
| 110+ | 1.70× | 6.0+ days; calibrate separately |
| Age | Provisional liquidity factor |
|---|---|
| ≤21 | 1.15 |
| 22–24 | 1.10 |
| 25–29 | 1.00 |
| 30+ | 0.90 |
| Event | Provisional factor |
|---|---|
| Transfer Madness | 1.40 |
| No relevant sale event | 1.00 |
The event factor must be replaced with observed results. An event confirms improved conditions, not a universal 40% reduction in hold time.
Risk-adjusted score
The preferred model includes failed-sale downside:
Expected profit = p × target resale
+ (1 − p) × fallback value
− acquisition cost
− Boss Coin cost
Risk-adjusted TE = Expected profit
/ (acquisition cost × adjusted holding days)
Use your own 48/72-hour sale rate for p. Until enough data exists, use a conservative range and calculate pessimistic, base, and optimistic scenarios.
Provisional decision bands
| Gross TE | Screening label | Decision rule |
|---|---|---|
| ≥0.25 | Exceptional candidate | Never “auto-buy”; verify cash, floor, live cap, and risk-adjusted EV |
| 0.15–0.249 | Strong candidate | Buy when squad and list capacity are safe |
| 0.08–0.149 | Acceptable | Use only with spare capacity or real sporting utility |
| <0.08 | Reject as pure flip | Capital/slot time is probably better used elsewhere |
These thresholds are unvalidated heuristics. A negative risk-adjusted TE always overrides a positive gross score.
Offline Python reference
class OSMTradeEvaluator:
@staticmethod
def resale_multiple(ovr: int, age: int) -> float:
if ovr <= 80:
return 2.50 if age <= 21 else 2.45
if ovr <= 90:
return 2.25
if ovr <= 99:
return 2.10
if ovr <= 109:
return 1.90
return 1.70
@staticmethod
def baseline_hold_days(ovr: int) -> float:
if ovr <= 80:
return 1.5
if ovr <= 90:
return 2.0
if ovr <= 99:
return 3.5
return 6.0
@staticmethod
def age_factor(age: int) -> float:
if age <= 21:
return 1.15
if age <= 24:
return 1.10
if age <= 29:
return 1.00
return 0.90
@classmethod
def evaluate(
cls,
ovr: int,
age: int,
player_value: float,
buy_price: float,
live_max_price: float,
sale_probability: float,
fallback_value: float,
boss_coin_cost: float = 0.0,
transfer_madness: bool = False,
) -> dict:
if buy_price <= 0 or player_value < 0 or live_max_price < 0:
raise ValueError("Prices must be valid and buy_price must be positive")
if not 0.0 <= sale_probability <= 1.0:
raise ValueError("sale_probability must be between 0 and 1")
target = min(
live_max_price,
player_value * cls.resale_multiple(ovr, age),
)
event_factor = 1.40 if transfer_madness else 1.00
hold_days = cls.baseline_hold_days(ovr) / (
cls.age_factor(age) * event_factor
)
gross_profit = target - buy_price
gross_te = gross_profit / (buy_price * hold_days)
expected_proceeds = (
sale_probability * target
+ (1.0 - sale_probability) * fallback_value
)
expected_profit = expected_proceeds - buy_price - boss_coin_cost
risk_te = expected_profit / (buy_price * hold_days)
if expected_profit <= 0 or risk_te < 0.08:
action = "PASS"
elif risk_te < 0.15:
action = "ACCEPTABLE"
elif risk_te < 0.25:
action = "STRONG CANDIDATE"
else:
action = "EXCEPTIONAL CANDIDATE — VERIFY MANUALLY"
return {
"target_resale": round(target, 2),
"adjusted_hold_days": round(hold_days, 2),
"gross_profit": round(gross_profit, 2),
"expected_profit": round(expected_profit, 2),
"gross_te": round(gross_te, 4),
"risk_adjusted_te": round(risk_te, 4),
"action": action,
}
16.2 Tactical experiment analyzer
The following offline model calculates basic KPIs and applies sample-size confidence caps. Its confidence score measures the strength of your evidence, not match win probability.
class OSMTacticalAnalyzer:
def __init__(self, tactic_name: str):
self.tactic_name = tactic_name
self.matches = []
def log_match(
self,
gf: int,
ga: int,
shots_for: int,
shots_against: int,
sot_for: int,
sot_against: int,
possession_for: float,
distorted: bool = False,
) -> None:
self.matches.append({
"gf": gf,
"ga": ga,
"shots_for": shots_for,
"shots_against": shots_against,
"sot_for": sot_for,
"sot_against": sot_against,
"possession_for": possession_for,
"distorted": distorted,
})
def valid_matches(self) -> list[dict]:
return [m for m in self.matches if not m["distorted"]]
def kpis(self) -> dict:
matches = self.valid_matches()
n = len(matches)
if n == 0:
return {"status": "No valid matches recorded"}
wins = sum(m["gf"] > m["ga"] for m in matches)
draws = sum(m["gf"] == m["ga"] for m in matches)
average = lambda key: sum(m[key] for m in matches) / n
return {
"sample_size": n,
"points_per_match": round((3 * wins + draws) / n, 2),
"goal_difference": round(
average("gf") - average("ga"), 2
),
"shot_difference": round(
average("shots_for") - average("shots_against"), 2
),
"sot_difference": round(
average("sot_for") - average("sot_against"), 2
),
"average_possession": round(average("possession_for"), 1),
}
def evidence_confidence(self, external_evidence: float = 15.0) -> float:
matches = self.valid_matches()
n = len(matches)
if n == 0:
return 0.0
score = max(0.0, min(external_evidence, 20.0))
for m in matches:
process_edge = (
m["shots_for"] > m["shots_against"]
and m["sot_for"] >= m["sot_against"]
)
if m["gf"] > m["ga"] and process_edge:
score += 3.0
elif m["gf"] < m["ga"] and process_edge:
score += 0.5
elif m["gf"] > m["ga"] and not process_edge:
score += 0.0
elif m["gf"] < m["ga"] and not process_edge:
score -= 5.0
if n == 1:
cap = 25.0
elif n < 5:
cap = 40.0
elif n < 10:
cap = 55.0
elif n < 20:
cap = 70.0
elif n <= 30:
cap = 85.0
else:
cap = 95.0
return round(min(cap, max(0.0, score)), 1)
16.3 AI diagnostic rules
Use suggested adjustments as the next variable to test, not proof of the cause:
| Repeated pattern | Working hypothesis | Next controlled test |
|---|---|---|
| High possession, few shots | Sterile circulation | Raise Tempo by 5 or alter one midfield instruction—not both |
| Many shots, low SOT | Weak shot selection/finishing | Compare lower Style or a non-SOS plan; review striker quality |
| Few shots conceded, many goals | Finishing/GK variance or unusually clear chances | Retest unchanged before restructuring |
| Low possession, high shot/SOT output | Counter/direct transition is functioning | Preserve setup and expand the sample |
| Repeated high shots conceded | Block is structurally too open | Lower Pressing or Style, or add one midfielder/defender |
Every automated output should return:
- the inputs used;
- whether the match/trade was distorted;
- the calculation and threshold;
- the evidence confidence;
- the recommended action;
- the assumptions still requiring manual validation.