@@ -41,6 +41,8 @@ import {
4141 Line ,
4242 LineChart ,
4343 ReferenceLine ,
44+ Scatter ,
45+ ScatterChart ,
4446 XAxis ,
4547 YAxis ,
4648} from 'recharts'
@@ -93,6 +95,20 @@ function ReportGame() {
9395 fetchPolicy : 'cache-first' ,
9496 } )
9597
98+ const {
99+ data : periodEndResults ,
100+ loading : periodEndResultsLoading ,
101+ error : periodEndResultsError ,
102+ } = useQuery ( SpecificResultsDocument , {
103+ variables : {
104+ gameId : Number ( router . query . id ) ,
105+ type : 'PERIOD_END' ,
106+ } ,
107+ // pollInterval: 15000,
108+ skip : ! router . query . id ,
109+ fetchPolicy : 'cache-first' ,
110+ } )
111+
96112 const memoizedData = useMemo ( ( ) => {
97113 if (
98114 loading ||
@@ -171,10 +187,11 @@ function ReportGame() {
171187 decisions [ v ] = Number ( result . facts . decisions [ v ] )
172188 } )
173189
174- const totalAssetsTmp = result . facts . assetsWithReturns
190+ const assetsWithReturns = result . facts . assetsWithReturns ?? [ ]
191+ const totalAssetsTmp = assetsWithReturns
175192 . filter ( ( _ , ix ) => ix > 0 )
176193 . map ( ( a ) => a . totalAssets )
177- const accTotalAssetsReturnTmp = result . facts . assetsWithReturns
194+ const accTotalAssetsReturnTmp = assetsWithReturns
178195 . filter ( ( _ , ix ) => ix > 0 )
179196 . map ( ( a ) => a . accTotalAssetsReturn )
180197
@@ -184,13 +201,22 @@ function ReportGame() {
184201 name : result . player . name ,
185202 totalAssets : [ ...totalAssetsTmp ] ,
186203 accTotalAssetsReturn : [ ...accTotalAssetsReturnTmp ] ,
204+ risk : result . facts . risk ,
205+ totalAssetsReturnsPA : result . facts . totalAssetsReturnsPA ,
187206 }
188207 } else {
189208 dataPerPlayer [ result . player . id ] . decisions . push ( decisions )
190209 dataPerPlayer [ result . player . id ] . totalAssets . push ( ...totalAssetsTmp )
191210 dataPerPlayer [ result . player . id ] . accTotalAssetsReturn . push (
192211 ...accTotalAssetsReturnTmp
193212 )
213+ if ( result . facts . risk ) {
214+ dataPerPlayer [ result . player . id ] . risk = result . facts . risk
215+ }
216+ if ( result . facts . totalAssetsReturnsPA ) {
217+ dataPerPlayer [ result . player . id ] . totalAssetsReturnsPA =
218+ result . facts . totalAssetsReturnsPA
219+ }
194220 }
195221 } )
196222 output . push ( dataPerPlayer )
@@ -316,6 +342,7 @@ function ReportGame() {
316342 dataTotalAssets,
317343 dataAccTotalAssetsReturn,
318344 segmentResultPerSegmentAvg,
345+ playerConfig,
319346 }
320347 } , [
321348 data ,
@@ -326,7 +353,68 @@ function ReportGame() {
326353 segmentEndResultsError ,
327354 ] )
328355
329- if ( loading || segmentEndResultsLoading || ! memoizedData ) {
356+ const memoizedDataPeriod = useMemo ( ( ) => {
357+ if (
358+ periodEndResultsLoading ||
359+ periodEndResultsError ||
360+ ! periodEndResults ?. specificResults
361+ ) {
362+ return null
363+ }
364+
365+ const previousPeriodResults = periodEndResults . specificResults
366+
367+ console . log ( 'previousPeriodResults' , previousPeriodResults )
368+
369+ const riskReturnPerPeriod = previousPeriodResults . reduce ( ( acc , result ) => {
370+ if ( ! acc [ result . period . index ] ) {
371+ acc [ result . period . index ] = {
372+ [ result . player . id ] : {
373+ name : result . player . name ,
374+ risk : result . facts . risk ,
375+ totalAssetsReturnsPA : result . facts . totalAssetsReturnsPA ,
376+ } ,
377+ }
378+ } else {
379+ acc [ result . period . index ] [ result . player . id ] = {
380+ name : result . player . name ,
381+ risk : result . facts . risk ,
382+ totalAssetsReturnsPA : result . facts . totalAssetsReturnsPA ,
383+ }
384+ }
385+ return acc
386+ } , [ ] )
387+
388+ const sharpeRatioPerPeriod = previousPeriodResults . reduce ( ( acc , result ) => {
389+ acc [ result . period . index ] = {
390+ ...acc [ result . period . index ] ,
391+ period : result . period . index + 1 ,
392+ [ result . player . name + '-sharpeRatio' ] : result . facts . sharpeRatio ,
393+ }
394+ return acc
395+ } , [ ] )
396+
397+ const configSharpeRatio = Object . keys (
398+ sharpeRatioPerPeriod ?. [ 0 ] ?? { }
399+ ) . reduce ( ( acc , item ) => {
400+ if ( item . endsWith ( 'sharpeRatio' ) ) {
401+ acc [ item ] = {
402+ label : item . replace ( '-sharpeRatio' , '' ) ,
403+ }
404+ }
405+ return acc
406+ } , { } )
407+
408+ return { riskReturnPerPeriod, sharpeRatioPerPeriod, configSharpeRatio }
409+ } , [ periodEndResults , periodEndResultsLoading , periodEndResultsError ] )
410+
411+ if (
412+ loading ||
413+ segmentEndResultsLoading ||
414+ periodEndResultsLoading ||
415+ ! memoizedDataPeriod ||
416+ ! memoizedData
417+ ) {
330418 return < div > loading...</ div >
331419 }
332420
@@ -336,6 +424,9 @@ function ReportGame() {
336424 if ( segmentEndResultsError ) {
337425 return < div > { segmentEndResultsError . message } </ div >
338426 }
427+ if ( periodEndResultsError ) {
428+ return < div > { periodEndResultsError . message } </ div >
429+ }
339430
340431 const {
341432 game,
@@ -346,6 +437,7 @@ function ReportGame() {
346437 dataTotalAssets,
347438 dataAccTotalAssetsReturn,
348439 segmentResultPerSegmentAvg,
440+ playerConfig,
349441 } = memoizedData
350442
351443 const decisionKeys = [ 'bank' , 'bonds' , 'stocks' ]
@@ -361,6 +453,9 @@ function ReportGame() {
361453 }
362454 } )
363455
456+ const { riskReturnPerPeriod, sharpeRatioPerPeriod, configSharpeRatio } =
457+ memoizedDataPeriod
458+
364459 return (
365460 < div className = "container mx-auto p-4" >
366461 < div className = "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-2" >
@@ -682,6 +777,115 @@ function ReportGame() {
682777 </ ChartContainer >
683778 </ CardContent >
684779 </ Card >
780+
781+ < Card className = "flex h-full w-full flex-col" >
782+ < CardHeader >
783+ < CardTitle > Risk-Return </ CardTitle >
784+ < CardDescription >
785+ Risk-Return chart of the latest completed period.
786+ </ CardDescription >
787+ </ CardHeader >
788+ < CardContent className = "flex-grow" >
789+ < ChartContainer config = { playerConfig } className = "h-[300px] w-full" >
790+ < ScatterChart >
791+ < ChartTooltip
792+ cursor = { false }
793+ content = { < ChartTooltipContent /> }
794+ formatter = { ( value , name , item ) => [
795+ < div
796+ key = { name }
797+ className = "flex w-full items-center justify-between gap-x-2"
798+ >
799+ < div className = "flex items-center gap-x-1" >
800+ < div
801+ className = "h-[8px] w-[8px] rounded-sm"
802+ style = { { background : item . color } }
803+ />
804+ < span className = "text-xs text-gray-600" > { name } </ span >
805+ </ div >
806+ < span className = "font-bold text-black" >
807+ { ( value * 100 ) . toFixed ( 2 ) } %
808+ </ span >
809+ </ div > ,
810+ ] }
811+ />
812+ < CartesianGrid />
813+ < XAxis
814+ dataKey = "risk"
815+ tickLine = { false }
816+ tickMargin = { 8 }
817+ type = "number"
818+ name = "Risk"
819+ tickFormatter = { ( v ) => `${ ( v * 100 ) . toFixed ( 2 ) } %` }
820+ />
821+ < YAxis
822+ dataKey = "totalAssetsReturnsPA"
823+ type = "number"
824+ name = "Returns p.a."
825+ tickLine = { false }
826+ tickMargin = { 8 }
827+ tickFormatter = { ( v ) => `${ ( v * 100 ) . toFixed ( 2 ) } %` }
828+ />
829+ { Object . values (
830+ riskReturnPerPeriod ?. [ riskReturnPerPeriod . length - 1 ] ?? { }
831+ ) . map ( ( playerData , ix ) => {
832+ return (
833+ < Scatter
834+ key = { playerData . name }
835+ name = { playerData . name }
836+ data = { [ playerData ] }
837+ fill = { colors [ ix ] }
838+ />
839+ )
840+ } ) }
841+ < ChartLegend content = { < ChartLegendContent /> } />
842+ </ ScatterChart >
843+ </ ChartContainer >
844+ </ CardContent >
845+ </ Card >
846+
847+ < Card className = "flex h-full w-full flex-col" >
848+ < CardHeader >
849+ < CardTitle > Sharpe Ratio</ CardTitle >
850+ { /* <CardDescription>Average decisions over players.</CardDescription> */ }
851+ </ CardHeader >
852+ < CardContent className = "flex-grow" >
853+ < ChartContainer
854+ config = { configSharpeRatio }
855+ className = "h-[300px] w-full"
856+ >
857+ < BarChart data = { sharpeRatioPerPeriod } >
858+ { Object . keys ( configSharpeRatio ) . map ( ( key , ix ) => {
859+ return (
860+ < Bar key = { key } dataKey = { key } fill = { colors [ ix ] } radius = { 4 } >
861+ < LabelList
862+ position = "top"
863+ className = "fill-foreground"
864+ fontSize = { 12 }
865+ formatter = { ( v ) => `${ v . toFixed ( 2 ) } ` }
866+ />
867+ </ Bar >
868+ )
869+ } ) }
870+ < CartesianGrid vertical = { false } />
871+ < XAxis
872+ dataKey = "period"
873+ tickLine = { false }
874+ axisLine = { false }
875+ tickMargin = { 8 }
876+ tickFormatter = { ( v ) => `P${ v } ` }
877+ />
878+ < YAxis
879+ tickLine = { false }
880+ axisLine = { false }
881+ tickMargin = { 8 }
882+ tickFormatter = { ( v ) => `${ v . toFixed ( 2 ) } ` }
883+ />
884+ < ChartLegend content = { < ChartLegendContent /> } />
885+ </ BarChart >
886+ </ ChartContainer >
887+ </ CardContent >
888+ </ Card >
685889 </ div >
686890 </ div >
687891 )
0 commit comments