zipfolio 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zipfolio-0.1.0/CHANGELOG.md +27 -0
- zipfolio-0.1.0/LICENSE +201 -0
- zipfolio-0.1.0/PKG-INFO +316 -0
- zipfolio-0.1.0/README.md +279 -0
- zipfolio-0.1.0/pyproject.toml +62 -0
- zipfolio-0.1.0/scripts/build_us_sector_map.py +52 -0
- zipfolio-0.1.0/src/zipfolio/__init__.py +58 -0
- zipfolio-0.1.0/src/zipfolio/factors.py +104 -0
- zipfolio-0.1.0/src/zipfolio/inputs.py +153 -0
- zipfolio-0.1.0/src/zipfolio/page.py +145 -0
- zipfolio-0.1.0/src/zipfolio/sections/__init__.py +1 -0
- zipfolio-0.1.0/src/zipfolio/sections/capacity.py +70 -0
- zipfolio-0.1.0/src/zipfolio/sections/execution.py +423 -0
- zipfolio-0.1.0/src/zipfolio/sections/factor_analysis.py +183 -0
- zipfolio-0.1.0/src/zipfolio/sections/instruments.py +204 -0
- zipfolio-0.1.0/src/zipfolio/sections/performance.py +32 -0
- zipfolio-0.1.0/src/zipfolio/sections/portfolio_risk.py +274 -0
- zipfolio-0.1.0/src/zipfolio/sections/positions.py +35 -0
- zipfolio-0.1.0/src/zipfolio/sections/round_trips.py +80 -0
- zipfolio-0.1.0/src/zipfolio/sections/sector_analysis.py +49 -0
- zipfolio-0.1.0/src/zipfolio/sections/trading_costs.py +219 -0
- zipfolio-0.1.0/src/zipfolio/sections/transactions.py +89 -0
- zipfolio-0.1.0/src/zipfolio/sectors.py +96 -0
- zipfolio-0.1.0/src/zipfolio/style.py +370 -0
- zipfolio-0.1.0/src/zipfolio/tables.py +127 -0
- zipfolio-0.1.0/src/zipfolio/tearsheet.py +176 -0
- zipfolio-0.1.0/src/zipfolio/ziplime.py +117 -0
- zipfolio-0.1.0/tests/conftest.py +8 -0
- zipfolio-0.1.0/tests/test_zipfolio.py +803 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 -- 2026-10-13
|
|
4
|
+
|
|
5
|
+
First release, extracted from the ZipLime backtester, where it builds the
|
|
6
|
+
report next to the QuantStats tearsheet of every backtest.
|
|
7
|
+
|
|
8
|
+
- `build_tearsheet`: one self-contained HTML page in the QuantStats Pro look --
|
|
9
|
+
Performance, Positions, Instruments, Sector Analysis, Factor Analysis,
|
|
10
|
+
Portfolio Risk, Transactions, Execution, Trading Costs, Round Trips, Capacity.
|
|
11
|
+
- `zipfolio.ziplime`: daily bars, per-fill bars and decision prices, and order
|
|
12
|
+
types read from a ziplime run.
|
|
13
|
+
- Fama-French 3/5 factors and momentum, daily, from Kenneth French's data
|
|
14
|
+
library, cached for a week.
|
|
15
|
+
- GICS sectors for US tickers from a table the user supplies (`table=` or
|
|
16
|
+
`ZIPFOLIO_GICS_TABLE`); `scripts/build_us_sector_map.py` builds one. No
|
|
17
|
+
sector data ships with the package -- GICS is licensed by MSCI and S&P.
|
|
18
|
+
- Every report links to https://github.com/Limex-com/zipfolio in its header
|
|
19
|
+
and footer, next to the Zipfolio version that built it, under the title
|
|
20
|
+
"Zipfolio Tearsheet: Portfolio, Execution & Cost Analysis".
|
|
21
|
+
- Fixes to pyfolio-reloaded 0.9.9 behaviour, applied without patching it
|
|
22
|
+
globally: the capacity join on same-day fills, the New-York-only fill-time
|
|
23
|
+
chart, the exposure panel lost to pandas period axes, overlapping labels.
|
|
24
|
+
- Allocation charts (top positions, sectors) draw a weight only while it is
|
|
25
|
+
held: an unheld name is a gap, not a zero, so exits and entries are no
|
|
26
|
+
longer vertical drops to the axis; one-session holdings are dotted, and the
|
|
27
|
+
axis keeps zero.
|
zipfolio-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
zipfolio-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zipfolio
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Backtest tearsheets that read the book, not just the returns: positions, instruments, sectors, factors, execution realism and trading costs. Part of the ZipLime ecosystem.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: backtesting,tearsheet,pyfolio,ziplime,portfolio-analytics,execution,fama-french
|
|
8
|
+
Author: Ziplime
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Provides-Extra: all
|
|
16
|
+
Provides-Extra: risk
|
|
17
|
+
Provides-Extra: ziplime
|
|
18
|
+
Requires-Dist: cycler (>=0.11)
|
|
19
|
+
Requires-Dist: empyrical-reloaded (>=0.5.9)
|
|
20
|
+
Requires-Dist: matplotlib (>=3.8)
|
|
21
|
+
Requires-Dist: numpy (>=1.26)
|
|
22
|
+
Requires-Dist: pandas (>=2.1,<3)
|
|
23
|
+
Requires-Dist: polars (>=1.0) ; extra == "all"
|
|
24
|
+
Requires-Dist: polars (>=1.0) ; extra == "ziplime"
|
|
25
|
+
Requires-Dist: pyfolio-reloaded (>=0.9.9)
|
|
26
|
+
Requires-Dist: scipy (>=1.11)
|
|
27
|
+
Requires-Dist: seaborn (>=0.13)
|
|
28
|
+
Requires-Dist: skfolio (>=0.15) ; extra == "all"
|
|
29
|
+
Requires-Dist: skfolio (>=0.15) ; extra == "risk"
|
|
30
|
+
Requires-Dist: statsmodels (>=0.14)
|
|
31
|
+
Project-URL: Changelog, https://github.com/Limex-com/zipfolio/blob/main/CHANGELOG.md
|
|
32
|
+
Project-URL: Homepage, https://github.com/Limex-com/zipfolio
|
|
33
|
+
Project-URL: Issues, https://github.com/Limex-com/zipfolio/issues
|
|
34
|
+
Project-URL: Repository, https://github.com/Limex-com/zipfolio
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# Zipfolio
|
|
38
|
+
|
|
39
|
+
**Backtest tearsheets that read the book, not just the returns.**
|
|
40
|
+
|
|
41
|
+
Zipfolio turns a backtest into one self-contained HTML report about *how the
|
|
42
|
+
strategy behaves position by position*: what it held and how concentrated it
|
|
43
|
+
was, what each instrument and sector earned, which factors it was really
|
|
44
|
+
exposed to, where its risk sat, how its orders were filled -- and how far those
|
|
45
|
+
fills can be believed -- and what trading costs took. It is part of the
|
|
46
|
+
[ZipLime](https://github.com/Limex-com) ecosystem, where it is built for every
|
|
47
|
+
backtest next to the QuantStats returns tearsheet.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from zipfolio import build_tearsheet
|
|
51
|
+
|
|
52
|
+
build_tearsheet("report.html", returns, benchmark,
|
|
53
|
+
position_records=positions, cash=ending_cash, transactions=fills,
|
|
54
|
+
orders=orders, fill_bars=fill_bars, decision_prices=decisions,
|
|
55
|
+
market_data=daily_bars, cost_settings=settings)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Why a backtester-aware report
|
|
61
|
+
|
|
62
|
+
A returns tearsheet -- QuantStats, or pyfolio fed only a return series -- sees
|
|
63
|
+
one number per day. That is enough for Sharpe ratios and drawdowns, and nothing
|
|
64
|
+
else: it cannot tell you whether a fill was possible, what slippage cost, or
|
|
65
|
+
which position carried the risk, because none of that is in the returns.
|
|
66
|
+
|
|
67
|
+
A backtester knows all of it while it runs. Every order has a type, a creation
|
|
68
|
+
time, a limit or a stop; every fill belongs to an order and happens on a bar
|
|
69
|
+
with an open, high, low, close and volume; every cost comes from a model with
|
|
70
|
+
known settings. Most of this is thrown away once the simulation ends.
|
|
71
|
+
**Zipfolio is built to be fed by the backtester itself**, while that detail
|
|
72
|
+
still exists, and that is what makes its metrics possible:
|
|
73
|
+
|
|
74
|
+
| Question the report answers | What it needs | Where a ziplime run has it |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| Was each fill priced inside the bar it happened on? | the fill's own bar (OHLC) | the market-data bundle, joined as of the fill time |
|
|
77
|
+
| Did a fill take more of the bar's volume than could trade? | the fill's bar volume | the same bundle |
|
|
78
|
+
| Did the strategy trade at a price it had only just seen? | execution timing settings | `same_bar_execution`, `price_used_in_order_execution` |
|
|
79
|
+
| What did a fill cost over the price the strategy decided at? | the close at the order's creation | the order's `created` time, joined to the bundle |
|
|
80
|
+
| How long did orders wait; were any left unfilled, and why? | order lifecycle | orders: `created`, `dt`, `filled`, `status`, `reason` |
|
|
81
|
+
| Which order types did the strategy use? | execution style | `MarketOrder` / `LimitOrder` / `StopOrder` / `StopLimitOrder` |
|
|
82
|
+
| What did commission cost? | commission per order | `order.commission` (ziplime charges it to the order, not the fill) |
|
|
83
|
+
| What did slippage cost? | the slippage model | `FixedBasisPointsSlippage`: reconstructed exactly from the fill prices |
|
|
84
|
+
| What did each instrument earn? | positions and fills per day | position snapshots, fills with prices and quantities |
|
|
85
|
+
| How much capital could the strategy take? | daily volume per traded name | the bundle, folded into sessions |
|
|
86
|
+
| How is the cost model set, in words? | the settings themselves | `PerShare` / `PerDollar`, minimum per order, slippage bps, volume limit |
|
|
87
|
+
|
|
88
|
+
The result reads like an execution and risk desk's review of the backtest,
|
|
89
|
+
not a restatement of its equity curve.
|
|
90
|
+
|
|
91
|
+
## What is in a report
|
|
92
|
+
|
|
93
|
+
Charts on the left and narrow tables on the right in a 960px column, in the
|
|
94
|
+
QuantStats Pro look (white ground, light grid, the same palette: strategy
|
|
95
|
+
`#6B9E00`, benchmark `#5C5C5C`), so the two reports read as one product.
|
|
96
|
+
Every chart is SVG; the page has no external requests. The header and footer
|
|
97
|
+
name the Zipfolio version that built the page and link back to this repository.
|
|
98
|
+
The default title is "Zipfolio Tearsheet: Portfolio, Execution & Cost Analysis"
|
|
99
|
+
(`title=` changes it).
|
|
100
|
+
|
|
101
|
+
| Section | Shown when | Contents |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| **Performance** | always | key metrics, cumulative returns, underwater curve -- deliberately short; returns are QuantStats' job |
|
|
104
|
+
| **Positions** | the strategy traded | exposure, top positions, long/short concentration, holdings, gross leverage |
|
|
105
|
+
| **Instruments** | traded | each name's PnL, contribution, weight, days held, trade stats; contribution by year; daily and round-trip return distributions per name; cumulative PnL by name |
|
|
106
|
+
| **Sector Analysis** | a held name has a sector | allocation over time, average and peak, profit attribution by GICS sector |
|
|
107
|
+
| **Factor Analysis** | a factor model is set | Fama-French 5 + momentum betas with Newey-West t-stats, alpha, fit, attribution (overall and by year), rolling betas, return decomposition |
|
|
108
|
+
| **Portfolio Risk** | always (more when traded) | VaR/CVaR/EVaR, drawdown-at-risk/CDaR/EDaR, Ulcer index, Gini mean difference; rolling tail risk; risk contribution per holding; clustered correlations; the same names under four allocation rules, walk-forward |
|
|
109
|
+
| **Transactions** | traded | trading frequency, fill sizes, buys and sells, turnover, volume, fill times on the market's own clock |
|
|
110
|
+
| **Execution** | orders are given | how the backtest fills orders, realism checks, orders by type, fill statistics, fill price within the bar and against its typical price, share of bar volume, implementation shortfall, waits |
|
|
111
|
+
| **Trading Costs** | traded | the cost model in plain words, cost summary and per symbol, costs over time, returns before and after costs, sensitivity to extra slippage |
|
|
112
|
+
| **Round Trips** | 5+ round trips | summary, PnL, duration and return statistics, per symbol, lifetimes, distributions |
|
|
113
|
+
| **Capacity** | daily bars are given | days to liquidate, fills over 5% of daily volume, Sharpe across capital bases |
|
|
114
|
+
|
|
115
|
+
A section that cannot be computed becomes a short note, a chart that fails is
|
|
116
|
+
left out, and both are logged; the page fails only if nothing at all can be
|
|
117
|
+
reported.
|
|
118
|
+
|
|
119
|
+
## Zipfolio and pyfolio
|
|
120
|
+
|
|
121
|
+
Zipfolio stands on [pyfolio-reloaded](https://github.com/stefan-jansen/pyfolio-reloaded)
|
|
122
|
+
and owes it most of its analytics. What it takes, what it changes, and what it
|
|
123
|
+
adds:
|
|
124
|
+
|
|
125
|
+
**Uses pyfolio for the computation.** Performance statistics, drawdown tables,
|
|
126
|
+
exposures and concentration, top positions, turnover, round-trip extraction and
|
|
127
|
+
statistics, profit attribution, sector exposure, liquidity and the capacity
|
|
128
|
+
sweep, slippage sensitivity -- all are pyfolio's functions (`timeseries`,
|
|
129
|
+
`pos`, `txn`, `round_trips`, `capacity`, `plotting.plot_*`).
|
|
130
|
+
|
|
131
|
+
**Replaces the presentation.** pyfolio is written for a notebook: tables go to
|
|
132
|
+
IPython's `display`, and each tear sheet is one tall seaborn figure of a dozen
|
|
133
|
+
panels. Zipfolio does not call the `create_*_tear_sheet` functions. It calls the
|
|
134
|
+
individual `plotting.plot_*` functions, one chart per figure on an axis it
|
|
135
|
+
owns, restyles each (palette, grid, percent axes, legends) and renders it to
|
|
136
|
+
SVG; it catches every table at `utils.print_table` as a *frame* and renders it
|
|
137
|
+
itself, with formatting pyfolio applies one-size-fits-all repaired (counts as
|
|
138
|
+
`146`, money as `-$5,067.13`, three-decimal percentages as two).
|
|
139
|
+
|
|
140
|
+
**Fixes, locally.** pyfolio-reloaded 0.9.9 is used as released; where it
|
|
141
|
+
misbehaves, Zipfolio works around it for the one call that needs it, never by
|
|
142
|
+
patching pyfolio globally:
|
|
143
|
+
|
|
144
|
+
- the capacity analysis fails whenever two names trade on the same day
|
|
145
|
+
(`daily_txns_with_bar_data` calls `asfreq` on duplicate dates) -- a repaired
|
|
146
|
+
copy is swapped in for that call;
|
|
147
|
+
- the transaction-time chart is fixed to New York's 09:30-16:00 -- Zipfolio
|
|
148
|
+
draws its own, on the market's clock and over the hours fills actually fall in;
|
|
149
|
+
- the exposure panel disappears when pandas draws a regular-frequency index in
|
|
150
|
+
period ordinals -- charts are drawn with matplotlib dates;
|
|
151
|
+
- labels and heatmap numbers that collide on dense axes are thinned and fitted
|
|
152
|
+
after drawing.
|
|
153
|
+
|
|
154
|
+
**Leaves out what QuantStats already shows.** Rolling statistics, return
|
|
155
|
+
distributions, the monthly heatmap and stress events are pyfolio's too, but a
|
|
156
|
+
ZipLime backtest already has them in its QuantStats tearsheet.
|
|
157
|
+
|
|
158
|
+
**Adds what pyfolio does not have.** The Instruments, Execution, Trading Costs
|
|
159
|
+
and Factor Analysis sections, the Portfolio Risk section (computed with
|
|
160
|
+
[skfolio](https://github.com/skfolio/skfolio)), sector analysis from a GICS
|
|
161
|
+
table you supply, and the cost model described in words.
|
|
162
|
+
|
|
163
|
+
## Zipfolio and ziplime
|
|
164
|
+
|
|
165
|
+
Zipfolio is the reporting layer of ZipLime: it is designed around what a
|
|
166
|
+
ziplime 1.20 run produces, and the `zipfolio.ziplime` module reads the parts a
|
|
167
|
+
return series loses.
|
|
168
|
+
|
|
169
|
+
What it relies on from ziplime:
|
|
170
|
+
|
|
171
|
+
- **Orders as objects** -- `execution_style` (market, limit, stop, stop-limit),
|
|
172
|
+
`created` and `dt`, `amount` and `filled`, `limit` and `stop`, `status` and
|
|
173
|
+
`reason`, and `commission`, which ziplime charges to the order rather than the
|
|
174
|
+
fill.
|
|
175
|
+
- **Fills with their order** -- each transaction carries its `order_id`,
|
|
176
|
+
`amount`, `price` and simulation time.
|
|
177
|
+
- **The market-data bundle** -- a polars frame of OHLCV bars per instrument,
|
|
178
|
+
daily or intraday, that the simulation read; `zipfolio.ziplime.execution_bars`
|
|
179
|
+
joins every fill and every order to it *as of* the simulation time, and
|
|
180
|
+
`daily_bars` folds it into sessions.
|
|
181
|
+
- **The cost and execution models** -- `PerShare` / `PerDollar` commission with
|
|
182
|
+
a per-order minimum, `FixedBasisPointsSlippage` with a volume limit,
|
|
183
|
+
`same_bar_execution` and `price_used_in_order_execution`. Zipfolio describes
|
|
184
|
+
them in words and checks every fill against them.
|
|
185
|
+
- **Positions and cash per bar** -- position snapshots taken during
|
|
186
|
+
`run_simulation_iter` (ziplime mutates position objects later, so they are
|
|
187
|
+
copied as they are emitted) and `ending_cash` from the performance frame.
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from zipfolio.ziplime import daily_bars, execution_bars, order_type
|
|
191
|
+
|
|
192
|
+
# while the run's bundle is still loaded:
|
|
193
|
+
bars = daily_bars(bundle) # symbol, day, price, volume
|
|
194
|
+
found = execution_bars(bundle, fills, orders) # fills: id, symbol, dt; orders: id, symbol, created
|
|
195
|
+
fill_bars, decisions = found["fill_bars"], found["decision_prices"]
|
|
196
|
+
kind = order_type(order) # "market", "limit", "stop", "stop_limit"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The ZipLime backtester calls exactly this after every simulation and passes the
|
|
200
|
+
frames to `build_tearsheet`. Nothing in the report requires ziplime itself:
|
|
201
|
+
the inputs are plain pandas frames, and any backtester that can produce them
|
|
202
|
+
gets the same report.
|
|
203
|
+
|
|
204
|
+
## Installation
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pip install zipfolio
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
With the optional parts:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pip install "zipfolio[all]" # everything
|
|
214
|
+
pip install "zipfolio[risk]" # + skfolio, for the Portfolio Risk section
|
|
215
|
+
pip install "zipfolio[ziplime]" # + polars, for zipfolio.ziplime
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Without skfolio the Portfolio Risk section is left out; nothing else changes.
|
|
219
|
+
Requires Python 3.10+.
|
|
220
|
+
|
|
221
|
+
Until the first release reaches PyPI, or for the latest unreleased code,
|
|
222
|
+
install from GitHub:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pip install "zipfolio[all] @ git+https://github.com/Limex-com/zipfolio"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Releases
|
|
229
|
+
|
|
230
|
+
Zipfolio is published on GitHub first and then on PyPI, under the same
|
|
231
|
+
version: a version is tagged here (`v0.1.0`), and the wheel built from that tag
|
|
232
|
+
is uploaded to PyPI as `zipfolio`. Every report says which version built it --
|
|
233
|
+
in the header ("Generated by ZipLime Zipfolio v0.1.0") and the footer, both
|
|
234
|
+
linking back to this repository -- so a report can always be traced to the code
|
|
235
|
+
that made it. Changes are listed in [CHANGELOG.md](CHANGELOG.md).
|
|
236
|
+
|
|
237
|
+
To publish a release:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
poetry version 0.1.1 # or: patch / minor / major
|
|
241
|
+
git commit -am "Zipfolio 0.1.1" && git tag v0.1.1 && git push --tags
|
|
242
|
+
poetry build && poetry publish # PyPI token configured with `poetry config pypi-token.pypi ...`
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Inputs
|
|
246
|
+
|
|
247
|
+
All frames are plain pandas. Only `returns` is required; each input switches
|
|
248
|
+
on the sections that need it.
|
|
249
|
+
|
|
250
|
+
| Argument | Shape | Used by |
|
|
251
|
+
| --- | --- | --- |
|
|
252
|
+
| `returns` | per-period strategy returns, datetime index | everything |
|
|
253
|
+
| `benchmark` | benchmark returns, same index, or `None` | Performance, Portfolio Risk |
|
|
254
|
+
| `position_records` | rows of `date, symbol, quantity, last_sale_price` per open position per day | Positions, Instruments, Sectors, Risk, Round Trips |
|
|
255
|
+
| `cash` | cash at the end of each period | (same) |
|
|
256
|
+
| `transactions` | rows of `id, order_id, dt, symbol, quantity, price` | Instruments, Transactions, Execution, Costs, Round Trips |
|
|
257
|
+
| `orders` | rows of `id, created, dt, type, symbol, quantity, filled, commission, limit, stop, status, reason` | Execution, Trading Costs, Instruments |
|
|
258
|
+
| `fill_bars` | `open, high, low, close, volume` indexed by fill `id` | Execution |
|
|
259
|
+
| `decision_prices` | `decision_price` indexed by order `id` | Execution (shortfall) |
|
|
260
|
+
| `market_data` | rows of `symbol, day, price, volume` | Capacity, Portfolio Risk |
|
|
261
|
+
| `cost_settings` | `commission_model, commission_per_share, commission_rate, commission_min_trade_cost, slippage_basis_points, slippage_volume_limit, price_used_in_order_execution, same_bar_execution, capital` | Trading Costs, Execution |
|
|
262
|
+
| `sector_mappings` | `{symbol: sector}`; `us_sector_mappings(["AAPL@XNGS", ...], table=...)` gives GICS sectors for US listings from your table | Sector Analysis |
|
|
263
|
+
| `factor_model` | `"ff5_mom"` (default), `"ff5"`, `"ff3"`, `"ff3_mom"` or `None` | Factor Analysis |
|
|
264
|
+
| `factor_data` | your own factor returns (decimals, with `RF`), instead of the download | Factor Analysis |
|
|
265
|
+
| `title` | the page title; defaults to `zipfolio.DEFAULT_TITLE` | header |
|
|
266
|
+
| `market_timezone` | e.g. `"Europe/Moscow"`; defaults to `ZIPFOLIO_MARKET_TIMEZONE`, then New York | fill-time chart |
|
|
267
|
+
|
|
268
|
+
`build_tearsheet` returns the titles of the sections that made it into the page.
|
|
269
|
+
|
|
270
|
+
## Data the package fetches or ships
|
|
271
|
+
|
|
272
|
+
- **Fama-French factors** are downloaded from Kenneth French's
|
|
273
|
+
[data library](https://mba.tuck.dartmouth.edu/pages/faculty/ken.french/data_library.html)
|
|
274
|
+
-- the same files `pandas_datareader.famafrench` reads, without the
|
|
275
|
+
dependency -- and cached for a week under `~/.cache/zipfolio`
|
|
276
|
+
(`ZIPFOLIO_CACHE_DIR`). A failed download falls back to a stale cache, then to
|
|
277
|
+
a note in the report; the timeout is 8 seconds. The library publishes monthly
|
|
278
|
+
with a lag, so the last weeks of a fresh backtest may fall outside it. The
|
|
279
|
+
factors describe US stocks: pass `factor_model=None` for other markets.
|
|
280
|
+
- **GICS sectors are not shipped.** The classification is licensed by MSCI
|
|
281
|
+
and S&P, so the codes are yours to bring: a CSV of `symbol,gics_sector`
|
|
282
|
+
(two-digit sector code) passed as `us_sector_mappings(listings, table=...)`
|
|
283
|
+
or found through `ZIPFOLIO_GICS_TABLE`. `scripts/build_us_sector_map.py`
|
|
284
|
+
builds one from exports that carry GICS codes, one row per ticker from its
|
|
285
|
+
most recent classification. `us_sector_mappings` looks up only listings on
|
|
286
|
+
US venues, so a Moscow `T` is not filed as AT&T; a reused ticker gets its
|
|
287
|
+
current holder's sector. Or skip the table and pass `sector_mappings`
|
|
288
|
+
yourself -- any `{symbol: sector}` works, GICS or not.
|
|
289
|
+
|
|
290
|
+
## Limitations
|
|
291
|
+
|
|
292
|
+
- Futures exposure is understated: position values are quantity x price, and
|
|
293
|
+
the records carry no contract multiplier.
|
|
294
|
+
- Slippage is reconstructed for the fixed-basis-point model; other slippage
|
|
295
|
+
models would need their own reconstruction.
|
|
296
|
+
- Instrument contribution adds daily PnL over the start-of-day portfolio value,
|
|
297
|
+
so dollars and percent can disagree in sign for a name that earned early and
|
|
298
|
+
gave back later -- the report says so where it shows them.
|
|
299
|
+
- Factor analysis needs at least 60 sessions inside the factor data.
|
|
300
|
+
|
|
301
|
+
## Development
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
pip install -e ".[all]" pytest # or: poetry install --all-extras
|
|
305
|
+
pytest
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Tests never touch the network: factor downloads are replaced by synthetic
|
|
309
|
+
factors. Release notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
Apache License 2.0 -- see [LICENSE](LICENSE). Zipfolio builds on
|
|
314
|
+
pyfolio-reloaded (Apache-2.0) and skfolio (BSD-3-Clause). No market or
|
|
315
|
+
classification data is distributed with it.
|
|
316
|
+
|