Last active
December 20, 2015 01:09
-
-
Save alexchinco/6047268 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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