feat(calibration): finalize Step 2 Risk & Stops with inline PDF reports and visual validation
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -21,3 +21,9 @@ logs/
|
|||||||
# Resultados
|
# Resultados
|
||||||
backtest_results/
|
backtest_results/
|
||||||
output/
|
output/
|
||||||
|
|
||||||
|
# Ignorar SOLO la reports de la raíz
|
||||||
|
/reports/
|
||||||
|
|
||||||
|
# Pero permitir reports dentro de src
|
||||||
|
!src/calibration/reports
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,93 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/F1 2 0 R /F2 3 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<<
|
|
||||||
/Author (\(anonymous\)) /CreationDate (D:20260212210317+01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260212210317+01'00') /Producer (ReportLab PDF Library - \(opensource\))
|
|
||||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
9 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1380
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatU3gJ[&k&:Ml+oF"lkWPV33AD+U)@:uT`QWc#5H(p0/M#n`]37uM1m+6/K<fCO9JO#)pBsh-bh5=I/$3WTCISZ:1-SZThV*Gp:F.h=K&H;i^`hUS:RgI>-j98;=;X-qp`]R!B"VuDJ%lgR?@2ropQE9N4,V@rC"VN.sOkaQK8CA5Xl?/OUq74)c5X.R4'B7?XoEB./@N2,-?5Q-T_CX*]$9C+-aiG-X%.Rsm"3(b`UTY*)"\e81]7Y+Tp/crS""VEY8cA^>TI=+/><ms*p+<p(5S6urRo?t[I1'Z]-(&ral[*)hDr4t#9s6CJeMC%@0egIr6c'=QP*'86+&`L'j2m>1RThS_$WRc6K;a$O$N8$tWmIK7*_b!^Tmml,>6hIi+[UL+O.HcIW9s%B!KeO\jqA?/E1,Y"6NW%!+O`%E]S!]X[U!>H^o?GTear>i?.:"KP\=0uS]$_emoQE,]4(ca0:A@^ofQ/Gh/1MQo(t%<_TmY!:=IIf!<'E!MW\kuV)BXpkE24%EU@-.s)[6-6DK&to6L-@Vr2:TaeeYV%Mj"pA]0GHNhiOM:;0op#>U#+XUHFRb&C2"W*;r&(14GqL!VHL$;'u&.^Mg>-[2%8kPP$(K^@eN%n-_\+o[=sj<#-O.%]$RN,b;daLWe[88;qi>%^Z>J^n^&5.LSKaDBjm!E,PhhlolI`7@FhpXBZWXLkm-cD7)r`qh5b:UB\-ctKtAI>$jJ3@<`nM!:0sSa!P)D-?IZp,dmYm,pY^qmjHpqgGY1bC8BC2qspLq.A#Y[dadp9`mV;p.YXU"fg592$B]^?Ltj:Gjt1ZHaDXQ7(QMtWRP(t)G);mM526%qp.sXXpQr6#M:`1eI6Is6`0O>#"ai,jViX(/i5[:`B=q0)DL*L0@%;qA=V!>AK:I&<#7#e@)k%5f@,Mf(BWj!)Pcu>aU=Nt!e]9Q\dfl-+6X93Zo#+N4)0qWZsGJsVT/O+m7c\'2RT)%Yq5Pp9K1`$-&_0q5ZCC"P+f.i;`R:In@2?8?5l0]NV"m3fJJZ.[_p*uhl'm!<]($-XltC>Wq78tG.V"]H8flUI2b0]HgK%@]MPZ.L..OpjN%mtV^mK#kWc?Cj?K3kbY`^H35`"H3P!P?\J:B;9(P,@hH/^2D5iE6@#:p`l6WUAY3jU6^u6YGaV;-WQsU3Fdg97D.#g9+/i;5Tjrup7Xb5(S*DA*G319IU.a$ML16f@B<F/F3n>8g3T(R_a/!Wcs1L4P5FKgnGPCM'9*EF&)NY/iD@s=A8e`%%OV!lFh@%`uOO,V>]A>\824pE&=3t2O001(#1mEG4.f4:k@0p;halQ/cj)Vq/#F`ri-jK!D]JW-[u5Xs<u+<u5GpF,qUqH]%foKT.N!(6~>endstream
|
|
||||||
endobj
|
|
||||||
10 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 522
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatUpa\K`-&;KY%MRaiEKT,<:9.KVOFooUFA#7>T:1ZuVi.UdfEC.U@3^l>sEgX,os(6_,#[m[pp:j]3*2[[o8q9`/1E\E0+[(IAFS%9P19sIsLj*h\-!c@p;A9d1J4^Np4SD:r0M\0BRUQGU[Y>[]!fg5tDOmee/`9g!eogLU[F.<a+h`MMY:4n]7DESiO9A.C8<<!K\4sK`G5r%k,C+6#4aG4()p0m*E,DV%\1qnRM::a=BqL;*>//Utpf.-]@VdP#r;cA?`%;735q["cg:f*/VI;A@9<Id</kgi_7n!(A0G<H'P(>aYl35<P9N.V]%-(ZWL]p@`1m$cDbOhMe:\p"401-mS&-[uAUU]I79E?AmGBtu_'Z/cP8BBiY[V`b)!l7TJ9/s4GAlauK><:?C13qCTb/;g#_:I/?V4N4;?*A;$.6cC`aq%pP3ONPsT>6.4F>$#6-C/K*/XmW*ht'`-A![jI;P-(8_2Z=Md#HJpY93AiPe#1Z!YO^G]1dV0+u"<JK'?CC~>endstream
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 11
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000061 00000 n
|
|
||||||
0000000102 00000 n
|
|
||||||
0000000209 00000 n
|
|
||||||
0000000321 00000 n
|
|
||||||
0000000524 00000 n
|
|
||||||
0000000728 00000 n
|
|
||||||
0000000796 00000 n
|
|
||||||
0000001076 00000 n
|
|
||||||
0000001141 00000 n
|
|
||||||
0000002612 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/ID
|
|
||||||
[<b164637c0884769535c99f7ca3ce39d9><b164637c0884769535c99f7ca3ce39d9>]
|
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
|
||||||
|
|
||||||
/Info 7 0 R
|
|
||||||
/Root 6 0 R
|
|
||||||
/Size 11
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
3225
|
|
||||||
%%EOF
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,93 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/F1 2 0 R /F2 3 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 9 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 10 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 8 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/PageMode /UseNone /Pages 8 0 R /Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<<
|
|
||||||
/Author (\(anonymous\)) /CreationDate (D:20260212210640+01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260212210640+01'00') /Producer (ReportLab PDF Library - \(opensource\))
|
|
||||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/Count 2 /Kids [ 4 0 R 5 0 R ] /Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
9 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1380
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatU3gJ[&k&:Ml+oF"lkWPV33j9olnfU8bqV3[$PSuAB:UO\+F%^n/2p?T16.un+eJO#)pBsh-bh5=I/$3WTCI)cVFZNs=?;P4Gm),Ta?M#MriUXA\Sc3/G;F?D'D.CCK^@h,D#"Vs'MpGf0(?lW`uROfQ=/1oeGGs4p_4LrdKOebX[<i\'`nk8eOJ:<+6-UaT7kptP2B,c6UGCgqG&8$cR<m\D6aU]C(q>pSSVSJS=GOh""21UPZA.LB^gA[sG.q%K63m"#P%htk@Hjf._cb=t^"=V+o?uq4WZ#@JF^BE"=@=1k0n')f1^T"%rZt]rR3!Dd=G>-XD:,6i^]MhS`HSjDs>'"KAG+nInG7YKB;5#)B5nt59e:C3&/Ca-_Dem[;GDP`/cUCWU9[rc[6BCmBLZT6^/TI3:^]OYYL`fVMqn.]nl*c"LHGHN=im#[+5+U:Jf9BrFWLZJAPOCHn;7iP-bUo6R/CZ57$e9+@C@]Ct;o6b.M;r]k/3lA%PN&Fn>cCF(r]-dOVHs'8rr3ruK$_l>d!T/L_^X5S4h38\`5*F2N/UjNfosobb/m`N>gLQ3Ya&XM8f8+2)7>CeDXg^7>mTh,Wpo8(9P&sXZfGP#k'S7XL%'Hbaog%B@YfLX/GY&)"cAXH%qD]</@$nN=H]*k[8IkV=C;m!NfS'B%5OD4@XurEni*Yu4>HL@fc?QuJ9AI>e!]hA?!Eq5E\gEKK1=$!lm#3uR:RQPL6u;He<sWIf9S;_B>6=V_LCoqZCeaaZJTfIZg7o<HIM8LQ`oScAK/tbhDB2hnBa"r=WiWF?:-uXi&0D-a+JmA5*uMW,LXhg8u9gbejrtQ9-i>dh1'ann#@KtI=MX!>YZHo2'f>k1e;X$/6;Q+I]d(B'TZ'Q._pSX^,Wa!PAr3:P]>K0F&g:lNZH=(;8l=V0*K^!2+Hn[P7oXB$Oh(9Bfh!0I%WjrI7#dm\YCL.9'[4ECMM(qe1O^l@oM,b'$F1V_fWAI?S_(f!(=5*0,Y[T>Es9:$I<LemBmPEcq++uo6'Y,CccI?p7;,8@?j1km>=)`V+T&WA@B\CZ?IOogUE;F0ur]=aU>Y<(!P/!.7]-64C(SO8,nF2-i^14DMHl__&89Xmc6jLqGpRI.F%<IH!cC9\"tQ"K-dZuWL(-K]!aut",EMQ,u`;O@;[OD:Fg7\UGV>M\PVbARu;70[1:KkFO7RWj!=%E<0Xk!ALOsOWk=kuhO>u0HIha!X].3p03"&[nR*Su-i^ce40142&gMK[`pP[?jnoS$V!Ig7d6KX9)/>Y1A?+M55'd\L3XlF301:/3mEF(Sf4:k@1%UGieii:X27c:0lKfS8a]Ie%!bkdnJ[DMH5[*m,mSUJUqH](goKSS3!(-~>endstream
|
|
||||||
endobj
|
|
||||||
10 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 522
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatUpa\K`-&;KY%MRaiEKT,<:9.KVOFooUFA#7>T:1ZuVi.UdfEC.U@3^l>sEgX,os(6_,#[m[pp:j]3*2[[o8q9`/1E\E0+[(IAFS%9P19sIsLj*h\-!c@p;A9d1J4^Np4SD:r0M\0BRUQGU[Y>[]!fg5tDOmee/`9g!eogLU[F.<a+h`MMY:4n]7DESiO9A.C8<<!K\4sK`G5r%k,C+6#4aG4()p0m*E,DV%\1qnRM::a=BqL;*>//Utpf.-]@VdP#r;cA?`%;735q["cg:f*/VI;A@9<Id</kgi_7n!(A0G<H'P(>aYl35<P9N.V]%-(ZWL]p@`1m$cDbOhMe:\p"401-mS&-[uAUU]I79E?AmGBtu_'Z/cP8BBiY[V`b)!l7TJ9/s4GAlauK><:?C13qCTb/;g#_:I/?V4N4;?*A;$.6cC`aq%pP3ONPsT>6.4F>$#6-C/K*/XmW*ht'`-A![jI;P-(8_2Z=Md#HJpY93AiPe#1Z!YO^G]1dV0+u"<JK'?CC~>endstream
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 11
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000061 00000 n
|
|
||||||
0000000102 00000 n
|
|
||||||
0000000209 00000 n
|
|
||||||
0000000321 00000 n
|
|
||||||
0000000524 00000 n
|
|
||||||
0000000728 00000 n
|
|
||||||
0000000796 00000 n
|
|
||||||
0000001076 00000 n
|
|
||||||
0000001141 00000 n
|
|
||||||
0000002612 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/ID
|
|
||||||
[<fc42dcfa3952c93b73ce887dcde189a4><fc42dcfa3952c93b73ce887dcde189a4>]
|
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
|
||||||
|
|
||||||
/Info 7 0 R
|
|
||||||
/Root 6 0 R
|
|
||||||
/Size 11
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
3225
|
|
||||||
%%EOF
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,74 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/F1 2 0 R /F2 3 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Author (\(anonymous\)) /CreationDate (D:20260211220412+01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260211220412+01'00') /Producer (ReportLab PDF Library - \(opensource\))
|
|
||||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<<
|
|
||||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 854
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatUr9lo#B&;KZOMS6Aa14pgnP&0=4cmCRIQqi7^;_AOqM,T?R>7BKJG:aa`Ld.aN%p5oVf<9?>#q-@tW;2s]!*:=K>Z7:(_?Ed(E!X4rQZ2RXU5^O5i-CpSqgje?3.W5pT*>0ln,c)_Sr[6r(`9;O>[0(XI[-.bX*B6d1g25e7AQ,`"a;HB$4RM>H8*et#?HoSP4CPZ6rTD<B+VdBGRfpir$^tKY=!k-lieG+ES&XQ'6CYh<\;1QJXA3['o[(\8tfE'6_lAd&LJlY9dgGg]d3N=1<5YBE%0Rte"+uhEAZkl-iIFL*3[=i3Ooab)fa>CV:D('H`m"pMrHH]\l_\KW:b*WOMp`N*8aW=$GuuO/dP/?nV)S6q172Yd\.Rp#nf5"ehE\4e&Q9=8[b-+ft1ODdu4+VkHNc>!cs?*R&"Io7&fHHfQcuMY#N9^J""]ioh'j_>pjsQSZ_[`"CDYDgoAeO`V7Qs&VL@O_X[Tm#,bk8X(5s[M1[+uN/]Y@EaZ.WlH+6iY&%o6f\*Jr[1*AKNKH93\i7I7mr?/aeqL)bhW^C[P,EH9l2+j'_X3&Bqm8`-&:>\#iPQ<4oJZ0SV=BLddD\aGW&^Yo<Ic]r=M_oM_o(.Mf#/.&7/r)"+OA3K*0?!Mg^FCm2mH$r+PB),Ps$ao9q6&)PL^?4Ik^V!K-:_Z0]0LpQs4QI>NXhjD\BLOcUB5XS=PPOJ`IXHjMe`<]Q1^P4g6s6hZIa=:>H#s(5VUoZkSUMkDgAO(P8:::.F/i41Z$V]@[Cr[]qQq[*eR$lW6f+?,t"4^Aua&BGEm;*?]G#4G0Ft6^m&%a/+A^U7X_9/&6m0I=6:LN>miU!!FD`U&~>endstream
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 9
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000061 00000 n
|
|
||||||
0000000102 00000 n
|
|
||||||
0000000209 00000 n
|
|
||||||
0000000321 00000 n
|
|
||||||
0000000524 00000 n
|
|
||||||
0000000592 00000 n
|
|
||||||
0000000872 00000 n
|
|
||||||
0000000931 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/ID
|
|
||||||
[<2ae2cf0ad41711ce62e7e9efe6438c88><2ae2cf0ad41711ce62e7e9efe6438c88>]
|
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
|
||||||
|
|
||||||
/Info 6 0 R
|
|
||||||
/Root 5 0 R
|
|
||||||
/Size 9
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1875
|
|
||||||
%%EOF
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/F1 2 0 R /F2 3 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Author (\(anonymous\)) /CreationDate (D:20260212201938+01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260212201938+01'00') /Producer (ReportLab PDF Library - \(opensource\))
|
|
||||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<<
|
|
||||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 929
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatUr9lldX&;KZQ'mjZ8ac)'%"G5DIVo_We9UhFAPRB4O6Y_P^ni6I>?%hPEiFD-kgPFBpos/nN5_7iZp&@,T\-V$ZM?7?#/:_c!6gt2l#,@NIl1'W@hf=\sXji$^N*2YX$I#hmmW*D@?lW]c'X*jBOXbT`Y(15rMBB0$`T\</j<i-1rE$Ln5YiMIL>]EEI>a<'`s*]?ja"Rq1.sLs+Is<)$/]cNiSe>Z!FphtZRZ5Z$RY_kQ8%`qI\u01=P:EHo0;^^:$F"^$_#T)CO&X*NdhVmm,D%4HSP2_@)Yk(g_66*I0\eJ\X'0p7M)S@@"c'(_C!o,rH%nj*o%"L>1/j<1V6;Fle@&lGc;&5j'%_G<Da.$kuu21eMjldg(WHObI''g/orN6>J<a`a.[cJ7;En*E8Vdg#qF"A/5Bu,Rq\pUklRcO`S"JdR'%p%.o+SU\6T6dZhrEed-g*!ZV.Ua6nYc!Okg%t-\(Cgjll[so6IoQ/!)F==M8j4/%mCJO$tO97;BTrms/G(K_d<Tk5"E`]3r+oA>4.%N>9[EN>N>.=Xl%8kD+&cCM_YUgMhj=)`C;-21t/ji7f#P#jihd&bJUlR3m%g0=U&7n)Mr<6nbb!F#m8/n`TZnIooN4]kr(O0qP)]\r\Y.l5I'm)(7&?&g#&b*_lp]-C-FfohG2gRl$1KI71[0\hOcPN5b0XqEZd/7p3?[dbS<XEWl7]dKMEP@Uk5dK1hL%b.+lcoC)=@J1^D7\!)8Y+%s@cQ-Zbnps=rmcb+Y;l7Lcc%.E!V[dP<M%EpF1eMaTZ+G[pOhUago5JJ:7_8d;TVVpB1V4_rWNNm%@D6H*P7@35<+@GSuH5XVC"F3j3k1,V!EK5caK,9S!$+j;&JU3K0$Qf#DZgDiIB<u*)':+)aWY\sNPU_VnH*8,abo2Q?!Wm[P3W~>endstream
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 9
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000061 00000 n
|
|
||||||
0000000102 00000 n
|
|
||||||
0000000209 00000 n
|
|
||||||
0000000321 00000 n
|
|
||||||
0000000524 00000 n
|
|
||||||
0000000592 00000 n
|
|
||||||
0000000872 00000 n
|
|
||||||
0000000931 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/ID
|
|
||||||
[<fbf718fd6966c08977bc531039e9de4e><fbf718fd6966c08977bc531039e9de4e>]
|
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
|
||||||
|
|
||||||
/Info 6 0 R
|
|
||||||
/Root 5 0 R
|
|
||||||
/Size 9
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1950
|
|
||||||
%%EOF
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/F1 2 0 R /F2 3 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Author (\(anonymous\)) /CreationDate (D:20260212202036+01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260212202036+01'00') /Producer (ReportLab PDF Library - \(opensource\))
|
|
||||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<<
|
|
||||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 930
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
GatUr9lldX&;KZQ'mjZ8ac)'%"G5DIVo_We9UhFAPRB4O6Y\ddni6I>>m,>$n1f(1fZ:,Dl=KX%"U0Joe\B95i51,Q.ghMl[fX\]%c.`kl:kS3<KC(#\;";Bd7UGNS2#Xp6%M%Na_d%j&PTm^&&NQ#(Q!_KAdb"'S+&0(<LC^D-ds?Am(`J+"F:.+1O,fdkZaSL4Z3Qh%**?.PZb#[#O/mg8[sN:$4$TW!AIR<41ere[:#`4C^nk-kncsh^rl@VR/_``BM-^#05/pmYN<.q>4+[a]8P:Kn&7+*Je\fiF&-oGob1$&ALp]\%)>s-^^.8ZK[$`b]'#$BRl9&Ko1%0^;_taUQ0k$8Wd(KMkMoa2UK(.nk"2Z8Wu@G\(>T;%KOL@;8Xikjq$kq&_VrkUUS8T\=WFq=@"o"D$*2O0hKIVA@+rDqAo[>V4XQYejI-a)%W3*>r9SlPl#f)A^s%u.0TrK\O(<aUb*,'_KsGh#QfU!U`X:eM2H4VniC\#\n7.QK>ERWXpjJ_N(87m*Tma\f`\,p21E#F^@UfKJD'P=&1b:MNZLbsnjKGq!@rK7".W*fdD6t3ThZr%^i'M*!WKs2IP0,V\X)Pa(p\YT+Q6c`\jc7ld@Z3Op/c!;9!pV?X+K"`%<Ruq!"f\Y%WQ[V9JX,^YrUb%a8iAE*&ZBXG=%JN;eFaF<?L#O$oP,ppB/0mA&O"55?'+DBK9db7s,\j873XY083aW68["->g,8n*Ihf2?IW"o.ccota2i[AZqi-A;YWqaMm:NS=iu(%j%8>A:FrODm^fT:'VLTX%YPh_p*;o_`1^+t&Un<6k9:^[[Q)oob.qq'#q('$E?Nh<o&(&M``rY6i%9_7T)MC*hU(mViDC"R8o`XPO!\UuI[*GQ!9:X+a,u,<R[Ad;NiDM-iguO*&83L`Bq$X1U>;-~>endstream
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 9
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000061 00000 n
|
|
||||||
0000000102 00000 n
|
|
||||||
0000000209 00000 n
|
|
||||||
0000000321 00000 n
|
|
||||||
0000000524 00000 n
|
|
||||||
0000000592 00000 n
|
|
||||||
0000000872 00000 n
|
|
||||||
0000000931 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/ID
|
|
||||||
[<7157e2a041ebda766e80d1441656b00d><7157e2a041ebda766e80d1441656b00d>]
|
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
|
||||||
|
|
||||||
/Info 6 0 R
|
|
||||||
/Root 5 0 R
|
|
||||||
/Size 9
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1951
|
|
||||||
%%EOF
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
%PDF-1.4
|
|
||||||
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
|
||||||
1 0 obj
|
|
||||||
<<
|
|
||||||
/F1 2 0 R /F2 3 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
2 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
3 0 obj
|
|
||||||
<<
|
|
||||||
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
4 0 obj
|
|
||||||
<<
|
|
||||||
/Contents 8 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 7 0 R /Resources <<
|
|
||||||
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
|
||||||
>> /Rotate 0 /Trans <<
|
|
||||||
|
|
||||||
>>
|
|
||||||
/Type /Page
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
5 0 obj
|
|
||||||
<<
|
|
||||||
/PageMode /UseNone /Pages 7 0 R /Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
6 0 obj
|
|
||||||
<<
|
|
||||||
/Author (\(anonymous\)) /CreationDate (D:20260211215935+01'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260211215935+01'00') /Producer (ReportLab PDF Library - \(opensource\))
|
|
||||||
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
7 0 obj
|
|
||||||
<<
|
|
||||||
/Count 1 /Kids [ 4 0 R ] /Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
8 0 obj
|
|
||||||
<<
|
|
||||||
/Filter [ /ASCII85Decode /FlateDecode ] /Length 640
|
|
||||||
>>
|
|
||||||
stream
|
|
||||||
Gat=h9lldX&;KZQ'mjZ8l&;km&-?a/g[ro8)CmaRA<::6o+;OVr9q8=FqSErb^a:B5[rV@YBH66_*RHOc2_`.bjQrV#''gqF$,Q!iMW3Y9^$2ea$9ph"@3m_+#nhdd'kF54p)BX4;7h1aoiPs6d5!9d%Ch?m&1t6\kp"\>q(sQ3S`r+W!obp048L9$1.G7T@V?X'gEFEI09$Hj>WH3`(.>P3%b4lIfoU2jgcJccS1]T@-o;B%E-t.G;/ulF,dOf!G*H_K_[F@As>nW+s'3+G<AW*N;Oe7B@Nu$0("<:F:c_Z_P*NlKaTHeU<&=C21<&b+M)ZC_+0s"4Cmbe(Lc/Ojq#WWZ5::uCV<Ct]r=u.,Oi.T&q&?IlOr+8\7fmbqP?d.+i6g`*7?A"G%R.<L(3N4aA)N`*6Y'PSVNbM`3DQs*[fMVQpZ^".oZ,`qdQ@U/a6/7?n.FT=A?Q3DB^25n!2pC1\?`SKsIXUgL`-r@f(+BA\A&!q<2_DasND--cQ'jD'5N3l`<o9*4G`j6<Ur#@B3+_S[QshZg2HB?Jp!9#hmi)XUXpPm*B:V2uBFKVPtfE_A:QPQkn#]."#hXgMNuRJ\"S?pg[;i>2tC>`0^-l_`fq^0O#!"OST(^PQ)F7SGi~>endstream
|
|
||||||
endobj
|
|
||||||
xref
|
|
||||||
0 9
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000061 00000 n
|
|
||||||
0000000102 00000 n
|
|
||||||
0000000209 00000 n
|
|
||||||
0000000321 00000 n
|
|
||||||
0000000524 00000 n
|
|
||||||
0000000592 00000 n
|
|
||||||
0000000872 00000 n
|
|
||||||
0000000931 00000 n
|
|
||||||
trailer
|
|
||||||
<<
|
|
||||||
/ID
|
|
||||||
[<dd9e03bef959be273127c681a80b79d4><dd9e03bef959be273127c681a80b79d4>]
|
|
||||||
% ReportLab generated PDF document -- digest (opensource)
|
|
||||||
|
|
||||||
/Info 6 0 R
|
|
||||||
/Root 5 0 R
|
|
||||||
/Size 9
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1661
|
|
||||||
%%EOF
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -24,7 +24,12 @@ from reportlab.platypus import PageBreak
|
|||||||
# HELPERS
|
# HELPERS
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
def _create_stop_histogram(stop_distances):
|
def _create_stop_histogram(
|
||||||
|
stop_distances,
|
||||||
|
p50=None,
|
||||||
|
p90=None,
|
||||||
|
p99=None,
|
||||||
|
):
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(6, 4))
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
|
|
||||||
@@ -34,6 +39,16 @@ def _create_stop_histogram(stop_distances):
|
|||||||
alpha=0.7,
|
alpha=0.7,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Percentile lines
|
||||||
|
if p50 is not None:
|
||||||
|
ax.axvline(p50 * 100, linestyle=":", linewidth=1)
|
||||||
|
|
||||||
|
if p90 is not None:
|
||||||
|
ax.axvline(p90 * 100, linestyle=":", linewidth=1)
|
||||||
|
|
||||||
|
if p99 is not None:
|
||||||
|
ax.axvline(p99 * 100, linestyle=":", linewidth=1)
|
||||||
|
|
||||||
ax.set_title("Stop Distance Distribution")
|
ax.set_title("Stop Distance Distribution")
|
||||||
ax.set_xlabel("Stop Distance (%)")
|
ax.set_xlabel("Stop Distance (%)")
|
||||||
ax.set_ylabel("Frequency")
|
ax.set_ylabel("Frequency")
|
||||||
@@ -46,18 +61,30 @@ def _create_stop_histogram(stop_distances):
|
|||||||
|
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def _create_position_size_plot(timestamps, position_sizes):
|
def _create_position_size_plot(
|
||||||
# Align and be robust
|
timestamps,
|
||||||
|
position_sizes,
|
||||||
|
*,
|
||||||
|
max_position_fraction=None,
|
||||||
|
):
|
||||||
ts, ps = _align_xy(timestamps, position_sizes)
|
ts, ps = _align_xy(timestamps, position_sizes)
|
||||||
if not ps:
|
if not ps:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
x = list(range(len(ps))) # robust axis (avoid matplotlib categorical date issues)
|
x = list(range(len(ps)))
|
||||||
y = [p * 100 for p in ps]
|
y = [p * 100 for p in ps]
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(6, 4))
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
|
|
||||||
ax.plot(x, y, linewidth=0.8)
|
ax.plot(x, y, linewidth=0.8)
|
||||||
|
|
||||||
|
if max_position_fraction is not None:
|
||||||
|
ax.axhline(
|
||||||
|
max_position_fraction * 100,
|
||||||
|
linestyle="--",
|
||||||
|
linewidth=1,
|
||||||
|
)
|
||||||
|
|
||||||
ax.set_title("Position Size Over Time")
|
ax.set_title("Position Size Over Time")
|
||||||
ax.set_ylabel("Position Size (% of equity)")
|
ax.set_ylabel("Position Size (% of equity)")
|
||||||
ax.set_xlabel("Samples")
|
ax.set_xlabel("Samples")
|
||||||
@@ -67,9 +94,19 @@ def _create_position_size_plot(timestamps, position_sizes):
|
|||||||
plt.savefig(buf, format="png", dpi=150)
|
plt.savefig(buf, format="png", dpi=150)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def _create_effective_risk_plot(timestamps, effective_risks):
|
|
||||||
|
def _create_effective_risk_plot(
|
||||||
|
timestamps,
|
||||||
|
effective_risks,
|
||||||
|
*,
|
||||||
|
risk_target=None,
|
||||||
|
p50=None,
|
||||||
|
p90=None,
|
||||||
|
p99=None,
|
||||||
|
):
|
||||||
ts, er = _align_xy(timestamps, effective_risks)
|
ts, er = _align_xy(timestamps, effective_risks)
|
||||||
if not er:
|
if not er:
|
||||||
return None
|
return None
|
||||||
@@ -78,8 +115,18 @@ def _create_effective_risk_plot(timestamps, effective_risks):
|
|||||||
y = [r * 100 for r in er]
|
y = [r * 100 for r in er]
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(6, 4))
|
fig, ax = plt.subplots(figsize=(6, 4))
|
||||||
|
|
||||||
|
# Main curve
|
||||||
ax.plot(x, y, linewidth=0.8)
|
ax.plot(x, y, linewidth=0.8)
|
||||||
|
|
||||||
|
# Risk target line
|
||||||
|
if risk_target is not None:
|
||||||
|
ax.axhline(
|
||||||
|
risk_target * 100,
|
||||||
|
linestyle="--",
|
||||||
|
linewidth=1,
|
||||||
|
)
|
||||||
|
|
||||||
ax.set_title("Effective Risk Over Time")
|
ax.set_title("Effective Risk Over Time")
|
||||||
ax.set_ylabel("Effective Risk (%)")
|
ax.set_ylabel("Effective Risk (%)")
|
||||||
ax.set_xlabel("Samples")
|
ax.set_xlabel("Samples")
|
||||||
@@ -89,6 +136,7 @@ def _create_effective_risk_plot(timestamps, effective_risks):
|
|||||||
plt.savefig(buf, format="png", dpi=150)
|
plt.savefig(buf, format="png", dpi=150)
|
||||||
plt.close(fig)
|
plt.close(fig)
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def _align_xy(x, y):
|
def _align_xy(x, y):
|
||||||
@@ -386,7 +434,18 @@ def generate_risk_report_pdf(
|
|||||||
story.append(Paragraph("6. Stop Distance Distribution", heading_style))
|
story.append(Paragraph("6. Stop Distance Distribution", heading_style))
|
||||||
story.append(Spacer(1, 8))
|
story.append(Spacer(1, 8))
|
||||||
|
|
||||||
img_buffer = _create_stop_histogram(stop_distances)
|
stop_metrics = (
|
||||||
|
results.get("checks", {})
|
||||||
|
.get("stop_sanity", {})
|
||||||
|
.get("metrics", {})
|
||||||
|
)
|
||||||
|
|
||||||
|
img_buffer = _create_stop_histogram(
|
||||||
|
stop_distances,
|
||||||
|
p50=stop_metrics.get("p50"),
|
||||||
|
p90=stop_metrics.get("p90"),
|
||||||
|
p99=stop_metrics.get("p99"),
|
||||||
|
)
|
||||||
|
|
||||||
img = Image(img_buffer, width=400, height=250)
|
img = Image(img_buffer, width=400, height=250)
|
||||||
story.append(img)
|
story.append(img)
|
||||||
@@ -405,7 +464,12 @@ def generate_risk_report_pdf(
|
|||||||
story.append(Paragraph("7. Position Size Over Time", heading_style))
|
story.append(Paragraph("7. Position Size Over Time", heading_style))
|
||||||
story.append(Spacer(1, 8))
|
story.append(Spacer(1, 8))
|
||||||
|
|
||||||
img_buffer = _create_position_size_plot(timestamps, position_sizes)
|
img_buffer = _create_position_size_plot(
|
||||||
|
timestamps,
|
||||||
|
position_sizes,
|
||||||
|
max_position_fraction=config.get("Max position fraction (%)", 0) / 100,
|
||||||
|
)
|
||||||
|
|
||||||
if img_buffer:
|
if img_buffer:
|
||||||
img = Image(img_buffer, width=400, height=250)
|
img = Image(img_buffer, width=400, height=250)
|
||||||
story.append(img)
|
story.append(img)
|
||||||
@@ -422,7 +486,14 @@ def generate_risk_report_pdf(
|
|||||||
story.append(Paragraph("8. Effective Risk Over Time", heading_style))
|
story.append(Paragraph("8. Effective Risk Over Time", heading_style))
|
||||||
story.append(Spacer(1, 8))
|
story.append(Spacer(1, 8))
|
||||||
|
|
||||||
img_buffer = _create_effective_risk_plot(timestamps, effective_risks)
|
risk_target = config.get("Risk per trade (%)", 0) / 100
|
||||||
|
|
||||||
|
img_buffer = _create_effective_risk_plot(
|
||||||
|
timestamps,
|
||||||
|
effective_risks,
|
||||||
|
risk_target=risk_target,
|
||||||
|
)
|
||||||
|
|
||||||
if img_buffer:
|
if img_buffer:
|
||||||
img = Image(img_buffer, width=400, height=250)
|
img = Image(img_buffer, width=400, height=250)
|
||||||
story.append(img)
|
story.append(img)
|
||||||
|
|||||||
@@ -68,6 +68,18 @@ def create_app() -> FastAPI:
|
|||||||
name="static",
|
name="static",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# Reports folder (public access)
|
||||||
|
# -------------------------
|
||||||
|
reports_root = PROJECT_ROOT / "reports"
|
||||||
|
reports_root.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
app.mount(
|
||||||
|
"/reports",
|
||||||
|
StaticFiles(directory=str(reports_root)),
|
||||||
|
name="reports",
|
||||||
|
)
|
||||||
|
|
||||||
# ==================================================
|
# ==================================================
|
||||||
# ROUTES — UI ONLY (TEMPORAL)
|
# ROUTES — UI ONLY (TEMPORAL)
|
||||||
# ==================================================
|
# ==================================================
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import uuid
|
import uuid
|
||||||
|
import re
|
||||||
|
|
||||||
from src.data.storage import StorageManager
|
from src.data.storage import StorageManager
|
||||||
from src.calibration.risk_inspector import (
|
from src.calibration.risk_inspector import (
|
||||||
@@ -261,15 +262,29 @@ def generate_risk_report(
|
|||||||
|
|
||||||
results = result
|
results = result
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ============================================================
|
||||||
# Generate file
|
# BUILD SAFE OUTPUT PATH (OUTSIDE src/)
|
||||||
# ---------------------------------------------
|
# ============================================================
|
||||||
|
|
||||||
reports_dir = Path("reports")
|
# Project root (3 niveles arriba desde router)
|
||||||
reports_dir.mkdir(exist_ok=True)
|
project_root = Path(__file__).resolve().parents[5]
|
||||||
|
|
||||||
filename = f"risk_report_{uuid.uuid4().hex}.pdf"
|
# Reports folder outside src
|
||||||
output_path = reports_dir / filename
|
reports_dir = project_root / "reports" / "risk"
|
||||||
|
reports_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Sanitize symbol for filesystem
|
||||||
|
safe_symbol = re.sub(r"[^a-zA-Z0-9_-]", "_", payload.symbol)
|
||||||
|
|
||||||
|
# Unique filename
|
||||||
|
file_id = uuid.uuid4().hex
|
||||||
|
|
||||||
|
filename = f"risk_report_{safe_symbol}_{payload.timeframe}_{file_id}.pdf"
|
||||||
|
|
||||||
|
symbol_dir = reports_dir / safe_symbol
|
||||||
|
symbol_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
output_path = symbol_dir / filename
|
||||||
|
|
||||||
generate_risk_report_pdf(
|
generate_risk_report_pdf(
|
||||||
output_path=output_path,
|
output_path=output_path,
|
||||||
@@ -278,8 +293,24 @@ def generate_risk_report(
|
|||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
return FileResponse(
|
# ---------------------------------------------
|
||||||
path=str(output_path),
|
# Create public URL
|
||||||
media_type="application/pdf",
|
# ---------------------------------------------
|
||||||
filename=filename,
|
|
||||||
|
public_url = f"/reports/risk/{safe_symbol}/{filename}"
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
content={
|
||||||
|
"status": "ok",
|
||||||
|
"url": public_url,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# return FileResponse(
|
||||||
|
# path=str(output_path),
|
||||||
|
# media_type="application/pdf",
|
||||||
|
# filename=output_path.name,
|
||||||
|
# headers={
|
||||||
|
# "Content-Disposition": f'inline; filename="{output_path.name}"'
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ async function validateCalibrationRisk() {
|
|||||||
async function generateRiskReport() {
|
async function generateRiskReport() {
|
||||||
console.log("[calibration_risk] generateRiskReport() START");
|
console.log("[calibration_risk] generateRiskReport() START");
|
||||||
|
|
||||||
const payload = buildRiskPayload(); // reutiliza la misma función que validate
|
const payload = buildRiskPayload();
|
||||||
|
|
||||||
const res = await fetch("/api/v2/calibration/risk/report", {
|
const res = await fetch("/api/v2/calibration/risk/report", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -220,25 +220,23 @@ async function generateRiskReport() {
|
|||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
const data = await res.json();
|
||||||
console.error("Failed to generate report");
|
|
||||||
return;
|
if (data.url) {
|
||||||
|
// Mostrar visor
|
||||||
|
const viewer = document.getElementById("pdf_viewer_section");
|
||||||
|
const frame = document.getElementById("pdf_frame");
|
||||||
|
|
||||||
|
frame.src = data.url;
|
||||||
|
viewer.classList.remove("d-none");
|
||||||
|
|
||||||
|
// Scroll suave hacia el visor
|
||||||
|
viewer.scrollIntoView({ behavior: "smooth" });
|
||||||
|
} else {
|
||||||
|
alert("Failed to generate report");
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await res.blob();
|
|
||||||
const url = window.URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = url;
|
|
||||||
a.download = "risk_report.pdf";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
|
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// =================================================
|
// =================================================
|
||||||
// WIZARD NAVIGATION
|
// WIZARD NAVIGATION
|
||||||
// =================================================
|
// =================================================
|
||||||
@@ -804,6 +802,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
document
|
document
|
||||||
.getElementById("generate_report_btn")
|
.getElementById("generate_report_btn")
|
||||||
?.addEventListener("click", generateRiskReport);
|
?.addEventListener("click", generateRiskReport);
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById("close_pdf_btn")
|
||||||
|
?.addEventListener("click", () => {
|
||||||
|
document.getElementById("pdf_viewer_section").classList.add("d-none");
|
||||||
|
document.getElementById("pdf_frame").src = "";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,27 @@
|
|||||||
<div id="stop_distance_plot" style="height: 360px;"></div>
|
<div id="stop_distance_plot" style="height: 360px;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ========================= -->
|
||||||
|
<!-- PDF REPORT VIEWER -->
|
||||||
|
<!-- ========================= -->
|
||||||
|
<div id="pdf_viewer_section" class="mt-5 d-none">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h4 class="card-title mb-0">Risk Report Preview</h4>
|
||||||
|
<button id="close_pdf_btn" class="btn btn-sm btn-outline-secondary">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
id="pdf_frame"
|
||||||
|
style="width: 100%; height: 800px; border: none;"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Plotly -->
|
<!-- Plotly -->
|
||||||
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user