Present-value model & variance bounds: Price equals the discounted stream of expected future payouts. Under rational expectations, the variance of price can’t exceed the variance of the discounted-cash-flow process; Shiller showed stock prices are too volatile relative to this bound (“Do Stock Prices Move Too Much…”, AER 1981). American Economic Association
Campbell–Shiller log-linear identity: A log-linearization of the present-value model links the log dividend-price ratio to expected future returns and dividend growth, yielding testable decompositions widely used in asset-pricing. Stern School of Business
Key PDFs (authoritative):
Shiller (1981) AER — “Do Stock Prices Move Too Much to be Justified by Subsequent Changes in Dividends?” (classic variance-bounds test). American Economic Association
Campbell & Shiller (1988, RFS/NBER) — “The Dividend-Price Ratio and Expectations of Future Dividends and Discount Factors” (log-linear present-value relation). Stern School of Business
Nobel Prize 2013 press release (PDF) — award to Fama, Hansen, Shiller “for their empirical analysis of asset prices.” NobelPrize.org
Shiller’s Nobel Prize lecture (PDF): “Speculative Asset Prices.” NobelPrize.org
Nobel “Popular Science” background (PDF): “Trendspotting in asset markets.” NobelPrize.org
Code
// ============================================================================
// Shiller_EURUSD.c — Zorro / lite-C demonstration script (EUR/USD)
// ============================================================================
// GOAL
// -----
// This script illustrates two classic ideas often discussed in the
// 'variance bounds' / 'dividend–price ratio' (dp) literature:
//
// Part A) An *illustrative* check of "excess volatility":
// compare the variance of price P_t to a discounted-sum proxy P*_t
// constructed from a toy 'dividend' series D_t (here: carry proxy).
//
// Part B) A very simple *return predictability* example using the
// dividend-price ratio dp_t := log(D_t / P_t) to predict future
// K-day returns via a rolling univariate OLS slope.
//
// NOTES (Zorro / lite-C specifics)
// --------------------------------
// • This is a research demo; it trades with a minimal rule only to show how to
// translate a scalar signal into actions. It is *not* a strategy.
// • Positive series index in Zorro points to the PAST. Example: X[1] is
// the previous bar, X[K] is K bars ago; X[0] is the current bar.
// • `vars` are rolling series returned by `series(...)`; `var` is a scalar.
// • `asset("EUR/USD")` selects the EUR/USD symbol defined by your asset list.
// • `Lots` is set to 1 purely for a visible action in Part B.
//
// SAFETY / ROBUSTNESS
// -------------------
// • We clamp denominators with a small epsilon to avoid log(0) or division by 0.
// • Window sizes and horizons are explicitly checked against LookBack and Bar.
//
// VERSION
// -------
// Tested with Zorro 2.x / lite-C syntax.
// ============================================================================
function run()
{
// ------------------------------------------------------------------------
// SESSION / DATA SETTINGS
// ------------------------------------------------------------------------
BarPeriod = 1440; // 1440 minutes = 1 day bars
StartDate = 2010; // start year (use your data span)
LookBack = 600; // bars held in rolling series (must cover max windows)
asset("EUR/USD");
set(PLOTNOW); // auto-plot series as they are produced
// ------------------------------------------------------------------------
// PRICE SERIES
// ------------------------------------------------------------------------
// P_t: close price; R1: 1-bar (1-day) log return (not used later, kept for ref)
vars P = series(priceClose()); // P[0]=today, P[1]=yesterday, ...
vars R1 = series(log(P[0]/P[1])); // ln(P_t / P_{t-1})
// ------------------------------------------------------------------------
// "DIVIDEND" PROXY D_t (here: a constant carry series for demonstration)
// ------------------------------------------------------------------------
// In FX, a carry-like proxy could be the interest rate differential.
// Here we just set a constant daily carry to mimic ~1.5% per annum.
var eps = 1e-12; // small epsilon for safe divisions
var carryDaily = 0.015/252.; // ? 1.5% p.a. / 252 trading days
vars D = series(carryDaily); // D_t aligned with bars
// =========================================================================
// PART A: "Ex post" discounted-sum proxy P*_t for an excess-volatility check
// =========================================================================
// Construct a simple discounted sum of past D_t as a toy P*_t proxy:
// P*_t ? ?_{k=1..Kmax} D_{t-k} / (1+r_d)^k
// This is ONLY an illustration, not a proper present-value model.
int Kmax = 126; // look-back horizon (~6 months)
var r_d = 0.0001; // daily discount ? 0.01% (~2.5% p.a.)
vars Px = series(0); // rolling proxy P*_t
if (Bar > LookBack)
{
// Build discounted sum from *past* values of D (D[1]..D[Kmax])
var sumDisc = 0;
var disc = 1;
int k;
for (k=1; k<=Kmax; k++)
{
disc /= (1 + r_d); // (1+r)^(-k)
var Dp = D[k]; // D_{t-k}
sumDisc += disc * Dp;
}
Px[0] = sumDisc; // write current P*_t proxy
// Compare rolling variances of P and P* over a window W
int W = 500;
if (Bar > LookBack + Kmax + W)
{
// Means
var meanP = 0, meanPx = 0;
int i;
for (i=0; i<W; i++) { meanP += P[i]; meanPx += Px[i]; }
meanP /= (var)W;
meanPx /= (var)W;
// Sample variances
var varP = 0, varPx = 0;
for (i=0; i<W; i++) {
var a = P[i]-meanP;
var b = Px[i]-meanPx;
varP += a*a;
varPx += b*b;
}
varP /= (var)(W-1);
varPx /= (var)(W-1);
// Plots for visual inspection
plot("Var(P)", varP, NEW, 0);
plot("Var(P*)", varPx, 0, 0);
// Console line every ~50 bars
if (Bar%50==0)
printf("\n[EXCESS VOL] W=%d Var(P)=%.6g Var(P*)=%.6g ratio=%.3f",
W, varP, varPx, varP/(varPx+eps));
}
}
// =========================================================================
// PART B: Return predictability via the dividend-price ratio, dp_t
// =========================================================================
// We compute:
// dp_t := log(D_t / P_t)
// and regress *past* K-day realized returns on dp over a rolling window Wreg
// to estimate a slope 'beta'. A positive beta implies higher dp predicts
// higher future returns (in this toy setup).
//
// Then we convert the instantaneous dp z-score into a small long/short
// trade signal, clipped and scaled to the range [-0.5, +0.5] (Lev).
int K = 20; // horizon (~1 month)
vars DP = series(log(max(eps, D[0]) / max(eps, P[0]))); // dp_t series
vars RK = series(log(P[0]/P[K])); // realized K-day return
int Wreg = 500; // regression window
if (Bar > LookBack + K + Wreg)
{
// ----------------------------
// Rolling univariate OLS slope
// ----------------------------
var sumX=0, sumY=0, sumXX=0, sumXY=0;
int i;
for (i=0;i<Wreg;i++){
var x = DP[i]; // predictor
var y = RK[i]; // response (past K-day return)
sumX += x;
sumY += y;
sumXX += x*x;
sumXY += x*y;
}
var meanX = sumX/Wreg;
var meanY = sumY/Wreg;
var denom = sumXX - Wreg*meanX*meanX;
var beta = 0; // OLS slope
if (denom != 0)
beta = (sumXY - Wreg*meanX*meanY)/denom;
plot("beta(dp->Kret)", beta, NEW, 0);
// ----------------------------
// z-score of current dp_t
// ----------------------------
var meanDP=0, varDP=0;
for (i=0;i<Wreg;i++) meanDP += DP[i];
meanDP/=Wreg;
for (i=0;i<Wreg;i++){ var d=DP[i]-meanDP; varDP += d*d; }
varDP /= (Wreg-1);
var sDP = sqrt(max(eps,varDP));
var zDP = (DP[0]-meanDP)/sDP;
// Clip z to avoid huge outliers
var zClip = zDP;
if (zClip > 2) zClip = 2;
if (zClip < -2) zClip = -2;
// Direction follows beta sign
var sig = 0;
if (beta > 0) sig = zClip;
else if (beta < 0) sig = -zClip;
// ----------------------------
// POSITION TRANSLATION
// ----------------------------
// Map raw signal in [-2,+2] to leverage-like knob in [-1,+1],
// then cap at ±0.5 to keep actions small in the demo.
var Target = sig; // raw -2..+2
var MaxLev = 0.5; // clamp bound
var Lev = Target/2.0; // scale to -1..+1 then cap
if (Lev > MaxLev) Lev = MaxLev;
if (Lev < -MaxLev) Lev = -MaxLev;
// Minimal action rule:
// if Lev is meaningfully positive => long; if negative => short; else flat.
// We keep Lots=1 for visibility; no money management here.
if (Lev > 0.05) { exitShort(); enterLong(); Lots = 1; }
else if (Lev < -0.05) { exitLong(); enterShort(); Lots = 1; }
else { exitLong(); exitShort(); }
// Plots for monitoring
plot("z(dp)", zDP, 0, 0);
plot("lev", Lev, 0, 0);
}
// End of run() — Zorro handles bar stepping automatically.
}
the strategy encodes the Campbell–Shiller insight that high dividend/“carry” yields relative to price predict higher future returns, wraps that valuation in a z-scored ????????????, and lets a linear neural unit blend it with momentum/volatility states to produce directional edge; trades are taken when the learned edge clears a small threshold.
Code
// ============================================================================
// Shiller NN EUR/USD — ML trading demo (Zorro / lite-C)
//
// • Learner: PERCEPTRON (+BALANCED) on a compact 8-feature vector.
// • Trigger: edge = predL - predS with a simple RSI + momentum fallback.
// • Plots: (name, value, color, style); panels anchored correctly.
// ============================================================================
#include <default.c>
// ===== Configuration =====
#define LOOKBACK 200 // Bars required before running the model/logic
#define BarMins 1440 // Daily bars
#define LotsFixed 1 // Position size per entry
#define EdgeMin 0.05 // ML edge threshold (predL - predS) to trigger a trade
// ===== Safe helpers =====
var safe_div(var a, var b)
{
if (invalid(a) || invalid(b) || b == 0) return 0;
return a/b;
}
var safe_logratio(var a, var b)
{
if (invalid(a) || invalid(b) || b == 0) return 0;
var r = a/b;
if (invalid(r) || r <= 0) return 0;
return log(r);
}
// NOTE: plot(name, value, color, style)
// We'll pass color first, then style flags (e.g., LINE, NEW, MAIN, etc.)
void plot_safe(string name, var v, int color, int style)
{
if (!invalid(v) && v > -1e-12 && v < 1e12)
plot(name, v, color, style);
}
// ===== Main strategy =====
function run()
{
// Session (file-free; no RULES ? no rule files created/loaded)
StartDate = 2010;
EndDate = 2025;
BarPeriod = BarMins;
LookBack = LOOKBACK;
Capital = 10000;
set(PARAMETERS|PLOTNOW);
Hedge = 0;
asset("EUR/USD");
// Base series
vars PC = series(priceClose()); // Close series for returns/momentum
vars PX = series(price()); // Price series for RSI
// Derived series (allocated once, updated every bar)
static vars R1; if (!R1) R1 = series(0); // 1-bar log return
static vars MOM5; if (!MOM5) MOM5 = series(0); // 5-bar momentum
static vars MOM20; if (!MOM20) MOM20 = series(0); // 20-bar momentum
static vars DP; if (!DP) DP = series(0); // dp proxy = log(D/P)
if (Bar > 0) R1[0] = safe_logratio(PC[0], PC[1]); else R1[0] = 0;
if (Bar > 5) MOM5[0] = safe_logratio(PC[0], PC[5]); else MOM5[0] = 0;
if (Bar > 20) MOM20[0] = safe_logratio(PC[0], PC[20]); else MOM20[0] = 0;
// Constant carry vs price ? dp_t = log(D_t / P_t) (simple demonstration proxy)
var carryDaily = 0.015/252.; // ~1.5% p.a.
if (Bar > 0) DP[0] = safe_logratio(carryDaily, PC[0]); else DP[0] = 0;
// Indicators with warmup guards
var vola = 0; if (Bar >= 21) vola = StdDev(R1, 20);
var atr20 = 0; if (Bar >= 21) atr20 = ATR(20);
var rsi14 = 50; if (Bar >= 15) rsi14 = RSI(PX, 14);
// dp z-score (window Wz)
var zDP = 0; int Wz = 500;
if (Bar >= Wz) {
int i; var mean = 0, s2 = 0;
for (i = 0; i < Wz; i++) mean += DP[i];
mean /= (var)Wz;
for (i = 0; i < Wz; i++) { var d = DP[i] - mean; s2 += d*d; }
var sd = sqrt(max(1e-12, s2/(Wz - 1)));
if (sd > 0) {
zDP = (DP[0] - mean) / sd;
if (zDP > 10) zDP = 10;
if (zDP < -10) zDP = -10;
}
}
if (Bar < LOOKBACK) {
// Anchor price panel early so the chart opens immediately
plot_safe("Close", priceClose(), 0, MAIN|LINE);
return;
}
// Feature vector (?20)
var F[8];
F[0] = zDP;
F[1] = MOM5[0];
F[2] = MOM20[0];
F[3] = vola;
F[4] = rsi14/100.;
F[5] = safe_div(atr20, PC[0]);
F[6] = R1[0];
F[7] = safe_logratio(PC[0], PC[10]);
// Built-in ML (file-free: no RULES)
var predL = adviseLong (PERCEPTRON + BALANCED, 0, F, 8);
var predS = adviseShort(PERCEPTRON + BALANCED, 0, F, 8);
// Trading logic: edge trigger with simple fallback
var edge = predL - predS;
Lots = LotsFixed;
int didTrade = 0;
if (!invalid(edge) && edge > EdgeMin) {
exitShort(); enterLong(); didTrade = 1;
} else if (!invalid(edge) && edge < -EdgeMin) {
exitLong(); enterShort(); didTrade = 1;
}
if (!didTrade) {
if (rsi14 > 55 && MOM5[0] > 0) { exitShort(); enterLong(); didTrade = 1; }
else if (rsi14 < 45 && MOM5[0] < 0) { exitLong(); enterShort(); didTrade = 1; }
else { exitLong(); exitShort(); }
}
// ===== Plots (color, style) =====
// Price on the main chart
plot_safe("Close", priceClose(), 0, MAIN|LINE);
// Open a NEW indicator panel with 'edge', then add other lines to the same panel
plot_safe("edge", edge, 0, NEW|LINE); // NEW opens the panel
plot_safe("predL", predL, 0, LINE);
plot_safe("predS", predS, 0, LINE);
plot_safe("RSI", rsi14, 0, LINE);
plot_safe("z(dp)", zDP, 0, LINE);
}
A walkford version :
Code
// ============================================================================
// BLA03x10_WFO_train_test.c
// EUR/USD — Single-script ML trading with explicit Train/Test separation.
// Zorro / lite-C
//
// • Train run (click “Train”): writes per-cycle RULE files (no TESTNOW).
// • Test/Trade run (click “Test” or “Trade”): loads the RULE files created in Train.
// • Learner: PERCEPTRON + BALANCED
// • WFO supported via NumWFOCycles + DataHorizon (same in Train & Test).
// • Consistent asset() + algo() + advise* signature in both phases.
// • No ternary operator; guarded indicators; compact 8-feature vector.
// ============================================================================
#include <default.c>
// ===== Switches / Knobs =====
#define USE_WFO 1 // 0 = single IS block; 1 = Walk-Forward (IS/OOS cycles)
#define WFO_CYCLES 6 // number of WFO cycles (Train & Test must match)
#define WFO_OOS_BARS 252 // OOS length per cycle in bars (daily ? 1y)
#define LOOKBACK 200 // warmup bars before any model/logic
#define BarMins 1440 // 1D bars
#define LotsFixed 1 // fixed position size
#define EdgeMin 0.05 // edge = predL - predS threshold
// ===== Safe helpers =====
var safe_div(var a,var b){ if(invalid(a)||invalid(b)||b==0) return 0; return a/b; }
var safe_logratio(var a,var b){
if(invalid(a)||invalid(b)||b==0) return 0;
var r=a/b; if(invalid(r)||r<=0) return 0; return log(r);
}
// plot(name, value, color, style)
void plot_safe(string n, var v, int c, int s){ if(!invalid(v)&&v>-1e12&&v<1e12) plot(n,v,c,s); }
// ===== Main =====
function run()
{
// -------- Session (identical in Train and Test except RULES/Hedge flags) --------
StartDate = 2010;
EndDate = 2025;
BarPeriod = BarMins;
LookBack = LOOKBACK;
Capital = 10000;
if(USE_WFO){
NumWFOCycles = WFO_CYCLES;
DataHorizon = WFO_OOS_BARS; // OOS size per cycle
} else {
NumWFOCycles = 1;
DataHorizon = 0;
}
set(PARAMETERS|PLOTNOW); // common flags
// Explicit Train/Test separation (no TESTNOW auto phase switching)
if(Train){
set(RULES); // write RULE files during Train only
Hedge = 2; // generate both L/S samples while training
} else {
Hedge = 0; // Test/Trade loads RULE files, no writing
}
asset("EUR/USD");
algo("dp"); // part of RULE key: <Asset>:<Algo>:<L|S>
// -------- Base series --------
vars PC = series(priceClose()); // close for returns/momentum
vars PX = series(price()); // price for RSI
// -------- Derived series (allocated once, updated every bar) --------
static vars R1; if(!R1) R1 = series(0); // 1-bar log return
static vars MOM5; if(!MOM5) MOM5 = series(0); // 5-bar log momentum
static vars MOM20; if(!MOM20) MOM20 = series(0); // 20-bar log momentum
static vars DP; if(!DP) DP = series(0); // dp proxy = log(D/P)
if(Bar > 0) R1[0] = safe_logratio(PC[0],PC[1]); else R1[0] = 0;
if(Bar > 5) MOM5[0] = safe_logratio(PC[0],PC[5]); else MOM5[0] = 0;
if(Bar > 20) MOM20[0] = safe_logratio(PC[0],PC[20]); else MOM20[0] = 0;
var carryDaily = 0.015/252.;
if(Bar > 0) DP[0] = safe_logratio(carryDaily, PC[0]); else DP[0] = 0;
// -------- Indicators with guards --------
var vola = 0; if(Bar >= 21) vola = StdDev(R1,20);
var atr20 = 0; if(Bar >= 21) atr20 = ATR(20);
var rsi14 = 50; if(Bar >= 15) rsi14 = RSI(PX,14);
// -------- dp z-score (window Wz) --------
var zDP = 0; int Wz = 500;
if(Bar >= Wz){
int i; var mean=0, s2=0;
for(i=0;i<Wz;i++) mean += DP[i];
mean /= (var)Wz;
for(i=0;i<Wz;i++){ var d = DP[i]-mean; s2 += d*d; }
var sd = sqrt(max(1e-12, s2/(Wz-1)));
if(sd > 0){
zDP = (DP[0]-mean)/sd;
if(zDP > 10) zDP = 10;
if(zDP < -10) zDP = -10;
}
}
// Anchor main panel early
if(Bar < LOOKBACK){
plot_safe("Close", priceClose(), BLACK, MAIN|LINE);
return;
}
// -------- Feature vector (? 20) --------
var F[8];
F[0] = zDP;
F[1] = MOM5[0];
F[2] = MOM20[0];
F[3] = vola;
F[4] = rsi14/100.;
F[5] = safe_div(atr20, PC[0]);
F[6] = R1[0];
F[7] = safe_logratio(PC[0], PC[10]);
// -------- Built-in ML calls (IDENTICAL in Train & Test) --------
// Important: same method flags, same slot number, same feature length & order,
// same asset() and algo() ? ensures matching rule keys and file names.
var predL = adviseLong (PERCEPTRON + BALANCED, 0, F, 8);
var predS = adviseShort(PERCEPTRON + BALANCED, 0, F, 8);
// -------- Execution logic (common) --------
var edge = predL - predS;
Lots = LotsFixed;
int didTrade = 0;
if(!invalid(edge) && edge > EdgeMin){ exitShort(); enterLong(); didTrade = 1; }
else if(!invalid(edge) && edge < -EdgeMin){ exitLong(); enterShort(); didTrade = 1; }
if(!didTrade){
if(rsi14 > 55 && MOM5[0] > 0) { exitShort(); enterLong(); didTrade = 1; }
else if(rsi14 < 45 && MOM5[0] < 0) { exitLong(); enterShort(); didTrade = 1; }
else { exitLong(); exitShort(); }
}
// -------- Plots --------
plot_safe("Close", priceClose(), BLACK, MAIN|LINE);
plot_safe("edge", edge, GREEN, NEW|LINE);
plot_safe("predL", predL, BLUE, LINE);
plot_safe("predS", predS, RED, LINE);
plot_safe("RSI", rsi14, 0x404040, LINE);
plot_safe("z(dp)", zDP, 0x800080, LINE);
}