zengm-games/zengm

Make Free Agency and Draft Values more team specific in hockey+football

nicidob opened this issue · 6 comments

Just opening an issue for this contribution.

Currently teams only use player value to determine who to draft or who to sign. Instead, I think they should look at some blend of "individual value" and "team value". In positionless BBGM, those are the same thing, but with position groups, they're not.

IMO it should be something like value = team_improvement + a*player_value for some constant a (it was 0.025 in my experiments below).

Following the example in https://github.com/nicidob/fbgm/blob/master/how_to_select.ipynb

The key ingredients are

  1. a team overall formula where there are smooth functions (exponentials?) for each position group, so that QB2 is some non-zero contribution. You can simply fit these to your current values (as I did for your old ones), but you can also use some non-linear optimization to find them directly in regression. This enables a non-zero team improvement for every single player & team.
  2. a variant of team-ovr which uses "value" ( age-adjusted pot+ovr blend) instead of ovr to judge change in team value. You could even just use potential instead. This lets young players' propensity for growth to be factored in, as well as older players decline.

Thanks, this is probably the next big thing I want to add. The idea is pretty simple...

Getting it kind of working....

I'm finding that the blended team+player value works well for draft (sometimes take "best prospect available" even if it's a bad fit) but for free agency just going straight team value works better. Some of that could be because FBGM has the draft first, unlike the NFL, so in free agency it's more "fill in the holes" while in the draft there's more flexibility.

And of course you still wind up with poorly constructed teams, because I haven't gotten to any of these scenarios yet (and not sure I will in the initial version):

  • re-signing
  • trades
  • player development leading to an organic log jam, so a player should be traded away for a more needed player

Much more testing needed...

https://github.com/zengm-games/zengm/tree/team-value-draft-fa if you're curious...

I did this test in the worker console on a 100 year sim:

var backup = 0;
var fa = 0;
for (let season = 2021; season < 2122; season++) {
    console.log(season);
	const players = await bbgm.idb.getCopies.playersPlus((await bbgm.idb.getCopies.players({
        activeSeason: season,
    })), {
		attrs: ["pid", "draft"],
		ratings: ["ovr", "pos", "season"],
		stats: ["tid", "season"],
		season,
		showNoStats: true,
	});

    const qbs = players.filter(p => p.ratings.pos === "QB");

    qbs.sort((a, b) => b.ratings.ovr - a.ratings.ovr);

    const top10 = qbs.slice(0, 10);
    const tids = new Set();
    for (const p of top10) {
        const tid = p.stats.tid;
        if (tid < 0) {
            if (season > p.draft.year) {
                fa += 1;
            }
            continue;
        } else if (tids.has(tid)) {
            backup += 1;
        } else {
            tids.add(tid);
        }
    }
}

console.log(backup, fa);

That counts the total number of seasons by top 10 QBs spent not starting (either on the bench or FA).

master: 58 years on bench, 19 years in FA

team-value-draft-fa: 15 years on the bench, 3 years in FA

Promising start...

wow i missed your updates here. This is exciting! I'm terrified by something taken to the 40th power (can u just take the log and multiply by 40 if it's positive? thx).

It's also not clear how you're handling QB2 or whatever? I didn't see exponentials. Although something like, where you just divide by a half for every position after the last one.

if idx < len(weights): weight = weights[idx]
else: weight = weights[-1]/(2**(idx-len(weights))

https://github.com/zengm-games/zengm/blob/team-value-draft-fa/src/worker/core/team/ovrByPosFactory.ts#L74-L87 is basically that... I pretty arbitrarily set the coefficients there, not sure how optimal it is, I should probably play around with it some more. Also it uses the "recommended amount of players by position" to bump it up even more when you cross that threshold, otherwise it was difficult to tune the balance between signing the correct number of backups at each position.

https://github.com/zengm-games/zengm/blob/team-value-draft-fa/src/worker/core/freeAgents/getBest.ts - this function also got a lot simpler, I got rid of all the hacky "don't forget to sign a kicker" code. The seenMinContractAtPos part is essential for performance too... it stops computing team value for a position once it hits a player with a minimum contract, because if it doesn't want to sign that player, it definitely won't want to sign a worse one.

yeah I hadn't really reasoned through all the FA logic. As the name of my file (how to select) is called, I really started with it as a drafting idea and did the lazy FA extension. neat!

If you're ever worried about parameter tuning. Just uhh... make it a free variable, defined a criteria and sample random numbers to see what works best. You already have an okay criteria with the "top 10 QBs on bench or FA".

Random search is tough to beat. In less than a hundred trials, even Google's fancy initial hyperparameter optimizer didn't beat random sampling (figure 6 here), and there's this brutal set of results, where doing random search with 2x as many iterations as some fancy optimizer is pretty much identical in final performance.