Add 1-Ounce Gold and Micro Ultra Treasury futures - #9692
Conversation
AlexCatarino
left a comment
There was a problem hiding this comment.
Reviewed against CME's own CFTC product-certification filings (cmegroup.com returns 403 to automated fetches, but COMEX Submission 24-474 and CBOT Submission 24-090 carry the verbatim rulebook text), and built/ran the suites locally.
The specs all check out, and the correction in the description is right. CBOT Submission 24-090: rulebook ch. 54 = MTN (the 10-Year Note micro), ch. 56 = MWN (the Bond micro), with the two tick sizes exactly as described (MTN ½ of 1/32 = $1.5625, MWN 1/32 = $3.125) and $100/point on $10,000 face. Issue #9622 has MWN backwards. For 1OZ, COMEX ch. 131 confirms 1 troy oz, $0.25/oz, financial settlement, listings in Feb/Apr/Jun/Aug/Oct/Dec only with no serial months, and termination on "the third last business day of the month prior to the contract month" — stated three separate ways in the filing, so the departure from GC is real.
Both expiry formulas reproduce CME-published dates: the filing's own worked example for MTN Jun-2024 (30 May 2024), the listed MTN/MWN Sep-2026 (28 Aug), Dec-2026 (27 Nov) and Mar-2027 (25 Feb), and 1OZ Oct-2026 (28 Sep) and Dec-2026 (25 Nov). I also verified the market-hours claim by parsing both versions of the JSON: exactly 3 entries added, 0 existing entries changed, ordering preserved.
Two things block. All three contracts expire in the month before the contract month but are not registered in ExpiriesPriorMonth, which makes Symbol.Value one month early; and the three margin files are missing, which silently gives these contracts zero margin. Details inline.
FYI on the suites you couldn't run: MarketHoursDatabaseTests and SymbolPropertiesDatabaseTests pass here on .NET 10 (152/152, no GIL crash), so that looks local to your setup.
|
|
||
| // This contract is cash settled, so it stops trading before the contract month even starts. | ||
| // Trading terminates on the third last business day of the month prior to the contract month. | ||
| var previousMonth = time.AddMonths(-1); |
There was a problem hiding this comment.
Because this returns a date in the month before the contract month, 1OZ also needs registering in FuturesExpiryUtilityFunctions.ExpiriesPriorMonth — that's the companion half of the HH pattern you followed (HH is there at line 414).
Without it, SymbolRepresentation.GenerateFutureTicker renders the Feb-2026 contract as 1OZ28F26 instead of 1OZ28G26 (I confirmed this by test), and LeanData paths plus FutureUniverse.ToCsv record the wrong contract month. Add to that dictionary:
{ Futures.Metals.OneOunceGold, 1 },Worth a [TestCase] in SymbolRepresentationTests.GenerateFutureTickerExpiringInPreviousMonth alongside the existing CL/HH rows.
| // trading earlier than TN does. | ||
| // Trading terminates 2 business days before the first delivery day of the contract | ||
| // month. The first delivery day is the first business day of that month. | ||
| var firstDeliveryDay = FuturesExpiryUtilityFunctions.NthBusinessDay(time, 1, holidays); |
There was a problem hiding this comment.
Same as the 1OZ note: this lands in the previous month, so MTN needs an ExpiriesPriorMonth entry too, otherwise the Mar-2025 contract renders as MTN27G25 instead of MTN27H25.
{ Futures.Financials.MicroUltraTenYearUSTreasuryNote, 1 },| // Trading terminates 2 business days before the first delivery day of the contract | ||
| // month. The first delivery day is the first business day of that month. | ||
| var firstDeliveryDay = FuturesExpiryUtilityFunctions.NthBusinessDay(time, 1, holidays); | ||
| return FuturesExpiryUtilityFunctions.AddBusinessDays(firstDeliveryDay, -2, holidays); |
There was a problem hiding this comment.
CBOT ch. 54102.A terminates trading at 2:00 p.m. CT, so the expiry should carry the time (19:00 UTC under LEAN's summer-offset convention). Returning midnight makes IsFutureContractExpired treat the contract as expired for the whole of its final trading day.
F1U is the precedent here — same 2:00 p.m. close, same 19:00 encoding, and its test case already uses TwoPMCentralTime.
| return FuturesExpiryUtilityFunctions.AddBusinessDays(firstDeliveryDay, -2, holidays); | |
| return FuturesExpiryUtilityFunctions.AddBusinessDays(firstDeliveryDay, -2, holidays).AddHours(19); |
| // trading earlier than UB does. | ||
| // Trading terminates 2 business days before the first delivery day of the contract | ||
| // month. The first delivery day is the first business day of that month. | ||
| var firstDeliveryDay = FuturesExpiryUtilityFunctions.NthBusinessDay(time, 1, holidays); |
There was a problem hiding this comment.
MWN needs the same ExpiriesPriorMonth entry — currently the Mar-2025 contract renders as MWN27G25 instead of MWN27H25.
{ Futures.Financials.MicroUltraUSTreasuryBond, 1 },| // Trading terminates 2 business days before the first delivery day of the contract | ||
| // month. The first delivery day is the first business day of that month. | ||
| var firstDeliveryDay = FuturesExpiryUtilityFunctions.NthBusinessDay(time, 1, holidays); | ||
| return FuturesExpiryUtilityFunctions.AddBusinessDays(firstDeliveryDay, -2, holidays); |
There was a problem hiding this comment.
Same 2:00 p.m. CT termination here (ch. 56102.A).
| return FuturesExpiryUtilityFunctions.AddBusinessDays(firstDeliveryDay, -2, holidays); | |
| return FuturesExpiryUtilityFunctions.AddBusinessDays(firstDeliveryDay, -2, holidays).AddHours(19); |
| [TestCase(QuantConnect.Securities.Futures.Financials.MicroUltraTenYearUSTreasuryNote, Zero)] | ||
| [TestCase(QuantConnect.Securities.Futures.Financials.MicroUltraUSTreasuryBond, Zero)] |
There was a problem hiding this comment.
Pairs with the 2:00 p.m. CT change above. TwoPMCentralTime already exists in this class (= "19:00:00"), so no new constant is needed.
| [TestCase(QuantConnect.Securities.Futures.Financials.MicroUltraTenYearUSTreasuryNote, Zero)] | |
| [TestCase(QuantConnect.Securities.Futures.Financials.MicroUltraUSTreasuryBond, Zero)] | |
| [TestCase(QuantConnect.Securities.Futures.Financials.MicroUltraTenYearUSTreasuryNote, TwoPMCentralTime)] | |
| [TestCase(QuantConnect.Securities.Futures.Financials.MicroUltraUSTreasuryBond, TwoPMCentralTime)] |
| [TestCase(QuantConnect.Securities.Futures.Metals.MiniNYSilver, OneTwentyFivePM)] | ||
| [TestCase(QuantConnect.Securities.Futures.Metals.Gold100Oz, OneThirtyPM)] | ||
| [TestCase(QuantConnect.Securities.Futures.Metals.Silver5000Oz, OneTwentyFivePM)] | ||
| [TestCase(QuantConnect.Securities.Futures.Metals.OneOunceGold, Zero)] |
There was a problem hiding this comment.
Minor, on the XML data behind this case: two of the six 1OZ rows use contract months CME doesn't list (2025-09-01, 2026-11-01 — listings are Feb/Apr/Jun/Aug/Oct/Dec only). They land on the real Oct-2025 and Dec-2026 last-trade dates anyway, so changing ContractMonth to 2025-10-01 and 2026-12-01 asserts the same values as genuine contracts. The roll-forward behaviour could then get its own [Test], the way MCLFutures and Kospi200Futures do it.
| cbot,MTN,future,Micro Ultra 10-Year U.S. Treasury Note Futures,USD,100.0,0.015625,1.0 | ||
| cbot,MWN,future,Micro Ultra U.S. Treasury Bond Futures,USD,100.0,0.03125,1.0 |
There was a problem hiding this comment.
Data/future/cbot/margins/MTN.csv and MWN.csv are missing. When the margin file isn't found, FutureMarginModel.FromCsvFile logs an error and defaults initial/maintenance to zero, so these contracts get effectively unlimited leverage.
Data/future/krx/margins/KM.csv (#9585) is the format to copy:
# we don't have historical information for this symbol
date,initial,maintenance
19900101,<initial>,<maintenance>
| cme,SDA,future,SP500 S&P 500 Annual Dividend Index future,USD,250.0,0.05,1.0 | ||
| cme,TPD,future,TOPIX USD Futures,USD,50.0,0.5,1.0 | ||
| cme,TPY,future,TOPIX JPY Futures,JPY,5000.0,0.5,1.0 | ||
| comex,1OZ,future,1-Ounce Gold Futures,USD,1,0.25,1 |
There was a problem hiding this comment.
Needs Data/future/comex/margins/1OZ.csv too (missing file → zero margin, as above).
Multiplier and tick are correct per ch. 131102.B/C. Cosmetic only — neighbouring rows write the multiplier and lot size in 1.0 style:
| comex,1OZ,future,1-Ounce Gold Futures,USD,1,0.25,1 | |
| comex,1OZ,future,1-Ounce Gold Futures,USD,1.0,0.25,1.0 |
Description
This adds three CME futures so
AddFuture()can find them:Each one gets a ticker constant, an expiry function, a symbol-properties row,
and a market-hours entry.
I found two things that are different from the issue:
1OZ does not use the same calendar as GC/MGC. Trading stops on the third
last business day of the month before the contract month. I checked 8 real
expiry dates from CME and they all match, including the Thanksgiving one.
I followed the same pattern already used for Henry Hub Last-day Financial (HH).
MWN is not the 10-Year Note micro. It is the Ultra Bond micro. The Note
micro is MTN. The tick sizes are different too (1/64 for the Note, 1/32 for
the Bond). I added both so it is correct either way. Happy to remove one if
you only want one.
I left QNDX out. The issue says Spot-Quoted futures do not follow the
quarterly-roll model and need a design decision first. I can do it in a
follow-up PR.
Related Issue
Partially addresses #9622.
Motivation and Context
These tickers are not registered anywhere in LEAN, so users cannot add them at
all. This unblocks live trading and live IB data for them.
Requires Documentation Change
Yes, the list of supported futures should get these three new tickers.
How Has This Been Tested?
I added new test cases in
FuturesExpiryFunctionsTestsand new rows inFuturesExpiryFunctionsTestData.xml.FuturesExpiryFunctionsTestspasses203/203 locally.
I could not run
MarketHoursDatabaseTestsorSymbolPropertiesDatabaseTestson my machine. They crash during teardown with a Python GIL error, and this
also happens on unmodified master, so I do not think it is from my change.
Some 1OZ test rows use off-cycle months on purpose, to test that it rolls
forward to the next Feb/Apr/Jun/Aug/Oct/Dec contract.
One note on the diff: the market-hours JSON looks like it changed a lot, but I
parsed the file before and after and only 3 entries were added. No existing
entry was changed. The big diff is just git lining up the wrong blocks, because
the new entries look almost the same as their neighbors.
Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>