Skip to content

PermitTrading#2285

Open
RahelMA wants to merge 4 commits intoremindmodel:developfrom
RahelMA:trading
Open

PermitTrading#2285
RahelMA wants to merge 4 commits intoremindmodel:developfrom
RahelMA:trading

Conversation

@RahelMA
Copy link
Contributor

@RahelMA RahelMA commented Feb 12, 2026

Purpose of this PR

This PR tries to reactivate permit trading ;)

With the new realization TradingOnRef in 41_emicapregi, a reference run is used to allocate emission allowances for each region and timestep.

Overall, there are three different types of permit trading:

cm_permittradescen  = 1;         !! def = 1  !! regexp = [1-3]

(1): full permit trade (no restrictions)
(2): no permit trade (only domestic mitigation)
(3): limited trade (certain percentage of regional allowances)
for limited trade use cm_pemittradefinalyr to set the final year until permit trading is allowed
with cm_pemittraderatio set the percentage of allowed trade

In these exemplary runs, I used 20% of permit allocation until 2060 and 2100:

image image image

Please note that I increased the surplus tolerance to also allow more aggressive trade patters like full trading or with a very short time frame. Honestly, it's hard for me to judge if it is a reasonable scale:

p80_surplusMaxTolerance("perm") = 2* 300 * 12/44 / 1000; to p80_surplusMaxTolerance("perm") = 3* 300 * 12/44 / 1000;

@LaviniaBaumstark what do you think?

Type of change

Indicate the items relevant for your PR by replacing ◻️ with ☑️.
Do not delete any lines. This makes it easier to understand which areas are affected by your changes and which are not.

Parts concerned

  • ☑️ GAMS Code
  • ◻️ R-scripts
  • ◻️ Documentation (GAMS incode documentation, comments, tutorials)
  • ◻️ Input data / CES parameters
  • ◻️ Tests, CI/CD (continuous integration/deployment)
  • ☑️ Configuration (switches in main.gms, default.cfg, and scenario_config*.csv files)
  • ◻️ Other (please give a description)

Impact

  • ◻️ Bug fix
  • ◻️ Refactoring
  • ◻️ New feature
  • ◻️ Change of parameter values or input data (including CES parameters)
  • ☑️ Minor change (default scenarios show only small differences)
  • ◻️ Fundamental change of results of default scenarios

Checklist

Do not delete any line. Leave unfinished elements unchecked so others know how far along you are.
In the end all checkboxes must be ticked before you can merge
.

  • I executed the automated model tests (make test) after my final commit and all tests pass (FAIL 0)
  • I adjusted the reporting in remind2 if and where it was needed
  • I adjusted the madrat packages (mrremind and other packages involved) for input data generation if and where it was needed
  • My code follows the coding etiquette
  • I explained my changes within the PR, particularly in hard-to-understand areas
  • I checked that the in-code documentation is up-to-date
  • I adjusted forbiddenColumnNames in readCheckScenarioConfig.R in case the PR leads to deprecated switches
  • I updated the CHANGELOG.md correctly (added, changed, fixed, removed, input data/calibration)

Further information (optional)

  • Runs with these changes are here:
    /p/tmp/rahelma/Committed/Equity/permTrading
  • Comparison of results (what changes by this PR?):
    /p/tmp/rahelma/Committed/Equity/permTrading/compScen-tradingScen-2026-02-10_20.52.00-H12.pdf

@RahelMA RahelMA marked this pull request as ready for review February 12, 2026 13:22
Copy link
Contributor

@lecfab lecfab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the dusting and testing that it takes to revive the permit trading!
I left comments here and there, mainly about aesthetics and documentation.

cm_pemittraderatio "Percentage of restricted permit trading"
;
cm_pemittraderatio = 0.2; !! def = 0.2
*'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the existing names of "permit trades scenarios" a little confusing, in the sense that we would always need to refer to the main.gms to understand what they mean. The big undertaking of reactivating permit trade is possibly a good opportunity to simplify that. Two suggestions (either/or):

  1. simpler one: rename the "no permit trade" to cm_permittradescen = 0
  2. nicer one: remove cm_permittradescen, because it is redundant with cm_pemittraderatio (0% for no trade, 100% for full trade, and whatever in-between for limited trade)

loop(ttot$(ttot.val ge 2005),
***this is a 30$/tCo2eq in 2020 trajectory:
pm_pvp(ttot,"perm") = 0.11*1.05**(ttot.val-2020) * pm_pvp(ttot,"good");
pm_pvp(ttot,"perm") = 0.18*1.05**(ttot.val-2020) * pm_pvp(ttot,"good");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this change mean? can you add a comment explaining this number and the whole formula?
Is there a parameter that we can use instead of hard-coded 1.05 @fpiontek @johanneskoch94? I believe in some other places it was decided to use 1.045 instead (but I can't find where, pm_prtp is usually only 1.015)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it just the initial guess for the permit price trajectory if no permit price exists in the input gdx?
Shouldn't we adjust the comment above to indicate " ~49$/tCO2eq" instead?
It is an increase in price level expected to reflect that we are moving more towards overshoot scenarios with end of period prices expected to be high?

*' * (2): no permit trade (only domestic mitigation)
*' * (3): limited trade (certain percentage of regional allowances)
*' for limited trade use cm_pemittradefinalyr to set the final year until permit trading is allowed
*' with cm_pemittraderatio set the percentage of allowed trade
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vm_Mport.up(t,regi,"perm")$(t.val gt cm_pemittradefinalyr)=0;
vm_Mport.up(t,regi,"perm")$(t.val le 2025)=0;
vm_Mport.lo(t,regi,"perm") = 0;
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when cm_permittradescen is 1?
Also, should the cap be defined by gross emissions rather than abs(p41_co2eq(t,regi))?

Visual suggestions:

- disactivate
+ deactivate 
 
- cm_pemittradefinalyr     cm_pemittraderatio
+ cm_pemitTradeFinalYr     cm_pemitTradeRatio

- ...)=0
+ ...) = 0

- vm_Xport.up(t,regi,"perm")$(t.val le cm_pemittradefinalyr)=abs(cm_pemittraderatio*(p41_co2eq(t,regi)));
- vm_Xport.up(t,regi,"perm")$(t.val gt cm_pemittradefinalyr)=0;
- vm_Xport.up(t,regi,"perm")$(t.val le 2025)=0;
- vm_Xport.lo(t,regi,"perm") = 0;
+ vm_Xport.fx(t,regi,"perm") = 0;
+ vm_Xport.up(t,regi,"perm") $ (t.val > 2025 and t.val <= cm_pemitTradeFinalYr) = cm_pemitTradeRatio * abs(p41_co2eq(t,regi));

*** initialization of pm_shPermit
pm_shPerm(t,regi) = p41_co2eq(t,regi)/sum(regi2, p41_co2eq(t,regi2));
pm_emicapglob(t) = sum(regi, p41_co2eq(t,regi));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give more explanations about what is loaded, and what the ref and bau runs will mean in the calculation of permit allowances?

Also, I'm a bit worried about the shPerm values when a region has negative emissions (in which case I suppose p41_co2eq is negative).

Visual suggestion to avoid calculating the same thing twice:

- pm_shPerm(t,regi) = p41_co2eq(t,regi)/sum(regi2, p41_co2eq(t,regi2));		
- pm_emicapglob(t) = sum(regi, p41_co2eq(t,regi));
+ pm_emicapglob(t) = sum(regi, p41_co2eq(t,regi));
+ pm_shPerm(t,regi) = p41_co2eq(t,regi) / pm_emicapglob(t);

Copy link

@benjaminpeeters benjaminpeeters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @RahelMA
Thanks for the PR.

Added just a few comments from my side, notably on the changes for etaSL.
Not sure this is useful. Hope it is.

I just have another (small) question:
why cm_pemittradefinalyr and cm_pemittraderatio instead of cm_permittradefinalyr and cm_permittraderatio (with an 'r' in 'permit')?

p80_etaST(tradePe) = 0.3;
p80_etaST("good") = 0.25;
p80_etaST("perm") = 0.3;
p80_etaST("perm") = 0.8;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand these p.._etaST/LT haven't been changed since the codebase was open-sourced in December 2019 with the explicit indication "These parameters are pretty sensitive" :)
The permit market also does not "benefit" from the adaptive convergence boosting mechanism (the 4x/8x/16x escalation after iterations 15/20/25) -- only applies to tradePe and good if I'm not wrong (see https://github.com/remindmodel/remind/blob/develop/modules/80_optimization/nash/postsolve.gms#L139C1-L173C19).

I wonder:

  • what motivate to such a big shift (from 0.3 to 0.8)?
  • why p80_etaST("pebiolc") = 0.8; was already fixed to 0.8 back then? @LaviniaBaumstark
  • have we try the "adaptative boosting"? wouldn't it be more consistent with the rest of the code? Are there good reasons to treat "perm" differently in that regard (e.g., much smaller fraction of the world exchanges wrt "good"? "pebiolc" does seem to have this boost as well)
  • don't we need to be even more strict on p80_etaSA for "perm" given the fact that the inter iteration adjustment cost parameter is much lower than for other goods (https://github.com/remindmodel/remind/blob/develop/modules/80_optimization/nash/datainput.gms#L45-L48) and therefore has more risk of oscillation? (not sure) Likewise, wouldn't it lead to better convergence (less oscillation while adjustment) to decrease the adjustment cost even further to not be so aggressive about p80_etaST ? In other words, instead of forcing the market with heavy-handed price corrections (high etaST), why not make the planners more responsive to prices (lower etaAdj) so the market clears more?

*** | AGPL-3.0, you are granted additional permissions described in the
*** | REMIND License Exception, version 1.0 (see LICENSE file).
*** | Contact: remind@pik-potsdam.de
*** SOF ./modules/41_emicapregi/AbilityToPay/bounds.gms

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new files reference AbilityToPay instead of TradingOnRef

Comment on lines +13 to +14
p41_co2eq(t, regi) = p41_co2eq_in(t,regi,"co2");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it used anywhere or come from the copy paste from AbilityToPay?

p80_surplusMaxTolerance(tradePe) = 2* 1.5 * sm_EJ_2_TWa; !! convert EJ/yr into internal unit TWa
p80_surplusMaxTolerance("good") = 2* 100/1000; !! in internal unit, trillion Dollar
p80_surplusMaxTolerance("perm") = 2* 300 * 12/44 / 1000; !! convert MtCO2eq into internal unit GtC
p80_surplusMaxTolerance("perm") = 3* 300 * 12/44 / 1000; !! convert MtCO2eq into internal unit GtC

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the actual relative residual permit surplus at convergence in the test runs? Could you share the p80_surplusMaxRel values? do the iterations converge below the tolerance, or does it tend to stay blocked close to it?

loop(ttot$(ttot.val ge 2005),
***this is a 30$/tCo2eq in 2020 trajectory:
pm_pvp(ttot,"perm") = 0.11*1.05**(ttot.val-2020) * pm_pvp(ttot,"good");
pm_pvp(ttot,"perm") = 0.18*1.05**(ttot.val-2020) * pm_pvp(ttot,"good");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it just the initial guess for the permit price trajectory if no permit price exists in the input gdx?
Shouldn't we adjust the comment above to indicate " ~49$/tCO2eq" instead?
It is an increase in price level expected to reflect that we are moving more towards overshoot scenarios with end of period prices expected to be high?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants