Can models learn hidden pricing rules in a market economy?
Abstract. I built a one-shot trade market that showed GRPO is able to teach Nemotron 3 Nano 4B hidden pricing rules in a market economy, improving from 21% to 93% of captured expected value after 1,000 steps.
I have always loved baseball, and one of the aspects that intrigues me the most is how players are valued, especially in trades. Just this past year my beloved Phillies traded away a hot pitching prospect flying through the minors, Ramon Marquez, in exchange for a rental of Luis Arraez.
This was a deadline trade. Usually, trading a controllable young pitcher who looks destined to be a high leverage reliever in the coming years for a few months of a contact hitter wouldn’t be thinkable. But for the Phillies, who saw this year as an opportunity to make a run in the postseason, their valuation of assets changed. Having Arraez now became more important than Marquez years down the line.
Now, in the Phillies’ case, the trade didn’t work out. Arraez ultimately wasn’t on the roster due to injury for the club’s 3 game loss in the NL Wild Card round. But the trade itself wasn’t illogical to make. GMs make similar moves all the time. There is a lot more involved in the decision to make a move than just a player’s value on paper.
As I’ve been experimenting with LLMs, I wanted to understand how well models understand these hidden pricing rules when they’re in the shoes of a GM, and whether or not we can teach a model how these rules work with PEFT.
Experiment design
To get started, I needed to understand how well a model understands these dynamics unmodified. For our experiment, I decided to use Nemotron 3 Nano 4B [1]. I’ve been experimenting with the NeMo toolkit lately, and this model is efficient to train with NeMo Gym [2] for our later RL experiments.
I wanted to design an experiment that would be easy to measure, and ensure the model wouldn’t be able to learn the hidden value of the player from their statline alone.
The design I ultimately decided on was a simulated trade deadline. The model makes one single take-it-or-leave-it offer for a player at the trade deadline, or takes no action.
Here’s an example of what the agent sees for a single turn:
Target: left-handed reliever, 2 extra year(s) of club control.
Public consensus value: 17.1
Your internal value (fit + need): 32.0
Seller: a fringe contender club.
Rival bidders known to be in on this player: 1
Days until the deadline: 9
Make your move.Listing 1. A player card as the model sees it. The seller's asking price is never shown.
This card is supposed to represent what a general manager would know going into a trade deadline. The model has a single offer they can make for a player, or they can choose to walk away and take no action. In the market simulation, the selling club has a private asking price, r. If the model’s offer called p is at least r the deal is executed. The model’s club gains V - p where V represents what the player is worth to that club internally.
When presented with the offer, the model can respond with one of two actions:
{ "action": "offer", "amount": X }
{ "action": "walk" }Listing 2. The model's two possible replies: an offer at a price, or a walk.
How is the hidden value in the market quantified?
Every seller in the market has a hidden asking price for a player. This is drawn from a normal distribution, the center of it being the public value of the player noted on the card. The distribution is scaled by a constant for values on the player card according to these terms:
| signal | effect on the seller’s ask |
|---|---|
| Rebuilding / middling / fringe-contender seller | ×0.75 / ×1.00 / ×1.30 |
| Each rival bidder | +10% |
| Each day before the deadline | +1.5% (sellers soften as the deadline gets close) |
| Each extra year of club control | +6% |
| Spread | standard deviation = 12% of the center |
Table 1. The hidden pricing rules. The first four rows move the center of the seller's asking-price distribution and the last sets its width. The model is never told any of them.
How is it scored?
In this experiment, the grader knows the true value of the player and the seller’s distribution. The reward it calculates given a model’s offer is the expected value of the offer, represented below.11 We’re using expected value here because our simulation is a one-shot trade market. Most trades are not likely to happen on their face, so using an outcomes based approach would not provide signal well simply due to the underlying market dynamics. More on the limitations of this approach later.
It will then divide this by the best possible expected value on the player offer which it finds with a 2,000 point grid search.
This means that the value of the reward would be:
- 1.0 for the best possible offer
- 0 for walking away from a deal worth making
- [-1,0) overpaying for a player, with a floor of -1
- -1 if the model outputs a value that cannot be parsed
The below chart plots the seller’s distribution, illustrating where the value lies in the true deadline market.
one trade: the seller's hidden asking price, in $M
- harvested: asks the best offer clears (31%)
- left on the table: asks under V it misses (34%)
- offers, labelled with their reward
data
| offer | seller accepts | reward |
|---|---|---|
| $17.1M | 0% | 0.00 |
| $26M | 10% | 0.63 |
| $28.8M | 31% | 1.00 |
| $32M | 64% | 0.00 |
| $34M | 82% | -1.00 |
This reward is modeled simply in our market module. This block is the entirety of the reward function.
def reward(task: Task, completion_text: str) -> float:
valid, offer = parse_action(completion_text)
if not valid:
return -1.0
_, best_ev = optimal_offer(task) # 2,000-point grid search over (0, V]
floor = NO_DEAL_EPS * task.public_value # 1% of the public value
ev = 0.0 if offer is None else expected_surplus(task, offer)
if best_ev <= floor:
# No deal worth making: walking (or a lowball) is correct
return 1.0 if ev >= 0 else max(-1.0, 1.0 + ev / floor)
return max(-1.0, min(1.0, ev / best_ev))Listing 3. The reward function from the environment's market module.
How well does the base model perform?
I ran the Nemotron 3 Nano 4B model through two splits, one was a held-out test of players with 0-3 active bidders per player. The other was a shifted market split where 4 or 5 rival bidders were competing for players. Each ran 300 trade scenarios with 8 samples per trade, for a total of 2,400 runs per split.
The way we’ll measure how well the model performs is by calculating how much of the expected value is captured by the model’s offers. This works since we know the hidden value in advance.
For the initial baseline run, I measured how much of the expected value the model captured for the test market (with 0-3 competitors) as well as the shifted market (with 4-5 competitors).
For the test market, the base model captured 21.20% of the potential EV for its scenarios. This fell to 17.62% for the shifted market. The model produced valid actions 100% of the time, but leaves a tremendous amount of economic value on the table. This is a clear indication that the base model doesn’t intrinsically understand the pricing rules in the market in this environment. In other words, there is room to learn the market’s hidden pricing structure.
whole market: best possible expected surplus over 300 trades, in $M
- captured by the untrained model
- left on the table
data
| captured by the untrained model | left on the table | total | |
|---|---|---|---|
| held-out trades, 0–3 rivals | $147.9M | $549.6M | $697.5M |
| shifted market, 4–5 rivals | $57.3M | $267.9M | $325.2M |
How can we improve the model’s performance?
One of the first challenges any developer building with AI will experience is a scenario where a model doesn’t reliably output the answer you’d expect when it’s put in a certain environment.
The first line approach for this scenario is to introduce RAG (Retrieval-Augmented Generation) [3]. This is where the application around the model will locate information relevant to the turn that the model is handling, and add that relevant information to the request before it’s sent to the model. It usually uses semantic search over some corpus of information stored in a vector database, but RAG doesn’t have to refer to semantic search, it really can be any kind of context we pull into a request before it’s sent off to the inference provider.
This approach works really well when we have domain-specific data that the app needs to rely on. Imagine a customer support bot that a company’s engineering team might build to increase customer satisfaction. This is a great case for a RAG implementation, because base models aren’t going to have the company’s knowledge base of information in their pre-training data. The bot will use RAG to pull from the company’s knowledge base as needed. This approach also allows developers to have more hands on control over what their model is responding with, since data in an index that RAG relies on can be updated on demand.
In our case, though, RAG isn’t ideal. Why? We want to influence how the model actually behaves. When the experiment is run again, the model still won’t have information about the seller’s hidden price. We want it to understand the dynamics of the market intrinsically.
Enter RL
So, how in the world do we actually change how a model behaves? This is where RL comes into play. For a task like this, a modern technique called Parameter-Efficient Fine-Tuning (PEFT) is a great choice for attempting to subtly influence the behavior of a model without retraining the whole thing.
In our case, I am going to experiment with an RL method called Group Relative Policy Optimization (GRPO) [4] to create a Low Rank Adaptation (LoRA) [5] that modifies only a small subset of the weights of the base model. LoRA is tremendous for experimenting with RL with limited resources. Tools like Unsloth [6] allow training adaptations sometimes on just single consumer GPUs, depending on the base model.
Here though, I decided to use NVIDIA’s NeMo stack for our RL experiment since I entered this project with the goal of getting familiar with NVIDIA’s tooling.
There are two main components to get familiar with when planning on using this stack: NeMo Gym and NeMo RL [7].
How GRPO works in this market
Time to start training. For this GRPO experiment, the model will run against each player card 8 times. Each is scored according to the reward logic above. Runs with higher reward are reinforced.
In other words, for any one given example, we reinforce rollouts where the reward is higher than the average reward for the 8 sample group.
I ran this experiment on Modal. You can see the Modal app entrypoint here in the reference code.
| setting | value |
|---|---|
| Base model | Nemotron 3 Nano 4B (BF16) |
| Adapter | LoRA, rank 32, alpha 64, on the attention, MLP and Mamba input projections |
| Objective | GRPO with Dr. GRPO normalization [8], no KL penalty |
| Batch | 8 cards × 8 offers = 64 completions per step |
| Learning rate | 5 × 10⁻⁶, 10-step warmup, then cosine decay |
| Steps | 1,000 |
| Sampling | temperature 1.0, up to 128 tokens |
Table 2. Training setup for the NeMo RL run, on one H100 on Modal.
What happened in training
During training the reward jumped quickly and then started to level off. It reached 0.40 over the first 25 steps, and reached 0.92 by step 400. Returns start to diminish around this point, with reward climbing to about 0.95 after step 700.
The below chart illustrates how the reward climbs as the run progresses, through 1,000 steps.
training reward, mean over each step's 64 completions
data
| step | training reward |
|---|---|
| 25 | 0.40 |
| 50 | 0.54 |
| 75 | 0.76 |
| 100 | 0.78 |
| 125 | 0.81 |
| 150 | 0.82 |
| 175 | 0.84 |
| 200 | 0.84 |
| 225 | 0.86 |
| 250 | 0.87 |
| 275 | 0.80 |
| 300 | 0.88 |
| 325 | 0.90 |
| 350 | 0.91 |
| 375 | 0.92 |
| 400 | 0.92 |
| 425 | 0.93 |
| 450 | 0.93 |
| 475 | 0.93 |
| 500 | 0.95 |
| 525 | 0.94 |
| 550 | 0.94 |
| 575 | 0.95 |
| 600 | 0.95 |
| 625 | 0.95 |
| 650 | 0.95 |
| 675 | 0.95 |
| 700 | 0.95 |
| 725 | 0.95 |
| 750 | 0.95 |
| 775 | 0.96 |
| 800 | 0.95 |
| 825 | 0.96 |
| 850 | 0.95 |
| 875 | 0.95 |
| 900 | 0.96 |
| 925 | 0.95 |
| 950 | 0.95 |
| 975 | 0.96 |
| 1,000 | 0.95 |
What did the model learn?
So, our reward seems to have risen nicely over 1,000 steps, but how does that actually translate to how the model behaves?
Let’s look at that player card from the original example again.
Target: left-handed reliever, 2 extra year(s) of club control.
Public consensus value: 17.1
Your internal value (fit + need): 32.0
Seller: a fringe contender club.
Rival bidders known to be in on this player: 1
Days until the deadline: 9
Make your move.Listing 4. The card from Listing 1 again. The grader can see what the model can't: the seller's ask is centred on $30.7M, which makes the best offer $28.8M.
If we take a look at what the model offered before the GRPO run, the offers are spread between $17.1M and $32M. Three of them are at the player’s public value of $17.1M, and only one offer (at $24.4M) earned more than 7% of the best surplus.
After training, all eight land between $29.0M and $29.5M, earning 96% or more of the best expected surplus.
before training: mean 6% of best
- share of best
- untrained offers
data
| offer | share of best | untrained offers |
|---|---|---|
| $15M | 0% | |
| $15.5M | 0% | |
| $16M | 0% | |
| $16.5M | 0% | |
| $17M | 0% | |
| $17.1M | 0% | 3 |
| $17.5M | 0% | |
| $18M | 0% | |
| $18.5M | 1% | |
| $19M | 1% | |
| $19.5M | 2% | |
| $20M | 2% | 2 |
| $20.5M | 3% | |
| $21M | 5% | |
| $21.5M | 7% | 1 |
| $22M | 9% | |
| $22.5M | 13% | |
| $23M | 17% | |
| $23.5M | 22% | |
| $24M | 29% | |
| $24.4M | 34% | 1 |
| $24.5M | 36% | |
| $25M | 44% | |
| $25.5M | 53% | |
| $26M | 63% | |
| $26.5M | 72% | |
| $27M | 81% | |
| $27.5M | 89% | |
| $28M | 96% | |
| $28.5M | 99% | |
| $28.8M | 100% | |
| $29M | 100% | |
| $29.5M | 96% | |
| $30M | 87% | |
| $30.5M | 74% | |
| $31M | 55% | |
| $31.5M | 30% | |
| $32M | 0% | 1 |
after 1,000 steps: mean 97% of best
- share of best
- trained offers
data
| offer | share of best | trained offers |
|---|---|---|
| $15M | 0% | |
| $15.5M | 0% | |
| $16M | 0% | |
| $16.5M | 0% | |
| $17M | 0% | |
| $17.1M | 0% | |
| $17.5M | 0% | |
| $18M | 0% | |
| $18.5M | 1% | |
| $19M | 1% | |
| $19.5M | 2% | |
| $20M | 2% | |
| $20.5M | 3% | |
| $21M | 5% | |
| $21.5M | 7% | |
| $22M | 9% | |
| $22.5M | 13% | |
| $23M | 17% | |
| $23.5M | 22% | |
| $24M | 29% | |
| $24.4M | 34% | |
| $24.5M | 36% | |
| $25M | 44% | |
| $25.5M | 53% | |
| $26M | 63% | |
| $26.5M | 72% | |
| $27M | 81% | |
| $27.5M | 89% | |
| $28M | 96% | |
| $28.5M | 99% | |
| $28.8M | 100% | |
| $29M | 100% | 2 |
| $29.5M | 96% | 6 |
| $30M | 87% | |
| $30.5M | 74% | |
| $31M | 55% | |
| $31.5M | 30% | |
| $32M | 0% |
Results
If we rerun the whole sample on held out trades, the results show that the model captures 93% of the market’s expected value, versus 21% from before training.
whole market: best possible expected surplus over 300 trades, in $M
- captured
- left on the table
data
| captured | left on the table | total | midpoint rule | |
|---|---|---|---|---|
| held-out, untrained | $147.9M | $549.6M | $697.5M | |
| held-out, 1,000 steps | $648.5M | $49M | $697.5M | $525.2M |
| shifted, untrained | $57.3M | $267.9M | $325.2M | |
| shifted, 1,000 steps | $253.9M | $71.3M | $325.2M | $282.3M |
The shifted market
Interestingly, when the model was exposed to the a more competitive market that included 4-5 rival bidders for players, the trained model falls to 78% of expected value recovered.
If we use a simple midpoint rule that averages the model’s value and the public value of the player, it beats the trained model at 87%.
The pitfall here appears to be scenarios where rebuilding clubs are offering a player. The trained model seems to consistently underestimate the willingness of the extra bidders to drive the price up.
Limitations and Next Steps
One of the main limitations of this experiment is that we are using a simulated market. The reward is modeled on expected value here, not outcomes. In a real world market there are additional factors that go into player moves beyond simply the best one-shot offer.
The reward also seemed to plateau here during training.
To take this experiment further, I think my next steps would be to work on redesigning the reward so that it learns the hidden rule about increased bidders leading to higher prices.
References
[1] NVIDIA. NVIDIA-Nemotron-3-Nano-4B-BF16. Hugging Face. huggingface.co
[2] NVIDIA. NeMo Gym. GitHub, 2025. github.com
[3] P. Lewis et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. arXiv:2005.11401, 2020. arxiv.org
[4] Z. Shao et al. DeepSeekMath, Pushing the Limits of Mathematical Reasoning in Open Language Models. arXiv:2402.03300, 2024. arxiv.org
[5] E. J. Hu et al. LoRA, Low-Rank Adaptation of Large Language Models. arXiv:2106.09685, 2021. arxiv.org
[6] Unsloth. GitHub. github.com
[7] NVIDIA. NeMo RL. GitHub. github.com
[8] Z. Liu et al. Understanding R1-Zero-Like Training, A Critical Perspective. arXiv:2503.20783, 2025. arxiv.org
cite this note
Jack. Can models learn hidden pricing rules in a market economy?. Undue Attention, note 1, 2026.