In this blog I’ll show how to use SAMOA to calculate the returns of buy-and-hold strategies for the 10Y Treasury Note Future (TY) that trades in the CBOT. These results can be useful for benchmark purposes.
Manual Calculation (Using adjusted prices)
- As explained in the blog “Generic Series”, SAMOA calculates forward and backwards adjusted prices for futures. In this section we’ll use those prices so that we don’t have to deal with the rolls. All we need is daily closes. SAMOA’s database goes back to the TYM82 contract:
select Min(DateS) as DateS, dbo.ssToFrontContract('TY', Min(DateS), null) as [Contract], Min([Close]) as Price from DailyFutFront where Market='TY'
union all
select Max(DateS) as DateS, dbo.ssToFrontContract('TY', Max(DateS), null) as [Contract], Max([Close]) as Price from DailyFutFront where Market='TY'

- First, lets calculate the monthly returns manually (using forward-adjusted prices):
declare @TY_DailyCloses_Returns_Monthly table (
DateS datetime
,FirstP decimal(18,8)
,LastP decimal(18,8)
,PrevLastP decimal(18,8)
,ReturnPerc decimal(18,2)
);
insert into @TY_DailyCloses_Returns_Monthly (DateS, FirstP, LastP)
select
Max(DateS) as DateS
, dbo.ssAggrFirst( dates, CloseA) as FirstP
, dbo.ssAggrLast( dates, CloseA) as LastP
from DailyFutFrontA
where Market='TY'
group by dbo.ssOnlyMonth(dates);
UPDATE t SET t.PrevLastP = u.PrevLastP
FROM @TY_DailyCloses_Returns_Monthly t
INNER JOIN (
select DateS, (select top 1 LastP from @TY_DailyCloses_Returns_Monthly where DateS<p.DateS order by DateS desc) as PrevLastP
from @TY_DailyCloses_Returns_Monthly p
) u
ON t.DateS = u.DateS;
UPDATE @TY_DailyCloses_Returns_Monthly SET ReturnPerc = (LastP-isnull(PrevLastP,FirstP) ) / isnull(PrevLastP,FirstP) * 100;
Select * from @TY_DailyCloses_Returns_Monthly Order by DateS;

Notes about these calculations:
- DateS: Last day of the month for which there is a closing price.
- FirstP: First closing price of the month (chronologically), adjusted by the rolls.
- LastP: Closing price on DateS, adjusted by the rolls.
- PrevLastP: LastP of the previous month.
- ReturnPerc: Return for the month ending on DateS. Calculated as (LastP- PrevLastP) / PrevLastP * 100, except for the first row, where PrevLastP is replaced by FirstP.
- The functions ssAggrFirst() and ssAggrLast() are aggregating functions provided by SAMOA, that return the first and last prices chronologically.
Using SAMOA
SAMOA’s Booking System can handle generic futures if you don’t want to deal with rolls. Let’s use this feature to simulate the strategy.
- The strategy script for this buy-and-hold strategy can be defined as follows (please read “Back-testing Strategies” for more information about how to define a strategy using JScript, and about the function ssRunStrategyS):
var K = 10000000;
var valuept = CacheDS.Get_PLInfo("TY").ValuePT;
function event_OpenBook() {
context.AddCapital(K, "Initial K");
context.AddMark("TY", vars.Close);
var q = Math.Floor( K / (valuept * vars.Close));
context.AddTrade(null, "TY", q, vars.Close, "Stay fully invested");
}
function event_CloseDay() {
context.AddMark("TY", vars.Close);
}
function event_CloseBook() {
context.AddTrade(null, "TY", -book.accQ("TY"), vars.Close, "Liquidate");
}
NOTES:
- At the beginning, we add $10,000,000 of capital (K) to the book (line 4)
- The code CacheDS.Get_PLInfo("TY").ValuePT returns the value of a point for TY.
- We calculate the amount of contracts (q) necessary to be fully invested (line 6), and make the trade (line 7) at the prevailing price (vars.Close).
- Each new day, we mark the book to market (line 10).
- Finally, at the end of the simulation, we close the position (line 13).
- We can feed this JScript into the function ssRunStrategyS(…), which will produce only 2 trades:
- The monthly P&L report will look like this:
Figure P1:

- The monthly returns in the green column match exactly those calculated previously. In fact, when displayed side by side we never see differences bigger than 1 basis point, which can be attributed to rounding errors.

- Here are the summary statistics on the column “FromPrices-FromBook”:
- Finally, here is the full table of returns:

With no cash flows
The strategy that we’ve simulated so far buys 140 contracts at the beginning of the simulation, and rolls them all into the new front contracts every 3 months. Since the prices of the front and back contracts are usually different, this portfolio generates cash flows along the simulation.
We can modify the strategy so that on each roll we only use the proceeds of the sale to buy the new front contracts, thus reducing the cash left after both transactions (not eliminating it completely because it is not possible to trade fractions of a contract).
- For this strategy we will take care of the rolls ourselves. Therefore we need a table with the front contracts and the dates of the rolls:
with MainT as (
select Market, DateS,
Expiry as ExpiryF, [Close] as CloseF,
dbo.ssNextGenericCode(Expiry,'Front','TYFut',(select RollingMethod from Markets where Market='TY'),1) as ExpiryB
from DailyFutFront
where Market='TY'
)
select
Market, DateS,
Market + ExpiryF as Front, CloseF as FrontP,
case when DateS=dbo.ssGetCodeDatesTO(ExpiryF, 'Front', Market+'Fut', NULL) then 'YES' else '' end as Switch,
Market + ExpiryB as Back,
(select [Close] from DailyFut where Market=MainT.Market and Expiry=MainT.ExpiryB and DateS=MainT.DateS) as 'BackP'
from MainT
order by DateS
NOTES:
- Front, FrontP: front contract on DateS, with it’s price
- Back, BackP: back contract on DateS, with it’s price (if available)
- Switch: “YES” when a roll needs to be made from the Front to the Back (which will become the front the next day)
- Only rows with a FrontP are returned in this table.
- Let’s define the strategy:
var K = 10000000;
var valuept = CacheDS.Get_PLInfo("TY").ValuePT;
function event_OpenBook() {
context.AddCapital(K, "Initial K");
//Initial Buy
var q = Math.Floor(K / (valuept * vars.FrontP));
context.AddTrade(null, vars.Front, q, vars.FrontP, "Initial Buy");
}
function event_OpenDay() {
//Mark the book
context.AddMark(vars.Front, vars.FrontP);
if (vars.BackP!=null) context.AddMark(vars.Back, vars.BackP);
}
function event_CloseDay() {
if (vars.Switch == "YES") {
context.Liquidate("Switch:sell");
//Switch: buy
var q = Math.Floor( book.NAV / (valuept * vars.BackP) );
context.AddTrade(null, vars.Back, q, vars.BackP, "Switch: buy");
}
}
function event_CloseBook() {
context.Liquidate("Liquidate");
}
Note that this time around, in event_OpenDay we roll the position whenever Switch=“YES”.
- Feeding this script into ssRunStrategyS will produce the following trades:
NOTES:
- One “Initial Buy”, followed by a number of rolls (“Switch:sell”/”Switch:sell” pairs), followed by one “Liquidate” trade.
- The number of contracts Q varies over time to keep us fully invested.
- Which will produce the following slightly different returns:

[...] Buy and Hold: TY futures [...]
[...] Buy and Hold: TY futures [...]