Skip to content

Instantly share code, notes, and snippets.

@alexchinco
Last active December 20, 2015 01:09
Show Gist options
  • Save alexchinco/6047268 to your computer and use it in GitHub Desktop.
Save alexchinco/6047268 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width">
<head>
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
<style type="text/css">
svg { font: 12px serif; }
.axis { font: 10px serif; }
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke: black;
stroke-width: 2px;
}
.plot1 path {
fill: none;
stroke: black;
opacity: 0.05;
stroke-width: 1px;
}
.plot1 path:hover {
fill: none;
stroke: steelblue;
opacity: 1;
stroke-width: 2.5px;
z-index: 5000;
}
.positXF path {
fill: none;
stroke: none;
opacity: none;
stroke-width: none;
}
.positXC path {
fill: none;
stroke: none;
opacity: none;
stroke-width: none;
}
.overlay {
fill: none;
pointer-events: all;
}
.area2 {
fill: black;
opacity: 0.75;
stroke: none;
}
</style>
</head>
<body>
<title>Momentum Portfolio Composition</title>
<div id="figContainer"></div>
<script type="text/javascript">
//// Define global parameters
var TM1 = 10;
var RM1 = 10;
var BM1 = 75;
var LM1 = 30;
var TM2 = 350;
var RM2 = 10;
var BM2 = 20;
var LM2 = 30;
var W1 = 700 - RM1 - LM1;
var H1 = 400 - TM1 - BM1;
var W2 = 700 - RM2 - LM2;
var H2 = 400 - TM2 - BM2;
//// Define formatting functions
var parseDate = d3.time.format("%Y-%m").parse;
var bisectDate = d3.bisector(function(d) { return d.date; }).left;
var formatValue = d3.format(",.2f");
var formatReturn = function(d) { return formatValue(d) + "%/mo"; };
//// Define scales
var scaleW1 = d3.time.scale().range([0, W1]);
var scaleH1 = d3.scale.linear().range([H1, 0]);
var scaleW2 = d3.time.scale().range([0, W2]);
var scaleH2 = d3.scale.linear().range([H2, 0]);
/*---------------------------------------------------------------------------*/
//// Define tooltop
var toolTip = d3.select("#figContainer").append("div").style("position", "absolute").style("z-index", "10").style("visibility", "hidden");
/*---------------------------------------------------------------------------*/
//// Define canvas segments
var canvas = d3.select("#figContainer").append("svg").attr("id", "canvas").attr("width", W1 + LM1 + RM1).attr("height", H1 + TM1 + BM1);
var focus = canvas.append("g").attr("id", "focus").attr("transform", "translate(" + LM1 + "," + TM1 + ")");
var context = canvas.append("g").attr("id", "context").attr("transform", "translate(" + LM2 + "," + TM2 + ")");
/*---------------------------------------------------------------------------*/
//// Define axes
var drawXAxis1 = d3.svg.axis().scale(scaleW1).orient("bottom");
focus.append("svg:g").attr("class", "x axis");
focus.append("svg:g").append("text").text("Constituent").attr("class", "label").attr("y", 302.5).attr("dx", ".50em").style("text-anchor", "start");
focus.append("svg:g").append("text").text("Returns").attr("class", "label").attr("y", 312.5).attr("dx", ".50em").style("text-anchor", "start");
var drawYAxis1 = d3.svg.axis().scale(scaleH1).orient("left");
focus.append("svg:g").attr("class", "y axis");
focus.append("svg:g").append("text").text("%/mo").attr("class", "label").attr("transform", "rotate(-90)").attr("y", 4).attr("dy", ".75em").style("text-anchor", "end");
var drawXAxis2 = d3.svg.axis().scale(scaleW2).orient("bottom");
context.append("svg:g").attr("class", "x axis");
context.append("svg:g").append("text").text("Portfolio").attr("class", "label").attr("y", 17.5).attr("dx", ".50em").style("text-anchor", "start");
context.append("svg:g").append("text").text("Returns").attr("class", "label").attr("y", 27.5).attr("dx", ".50em").style("text-anchor", "start");
/*---------------------------------------------------------------------------*/
//// Draw bar to select portfolio inspection month
var selectPermno = d3.set([]);
var positXC = context.append("g").attr("class", "positXC").style("display", "none");
positXC.append("path").attr("class", "dateX").style("stroke-width", 5);
positXC.append("text").attr("x", 9);
var positXF = focus.append("g").attr("class", "positXF").style("display", "none");
positXF.append("path").attr("class", "dateX").style("stroke-width", 5);
/*---------------------------------------------------------------------------*/
var loadingMsg = focus.append("svg:g").append("text").attr("class", "loadingMsg").text("LOADING...").attr("x", 175).attr("y", 200).style("font", "54px Courier New").style("opacity", 0.25);
var instructions = focus.append("svg:g").attr("class", "instructions");
/*---------------------------------------------------------------------------*/
var drawArea = d3.svg.area().x(function(d) { return scaleW2(d.date); }).y1(function(d) { return scaleH2(d.mret); }).y0(function(d) { return scaleH2(0); });
var area2 = context.append("svg:g").append("path").attr("class", "area2");
/*---------------------------------------------------------------------------*/
//// Update which position (short or long) to plot
var chooseLongPosition = focus.append("svg:g").attr("class", "longButton").attr("width", 100).attr("height", 20)
.append("text").attr("x", 550).attr("y", 50).text("Long Position")
.on("click", changeToLong);
var chooseShortPosition = focus.append("svg:g").attr("class", "shortButton").attr("width", 100).attr("height", 20)
.append("text").attr("x", 550).attr("y", 75).text("Short Position")
.on("click", changeToShort);
//// Pick which position to plot first
var positChoice = "long";
/*---------------------------------------------------------------------------*/
//// Load datasets
var nameDict, folioReturn, firmReturn, positDict;
d3.csv("jegadeesh-and-titman-permno-monthly-returns-d3.csv", function(csv1) {
d3.csv("jegadeesh-and-titman-permno-name-dict-d3.csv", function(csv2) {
d3.csv("jegadeesh-and-titman-momentum-monthly-returns-d3.csv", function(csv3) {
d3.csv("jegadeesh-and-titman-momentum-monthly-long-position-d3.csv", function(csv4) {
d3.csv("jegadeesh-and-titman-momentum-monthly-short-position-d3.csv", function(csv5) {
firmReturn = csv1.map(function(d) {
return {
permno: d.permno,
date: parseDate(d.date),
ret: d.ret != "NA" ? 100 * +d.ret : null
};
});
firmReturn = d3.nest()
.key(function(d) {
return d.permno;
})
.entries(firmReturn);
nameDict = d3.nest().key(function(d) { return d.permno; }).map(csv2);
csv3.forEach(function(d) {
d.date = parseDate(d.date);
d.mret = 100 * +d.mret;
});
folioReturn = csv3;
longPositDict = d3.nest().key(function(d) { return parseDate(d.date); }).map(csv4);
shortPositDict = d3.nest().key(function(d) { return parseDate(d.date); }).map(csv5);
redraw();
});
});
});
});
});
/*---------------------------------------------------------------------------*/
//// Draw figure
function redraw() {
//// Remove loading message and display instructions
loadingMsg.style("display", "none");
instructions.append("g").append("text").text("Mouse over momentum portfolio return time series at bottom to display").attr("x", 175).attr("y", 180).style("opacity", 0.75);
instructions.append("g").append("text").text("returns for each of the stocks in the long or short leg of the strategy").attr("x", 175).attr("y", 190).style("opacity", 0.75);
instructions.append("g").append("text").text("in a particular month. Click on long position and short position buttons").attr("x", 175).attr("y", 200).style("opacity", 0.75);
instructions.append("g").append("text").text("in the upper right hand corner to toggle between legs of the strategy.").attr("x", 175).attr("y", 210).style("opacity", 0.75);
//// Define scale domains
scaleW1.domain([
d3.min(firmReturn, function(k) { return d3.min(k.values, function(v) { return v.date; }); }),
d3.max(firmReturn, function(k) { return d3.max(k.values, function(v) { return v.date; }); })
]);
scaleH1.domain([
d3.min(firmReturn, function(k) { return d3.min(k.values, function(v) { return v.ret; }); }),
d3.max(firmReturn, function(k) { return d3.max(k.values, function(v) { return v.ret; }); })
]);
scaleW2.domain(scaleW1.domain());
scaleH2.domain(d3.extent(folioReturn, function(d) { return d.mret; }));
//// Draw portfolio returns
area2.datum(folioReturn).attr("d", drawArea).call(drawArea);
//// Define behavior in response to moving the bar
context.append("rect").attr("class", "overlay").attr("width", W2).attr("height", H2)
.on("mouseover", function() { positXC.style("display", null); positXF.style("display", null); instructions.style("display", "none"); })
.on("mouseout", function() { positXC.style("display", null); positXF.style("display", null); selectPermno = d3.set([]); })
.on("mousemove", mousemove)
.on("click", function() { selectPermno = d3.set([]); instructions.style("display", "none"); });
//// Plot returns of stocks in either leg of the momentum portfolio
function mousemove() {
// Choose portfolio position
if (positChoice == "long") {
positDict = longPositDict;
} else {
positDict = shortPositDict;
}
// Look up location of drag bar
var x0 = scaleW2.invert(d3.mouse(this)[0]);
var i = bisectDate(folioReturn, x0, 1);
var d0 = folioReturn[i - 1];
var d1 = folioReturn[i];
var d = x0 - d0.date > d1.date - x0 ? d1 : d0;
var pB1 = [scaleW1(d.date), scaleH1(-50)];
var pT1 = [scaleW1(d.date), scaleH1(400)];
var pB2 = [scaleW2(d.date), scaleH2(-30)];
var pT2 = [scaleW2(d.date), scaleH2(30)];
// Draw drag bars
positXF.select("path").attr("d", "M" + pB1 + "L" + pT1)
.attr("class", "dateX")
.style("opacity", "0.75")
.style("stroke-width", "5px")
.style("stroke", function(d) { if (positChoice == "long") { return "green"; } else { return "red"; } });
positXC.select("path").attr("d", "M" + pB2 + "L" + pT2)
.attr("class", "dateX")
.style("opacity", "0.75")
.style("stroke-width", "5px")
.style("stroke", function(d) { if (positChoice == "long") { return "green"; } else { return "red"; } });
positXC.select("text").text(formatReturn(d.mret));
positXC.select("text").attr("transform", "translate(" + scaleW1(d.date) + "," + scaleH2(-30) + ")");
// Look up portfolio position at time of drag bar
var positPermno = d3.values(positDict[d.date]);
selectPermno = d3.set([]);
d3.entries(positPermno).forEach(function(d) { selectPermno.add(d.value.permno); });
// Collect all of the return data for these stocks
var plotData = [];
function createPlotData(d) {
var temp = [];
for (var i = 0; i < d.length; i++) {
if (selectPermno.has(d[i].key)) {
temp.push(d[i]);
}
}
return temp;
}
plotData = createPlotData(firmReturn);
// Draw a line in the focus canvas for each of these stocks
var drawLine1 = d3.svg.line().defined(function(d) { return d.ret != null; }).x(function(d) { return scaleW1(d.date); }).y(function(d) { return scaleH1(d.ret); });
var plot1 = focus.selectAll(".plot1").data(plotData);
var plotEnter1 = plot1.enter().append("g").attr("class", "plot1")
.append("path").attr("class", "drawLine1").attr("id", function(d) { return d.key; }).attr("d", function(d) { return drawLine1(d.values); }).style("position", "relative")
.on("mouseover", function(d) { return toolTip.style("visibility", "visible").text(d3.values(nameDict[d.key][0])[1] + " (" + d3.values(nameDict[d.key][0])[0] + ")"); })
.on("mousemove", function() { return toolTip.style("left", 100+"px").style("top", 50+"px"); })
.on("mouseout", function() { return toolTip.style("visibility", "hidden"); });
plot1.exit().remove();
var plotUpdate1 = d3.transition(plot1)
plotUpdate1.select("path").attr("d", function(d) { return drawLine1(d.values); });
} // End mousemove()
//// Draw boundary of focus canvas
var focusUpdate = d3.transition(focus);
focusUpdate.select(".x.axis").attr("transform", "translate(0," + H1 + ")").call(drawXAxis1);
focusUpdate.select(".y.axis").call(drawYAxis1);
focusUpdate.select(".longButton").style("fill", function() { if (positChoice == "long") { return "green"; } else { return null; } })
.style("opacity", function() { if (positChoice == "long") { return 1; } else { return 0.50; } });
focusUpdate.select(".shortButton").style("fill", function() { if (positChoice == "short") { return "red"; } else { return null; } })
.style("opacity", function() { if (positChoice == "short") { return 1; } else { return 0.50; } });
focusUpdate.select(".dateX").style("stroke", function() { if (positChoice == "short") { return "red"; } else { return "green"; } });
//// Draw boundary and values in context canvas
var contextUpdate = d3.transition(context);
contextUpdate.select(".x.axis").attr("transform", "translate(0," + H2 + ")").call(drawXAxis2);
contextUpdate.select(".dateX").style("stroke", function() { if (positChoice == "short") { return "red"; } else { return "green"; } });
} // End redraw()
/*---------------------------------------------------------------------------*/
function changeToLong(){
positChoice = "long";
instructions.style("display", "none");
selectPermno = d3.set([]);
d3.transition().duration(50).each(redraw);
}
function changeToShort(){
positChoice = "short";
instructions.style("display", "none");
selectPermno = d3.set([]);
d3.transition().duration(50).each(redraw);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment