qis 2.1.33__tar.gz → 2.1.34__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.
Files changed (149) hide show
  1. {qis-2.1.33 → qis-2.1.34}/PKG-INFO +2 -2
  2. {qis-2.1.33 → qis-2.1.34}/pyproject.toml +1 -1
  3. {qis-2.1.33 → qis-2.1.34}/qis/examples/factsheets/multi_assets.py +4 -3
  4. {qis-2.1.33 → qis-2.1.34}/qis/examples/factsheets/multi_strategy.py +2 -2
  5. {qis-2.1.33 → qis-2.1.34}/qis/examples/factsheets/strategy.py +1 -1
  6. {qis-2.1.33 → qis-2.1.34}/qis/examples/factsheets/strategy_benchmark.py +2 -2
  7. qis-2.1.34/qis/examples/perp_pricing.py +56 -0
  8. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/multi_portfolio_data.py +182 -124
  9. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/portfolio_data.py +5 -1
  10. {qis-2.1.33 → qis-2.1.34}/LICENSE.txt +0 -0
  11. {qis-2.1.33 → qis-2.1.34}/README.md +0 -0
  12. {qis-2.1.33 → qis-2.1.34}/qis/__init__.py +0 -0
  13. {qis-2.1.33 → qis-2.1.34}/qis/examples/best_returns.py +0 -0
  14. {qis-2.1.33 → qis-2.1.34}/qis/examples/bond_futures_portfolio.py +0 -0
  15. {qis-2.1.33 → qis-2.1.34}/qis/examples/bootstrap_analysis.py +0 -0
  16. {qis-2.1.33 → qis-2.1.34}/qis/examples/boxplot_conditional_returns.py +0 -0
  17. {qis-2.1.33 → qis-2.1.34}/qis/examples/btc_asset_corr.py +0 -0
  18. {qis-2.1.33 → qis-2.1.34}/qis/examples/constant_notional.py +0 -0
  19. {qis-2.1.33 → qis-2.1.34}/qis/examples/constant_weight_portfolios.py +0 -0
  20. {qis-2.1.33 → qis-2.1.34}/qis/examples/core/perf_bbg_prices.py +0 -0
  21. {qis-2.1.33 → qis-2.1.34}/qis/examples/core/price_plots.py +0 -0
  22. {qis-2.1.33 → qis-2.1.34}/qis/examples/core/us_election.py +0 -0
  23. {qis-2.1.33 → qis-2.1.34}/qis/examples/credit_spreads.py +0 -0
  24. {qis-2.1.33 → qis-2.1.34}/qis/examples/credit_trackers.py +0 -0
  25. {qis-2.1.33 → qis-2.1.34}/qis/examples/europe_futures.py +0 -0
  26. {qis-2.1.33 → qis-2.1.34}/qis/examples/factsheets/pyblogs_reports.py +0 -0
  27. {qis-2.1.33 → qis-2.1.34}/qis/examples/generate_option_rolls.py +0 -0
  28. {qis-2.1.33 → qis-2.1.34}/qis/examples/interpolation_infrequent_returns.py +0 -0
  29. {qis-2.1.33 → qis-2.1.34}/qis/examples/leveraged_strategies.py +0 -0
  30. {qis-2.1.33 → qis-2.1.34}/qis/examples/long_short.py +0 -0
  31. {qis-2.1.33 → qis-2.1.34}/qis/examples/momentum_indices.py +0 -0
  32. {qis-2.1.33 → qis-2.1.34}/qis/examples/ohlc_vol_analysis.py +0 -0
  33. {qis-2.1.33 → qis-2.1.34}/qis/examples/overnight_returns.py +0 -0
  34. {qis-2.1.33 → qis-2.1.34}/qis/examples/perf_external_assets.py +0 -0
  35. {qis-2.1.33 → qis-2.1.34}/qis/examples/readme_performances.py +0 -0
  36. {qis-2.1.33 → qis-2.1.34}/qis/examples/risk_return_frontier.py +0 -0
  37. {qis-2.1.33 → qis-2.1.34}/qis/examples/rolling_performance.py +0 -0
  38. {qis-2.1.33 → qis-2.1.34}/qis/examples/seasonality.py +0 -0
  39. {qis-2.1.33 → qis-2.1.34}/qis/examples/sharpe_vs_sortino.py +0 -0
  40. {qis-2.1.33 → qis-2.1.34}/qis/examples/simulate_quant_strats.py +0 -0
  41. {qis-2.1.33 → qis-2.1.34}/qis/examples/test_ewm.py +0 -0
  42. {qis-2.1.33 → qis-2.1.34}/qis/examples/test_scatter.py +0 -0
  43. {qis-2.1.33 → qis-2.1.34}/qis/examples/try_pybloqs.py +0 -0
  44. {qis-2.1.33 → qis-2.1.34}/qis/examples/universe_corrs.py +0 -0
  45. {qis-2.1.33 → qis-2.1.34}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
  46. {qis-2.1.33 → qis-2.1.34}/qis/examples/vix_conditional_returns.py +0 -0
  47. {qis-2.1.33 → qis-2.1.34}/qis/examples/vix_spy_by_year.py +0 -0
  48. {qis-2.1.33 → qis-2.1.34}/qis/examples/vix_tenor_analysis.py +0 -0
  49. {qis-2.1.33 → qis-2.1.34}/qis/examples/vol_without_weekends.py +0 -0
  50. {qis-2.1.33 → qis-2.1.34}/qis/file_utils.py +0 -0
  51. {qis-2.1.33 → qis-2.1.34}/qis/local_path.py +0 -0
  52. {qis-2.1.33 → qis-2.1.34}/qis/models/README.md +0 -0
  53. {qis-2.1.33 → qis-2.1.34}/qis/models/__init__.py +0 -0
  54. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/__init__.py +0 -0
  55. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/auto_corr.py +0 -0
  56. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/corr_cov_matrix.py +0 -0
  57. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/ewm.py +0 -0
  58. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/ewm_convolution.py +0 -0
  59. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/ewm_factors.py +0 -0
  60. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/ewm_winsor_outliers.py +0 -0
  61. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/pca.py +0 -0
  62. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/plot_correlations.py +0 -0
  63. {qis-2.1.33 → qis-2.1.34}/qis/models/linear/ra_returns.py +0 -0
  64. {qis-2.1.33 → qis-2.1.34}/qis/models/stats/__init__.py +0 -0
  65. {qis-2.1.33 → qis-2.1.34}/qis/models/stats/bootstrap.py +0 -0
  66. {qis-2.1.33 → qis-2.1.34}/qis/models/stats/ohlc_vol.py +0 -0
  67. {qis-2.1.33 → qis-2.1.34}/qis/models/stats/rolling_stats.py +0 -0
  68. {qis-2.1.33 → qis-2.1.34}/qis/models/stats/test_bootstrap.py +0 -0
  69. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/README.md +0 -0
  70. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/__init__.py +0 -0
  71. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/cond_regression.py +0 -0
  72. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/config.py +0 -0
  73. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/desc_table.py +0 -0
  74. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/fx_ops.py +0 -0
  75. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/perf_stats.py +0 -0
  76. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/regime_classifier.py +0 -0
  77. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/returns.py +0 -0
  78. {qis-2.1.33 → qis-2.1.34}/qis/perfstats/timeseries_bfill.py +0 -0
  79. {qis-2.1.33 → qis-2.1.34}/qis/plots/README.md +0 -0
  80. {qis-2.1.33 → qis-2.1.34}/qis/plots/__init__.py +0 -0
  81. {qis-2.1.33 → qis-2.1.34}/qis/plots/bars.py +0 -0
  82. {qis-2.1.33 → qis-2.1.34}/qis/plots/boxplot.py +0 -0
  83. {qis-2.1.33 → qis-2.1.34}/qis/plots/contour.py +0 -0
  84. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/__init__.py +0 -0
  85. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/data_timeseries.py +0 -0
  86. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/desc_table.py +0 -0
  87. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/drawdowns.py +0 -0
  88. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/perf_table.py +0 -0
  89. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/prices.py +0 -0
  90. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/regime_class_table.py +0 -0
  91. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/regime_data.py +0 -0
  92. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/regime_pdf.py +0 -0
  93. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/regime_scatter.py +0 -0
  94. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/returns_heatmap.py +0 -0
  95. {qis-2.1.33 → qis-2.1.34}/qis/plots/derived/returns_scatter.py +0 -0
  96. {qis-2.1.33 → qis-2.1.34}/qis/plots/errorbar.py +0 -0
  97. {qis-2.1.33 → qis-2.1.34}/qis/plots/heatmap.py +0 -0
  98. {qis-2.1.33 → qis-2.1.34}/qis/plots/histogram.py +0 -0
  99. {qis-2.1.33 → qis-2.1.34}/qis/plots/histplot2d.py +0 -0
  100. {qis-2.1.33 → qis-2.1.34}/qis/plots/lineplot.py +0 -0
  101. {qis-2.1.33 → qis-2.1.34}/qis/plots/pie.py +0 -0
  102. {qis-2.1.33 → qis-2.1.34}/qis/plots/qqplot.py +0 -0
  103. {qis-2.1.33 → qis-2.1.34}/qis/plots/reports/__init__.py +0 -0
  104. {qis-2.1.33 → qis-2.1.34}/qis/plots/reports/econ_data_single.py +0 -0
  105. {qis-2.1.33 → qis-2.1.34}/qis/plots/reports/gantt_data_history.py +0 -0
  106. {qis-2.1.33 → qis-2.1.34}/qis/plots/reports/price_history.py +0 -0
  107. {qis-2.1.33 → qis-2.1.34}/qis/plots/reports/utils.py +0 -0
  108. {qis-2.1.33 → qis-2.1.34}/qis/plots/scatter.py +0 -0
  109. {qis-2.1.33 → qis-2.1.34}/qis/plots/stackplot.py +0 -0
  110. {qis-2.1.33 → qis-2.1.34}/qis/plots/table.py +0 -0
  111. {qis-2.1.33 → qis-2.1.34}/qis/plots/time_series.py +0 -0
  112. {qis-2.1.33 → qis-2.1.34}/qis/plots/utils.py +0 -0
  113. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/README.md +0 -0
  114. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/__init__.py +0 -0
  115. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/backtester.py +0 -0
  116. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/ewm_portfolio_risk.py +0 -0
  117. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/__init__.py +0 -0
  118. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/brinson_attribution.py +0 -0
  119. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/config.py +0 -0
  120. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
  121. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
  122. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
  123. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/strategy_benchmark_factsheet.py +0 -0
  124. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
  125. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/strategy_factsheet.py +0 -0
  126. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
  127. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/strats/__init__.py +0 -0
  128. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
  129. {qis-2.1.33 → qis-2.1.34}/qis/portfolio/strats/seasonal_strats.py +0 -0
  130. {qis-2.1.33 → qis-2.1.34}/qis/settings.yaml +0 -0
  131. {qis-2.1.33 → qis-2.1.34}/qis/sql_engine.py +0 -0
  132. {qis-2.1.33 → qis-2.1.34}/qis/test_data.py +0 -0
  133. {qis-2.1.33 → qis-2.1.34}/qis/utils/README.md +0 -0
  134. {qis-2.1.33 → qis-2.1.34}/qis/utils/__init__.py +0 -0
  135. {qis-2.1.33 → qis-2.1.34}/qis/utils/dates.py +0 -0
  136. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_agg.py +0 -0
  137. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_cut.py +0 -0
  138. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_freq.py +0 -0
  139. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_groups.py +0 -0
  140. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_melt.py +0 -0
  141. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_ops.py +0 -0
  142. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_str.py +0 -0
  143. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_to_scores.py +0 -0
  144. {qis-2.1.33 → qis-2.1.34}/qis/utils/df_to_weights.py +0 -0
  145. {qis-2.1.33 → qis-2.1.34}/qis/utils/generic.py +0 -0
  146. {qis-2.1.33 → qis-2.1.34}/qis/utils/np_ops.py +0 -0
  147. {qis-2.1.33 → qis-2.1.34}/qis/utils/ols.py +0 -0
  148. {qis-2.1.33 → qis-2.1.34}/qis/utils/sampling.py +0 -0
  149. {qis-2.1.33 → qis-2.1.34}/qis/utils/struct_ops.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: qis
3
- Version: 2.1.33
3
+ Version: 2.1.34
4
4
  Summary: Implementation of visualisation and reporting analytics for Quantitative Investment Strategies
5
5
  Home-page: https://github.com/ArturSepp/QuantInvestStrats
6
6
  License: LICENSE.txt
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qis"
3
- version = "2.1.33"
3
+ version = "2.1.34"
4
4
  description = "Implementation of visualisation and reporting analytics for Quantitative Investment Strategies"
5
5
  license = "LICENSE.txt"
6
6
  authors = ["Artur Sepp <artursepp@gmail.com>"]
@@ -23,8 +23,10 @@ class UnitTests(Enum):
23
23
 
24
24
  def run_unit_test(unit_test: UnitTests):
25
25
 
26
- prices = None
27
- end_date = '16Oct2024'
26
+ end_date = '10Jan2025' # performance repoting
27
+
28
+ prices = None # if Noe, use yahoo finance data
29
+
28
30
  if unit_test == UnitTests.CORE_ETFS:
29
31
  benchmark = 'SPY'
30
32
  tickers = [benchmark, 'QQQ', 'EEM', 'TLT', 'IEF', 'LQD', 'HYG', 'SHY', 'GLD']
@@ -78,7 +80,6 @@ def run_unit_test(unit_test: UnitTests):
78
80
  prices = prices.rename(tickers, axis=1)
79
81
  time_period = qis.get_time_period(prices)
80
82
 
81
-
82
83
  else:
83
84
  raise NotImplementedError
84
85
 
@@ -66,8 +66,8 @@ class UnitTests(Enum):
66
66
  def run_unit_test(unit_test: UnitTests):
67
67
 
68
68
  if unit_test == UnitTests.VOLPARITY_SPAN:
69
-
70
- time_period = qis.TimePeriod('31Dec2005', '16Oct2024') # time period for portfolio reporting
69
+ # time period for portfolio reporting
70
+ time_period = qis.TimePeriod('31Dec2005', '10Jan2025')
71
71
 
72
72
  prices, benchmark_prices, group_data = fetch_universe_data()
73
73
  multi_portfolio_data = generate_volparity_multi_strategy(prices=prices,
@@ -86,7 +86,7 @@ class UnitTests(Enum):
86
86
 
87
87
  def run_unit_test(unit_test: UnitTests):
88
88
 
89
- time_period = qis.TimePeriod('31Dec2005', '16Oct2024') # time period for portfolio reporting
89
+ time_period = qis.TimePeriod('31Dec2005', '10Jan2025') # time period for portfolio reporting
90
90
  time_period_short = TimePeriod('31Dec2022', time_period.end)
91
91
  rebalancing_costs = 0.0010 # per traded volume
92
92
 
@@ -82,7 +82,7 @@ class UnitTests(Enum):
82
82
  def run_unit_test(unit_test: UnitTests):
83
83
 
84
84
  # time period for portfolio reporting
85
- time_period = qis.TimePeriod('31Dec2006', '06Dec2024')
85
+ time_period = qis.TimePeriod('31Dec2006', '10Jan2025')
86
86
  prices, benchmark_prices, group_data = fetch_universe_data()
87
87
  multi_portfolio_data = generate_volparity_multiportfolio(prices=prices,
88
88
  benchmark_prices=benchmark_prices,
@@ -105,7 +105,7 @@ def run_unit_test(unit_test: UnitTests):
105
105
  add_grouped_exposures=False, # for strategy factsheet
106
106
  add_grouped_cum_pnl=False, # for strategy factsheet
107
107
  **fetch_default_report_kwargs(time_period=time_period,
108
- reporting_frequency=True, add_rates_data=True))
108
+ add_rates_data=True))
109
109
  """
110
110
 
111
111
  """
@@ -0,0 +1,56 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import seaborn as sns
5
+ import qis as qis
6
+ from bbg_fetch import fetch_field_timeseries_per_tickers, fetch_index_members_weights, fetch_bonds_info
7
+
8
+
9
+ index_ticker = 'I31415US Index'
10
+
11
+ end_dates = ['20180101', '20190101', '20200101', '20210101', '20220101', '20230101', '20240101', '20250101', '20250106']
12
+
13
+ data_out = {}
14
+ weighted_resets = []
15
+ for idx, end_date in enumerate(end_dates):
16
+ if idx > 0:
17
+ members = fetch_index_members_weights(index_ticker, END_DATE_OVERRIDE=end_dates[idx-1])
18
+ corp_index = [f"{x} corp" for x in members.index]
19
+ members.index = corp_index
20
+ """
21
+ amt_outstanding = fetch_bonds_info(isins=members.index.to_list(), fields=['amt_outstanding'],
22
+ END_DATE_OVERRIDE=end_dates[idx-1])['amt_outstanding']
23
+ amt_outstanding = amt_outstanding.loc[members.index]
24
+ amt_outstanding.index = corp_index
25
+ """
26
+ prices = fetch_field_timeseries_per_tickers(tickers=corp_index,
27
+ start_date=pd.Timestamp(end_dates[idx-1]),
28
+ end_date=pd.Timestamp(end_date),
29
+ freq='B')
30
+ prices = prices.resample('W-WED').last()
31
+ # market_value = prices.multiply(amt_outstanding, axis=1)
32
+ #market_value.divide(np.nansum(market_value, axis=1, keepdims=True), axis=1)
33
+ is_reset = (prices > 100).astype(float)
34
+ market_weights = members.iloc[:, 0]
35
+ weighted_reset = is_reset.multiply(market_weights, axis=1)
36
+ weighted_reset = weighted_reset.sum(1)
37
+ weighted_resets.append(weighted_reset)
38
+ data_out[f"{end_dates[idx-1]} members"] = members
39
+ data_out[f"{end_date} prices"] = prices
40
+ data_out[f"{end_date} is_reset"] = is_reset
41
+
42
+ weighted_resets = pd.concat(weighted_resets)
43
+ weighted_resets = weighted_resets.loc[~weighted_resets.index.duplicated(keep='first')]
44
+ weighted_resets = weighted_resets.to_frame('weighted_par_reset %')
45
+ print(weighted_resets)
46
+
47
+ data_out['weighted_resets'] = weighted_resets
48
+ qis.save_df_to_excel(data=data_out, file_name='perp_pricing')
49
+
50
+ with sns.axes_style("darkgrid"):
51
+ fig, ax = plt.subplots(1, 1, figsize=(15, 8), tight_layout=True)
52
+ qis.plot_time_series(weighted_resets,
53
+ title='weighted_par_reset',
54
+ ax=ax)
55
+
56
+ plt.show()
@@ -7,8 +7,7 @@ import numpy as np
7
7
  import pandas as pd
8
8
  import matplotlib.pyplot as plt
9
9
  from dataclasses import dataclass
10
- from typing import List, Optional, Tuple, Union
11
-
10
+ from typing import List, Optional, Tuple, Union, Dict
12
11
  # qis
13
12
  import qis as qis
14
13
  from qis import PerfParams, PerfStat, RegimeData, BenchmarkReturnsQuantileRegimeSpecs, TimePeriod, RollingPerfStat
@@ -17,7 +16,6 @@ import qis.utils.df_groups as dfg
17
16
  import qis.perfstats.returns as ret
18
17
  import qis.perfstats.perf_stats as rpt
19
18
  import qis.perfstats.regime_classifier as rcl
20
-
21
19
  # plots
22
20
  import qis.plots.time_series as pts
23
21
  import qis.plots.derived.prices as ppd
@@ -28,6 +26,7 @@ import qis.plots.derived.drawdowns as cdr
28
26
  from qis.portfolio.portfolio_data import PortfolioData, AttributionMetric
29
27
 
30
28
 
29
+ # default perf params
31
30
  PERF_PARAMS = PerfParams(freq='W-WED')
32
31
  REGIME_PARAMS = BenchmarkReturnsQuantileRegimeSpecs(freq='ME')
33
32
 
@@ -36,9 +35,15 @@ REGIME_PARAMS = BenchmarkReturnsQuantileRegimeSpecs(freq='ME')
36
35
  class MultiPortfolioData:
37
36
  """
38
37
  data structure to unify multi portfolio reporting
38
+ portfolio_datas: List[PortfolioData]
39
+ benchmark_prices: Union[pd.DataFrame, pd.Series] = None # Optional
40
+ pd_covars: Dict[pd.Timestamp, pd.DataFrame] = None # annualised covariance matrix
41
+ for investable universe for computing tracking error
39
42
  """
40
43
  portfolio_datas: List[PortfolioData]
41
44
  benchmark_prices: Union[pd.DataFrame, pd.Series] = None
45
+ pd_covars: Dict[pd.Timestamp, pd.DataFrame] = None
46
+ navs: pd.DataFrame = None # computed internally
42
47
 
43
48
  def __post_init__(self):
44
49
  # default frequency is freq of backtests, can be non for strats at different freqs
@@ -82,7 +87,7 @@ class MultiPortfolioData:
82
87
  navs = pd.concat([self.benchmark_prices[benchmark].reindex(index=navs.index).ffill(), navs], axis=1)
83
88
  elif add_benchmarks_to_navs:
84
89
  navs = pd.concat([navs, self.benchmark_prices.reindex(index=navs.index).ffill()], axis=1).ffill()
85
-
90
+
86
91
  if time_period is not None:
87
92
  navs = time_period.locate(navs)
88
93
  return navs
@@ -102,16 +107,170 @@ class MultiPortfolioData:
102
107
  time_period: TimePeriod = None,
103
108
  is_add_group_total: bool = False
104
109
  ) -> pd.DataFrame:
105
- prices = self.portfolio_datas[portfolio_idx].get_group_navs(time_period=time_period, is_add_group_total=is_add_group_total)
110
+ prices = self.portfolio_datas[portfolio_idx].get_group_navs(time_period=time_period,
111
+ is_add_group_total=is_add_group_total)
106
112
  if benchmark is not None:
107
113
  benchmark_price = self.get_benchmark_price(benchmark=self.benchmark_prices.columns[0],
108
114
  time_period=time_period)
109
115
  prices = pd.concat([prices, benchmark_price], axis=1)
110
116
  return prices
111
117
 
112
- """
113
- plot methods
114
- """
118
+ def get_ra_perf_table(self,
119
+ benchmark: str = None,
120
+ time_period: TimePeriod = None,
121
+ drop_benchmark: bool = False,
122
+ is_convert_to_str: bool = True,
123
+ perf_params: PerfParams = PERF_PARAMS,
124
+ perf_columns: List[PerfStat] = rpt.BENCHMARK_TABLE_COLUMNS,
125
+ **kwargs
126
+ ) -> pd.DataFrame:
127
+ if benchmark is None:
128
+ benchmark = self.benchmark_prices.columns[0]
129
+ prices = self.get_navs(benchmark=benchmark, time_period=time_period)
130
+ ra_perf_table = ppt.get_ra_perf_benchmark_columns(prices=prices,
131
+ benchmark=benchmark,
132
+ drop_benchmark=drop_benchmark,
133
+ is_convert_to_str=is_convert_to_str,
134
+ perf_params=perf_params,
135
+ perf_columns=perf_columns,
136
+ **kwargs)
137
+ return ra_perf_table
138
+
139
+ def get_aligned_weights(self,
140
+ strategy_idx: int = 0,
141
+ benchmark_idx: int = 1,
142
+ freq: Optional[str] = 'B',
143
+ time_period: TimePeriod = None,
144
+ is_grouped: bool = False,
145
+ **kwargs
146
+ ) -> Tuple[pd.DataFrame, pd.DataFrame]:
147
+ strategy_weights = self.portfolio_datas[strategy_idx].get_weights(time_period=time_period, freq=None,
148
+ is_input_weights=True, is_grouped=is_grouped)
149
+ benchmark_weights = self.portfolio_datas[benchmark_idx].get_weights(time_period=time_period, freq=None,
150
+ is_input_weights=True,
151
+ is_grouped=is_grouped)
152
+ tickers_union = qis.merge_lists_unique(list1=strategy_weights.columns.to_list(),
153
+ list2=benchmark_weights.columns.to_list())
154
+ # replace with ac order of benchmark
155
+ if is_grouped and self.portfolio_datas[benchmark_idx].group_order is not None:
156
+ tickers_union = self.portfolio_datas[benchmark_idx].group_order
157
+ strategy_weights = strategy_weights.reindex(columns=tickers_union)
158
+ benchmark_weights = benchmark_weights.reindex(columns=tickers_union)
159
+ return strategy_weights, benchmark_weights
160
+
161
+ def get_aligned_turnover(self,
162
+ strategy_idx: int = 0,
163
+ benchmark_idx: int = 1,
164
+ turnover_rolling_period: Optional[int] = 260,
165
+ freq_turnover: Optional[str] = 'B',
166
+ time_period: TimePeriod = None,
167
+ is_grouped: bool = False,
168
+ **kwargs
169
+ ) -> Tuple[pd.DataFrame, pd.DataFrame]:
170
+
171
+ strategy_turnover = self.portfolio_datas[strategy_idx].get_turnover(time_period=time_period, freq=freq_turnover,
172
+ roll_period=turnover_rolling_period,
173
+ add_total=False, is_grouped=is_grouped)
174
+ benchmark_turnover = self.portfolio_datas[benchmark_idx].get_turnover(time_period=time_period,
175
+ freq=freq_turnover,
176
+ roll_period=turnover_rolling_period,
177
+ add_total=False, is_grouped=is_grouped)
178
+ tickers_union = qis.merge_lists_unique(list1=strategy_turnover.columns.to_list(),
179
+ list2=benchmark_turnover.columns.to_list())
180
+ # replace with ac order of benchmark
181
+ if is_grouped and self.portfolio_datas[benchmark_idx].group_order is not None:
182
+ tickers_union = self.portfolio_datas[benchmark_idx].group_order
183
+ strategy_turnover = strategy_turnover.reindex(columns=tickers_union)
184
+ benchmark_turnover = benchmark_turnover.reindex(columns=tickers_union)
185
+ return strategy_turnover, benchmark_turnover
186
+
187
+ def compute_tracking_error_implied_by_covar(self,
188
+ strategy_idx: int = 0,
189
+ benchmark_idx: int = 1
190
+ ) -> pd.Series:
191
+ """
192
+ compute Ex ante tracking error =
193
+ (strategy_weights - strategy_weights) @ covar @ (strategy_weights - strategy_weights).T
194
+ """
195
+ if self.pd_covars is None:
196
+ raise ValueError(f"must pass pd_covars")
197
+ strategy_weights = self.portfolio_datas[strategy_idx].get_weights(freq=None, is_input_weights=True)
198
+ benchmark_weights = self.portfolio_datas[benchmark_idx].get_weights(freq=None, is_input_weights=True)
199
+ covar_index = list(self.pd_covars.keys())
200
+ investable_assets = self.pd_covars[covar_index[0]].columns.to_list()
201
+ strategy_weights = strategy_weights.reindex(index=covar_index, columns=investable_assets).ffill().fillna(0.0)
202
+ benchmark_weights = benchmark_weights.reindex(index=covar_index, columns=investable_assets).ffill().fillna(0.0)
203
+
204
+ weight_diffs = benchmark_weights - strategy_weights
205
+ tracking_error = {}
206
+ for date, pd_covar in self.pd_covars.items():
207
+ w = weight_diffs.loc[date]
208
+ tracking_error[date] = np.sqrt(w @ pd_covar @ w.T)
209
+ tracking_error = pd.Series(tracking_error)
210
+ return tracking_error
211
+
212
+ def compute_tracking_error_table(self,
213
+ strategy_idx: int = 0,
214
+ benchmark_idx: int = 1,
215
+ freq: Optional[str] = 'B',
216
+ time_period: TimePeriod = None,
217
+ af: float = 260,
218
+ is_norm_costs: bool = True,
219
+ **kwargs
220
+ ) -> pd.DataFrame:
221
+ """
222
+ compute realised tracking error = mean(P&L diff) / std(P&L diff)
223
+ """
224
+ strategy_pnl = self.portfolio_datas[strategy_idx].get_attribution_table_by_instrument(time_period=time_period,
225
+ freq=freq)
226
+ benchmark_pnl = self.portfolio_datas[benchmark_idx].get_attribution_table_by_instrument(time_period=time_period,
227
+ freq=freq)
228
+
229
+ tickers_union = qis.merge_lists_unique(list1=strategy_pnl.columns.to_list(),
230
+ list2=benchmark_pnl.columns.to_list())
231
+ strategy_pnl = strategy_pnl.reindex(columns=tickers_union)
232
+ benchmark_pnl = benchmark_pnl.reindex(columns=tickers_union).reindex(index=strategy_pnl.index)
233
+ pnl_diff = strategy_pnl.subtract(benchmark_pnl)
234
+
235
+ strategy_weights = self.portfolio_datas[strategy_idx].get_weights(time_period=time_period, freq=None,
236
+ is_input_weights=True)
237
+ strategy_turnover = self.portfolio_datas[strategy_idx].get_turnover(time_period=time_period, freq=None,
238
+ roll_period=None, add_total=False)
239
+ strategy_cost = self.portfolio_datas[strategy_idx].get_costs(time_period=time_period, freq=freq,
240
+ roll_period=None,
241
+ add_total=False, is_norm_costs=is_norm_costs)
242
+ strategy_ticker = self.portfolio_datas[strategy_idx].ticker
243
+
244
+ benchmark_weights = self.portfolio_datas[benchmark_idx].get_weights(time_period=time_period, freq=None,
245
+ is_input_weights=True)
246
+ benchmark_turnover = self.portfolio_datas[benchmark_idx].get_turnover(time_period=time_period, freq=None,
247
+ roll_period=None, add_total=False)
248
+ benchmark_cost = self.portfolio_datas[benchmark_idx].get_costs(time_period=time_period, freq=freq,
249
+ roll_period=None,
250
+ add_total=False, is_norm_costs=is_norm_costs)
251
+ benchmark_ticker = self.portfolio_datas[benchmark_idx].ticker
252
+
253
+ # compute stats
254
+ total_strategy_perf = strategy_pnl.cumsum(0).iloc[-1, :].rename(f"{strategy_ticker} total perf")
255
+ total_benchmark_perf = benchmark_pnl.cumsum(0).iloc[-1, :].rename(f"{benchmark_ticker} total perf")
256
+ total_diff = total_strategy_perf.subtract(total_benchmark_perf).rename(
257
+ f"{strategy_ticker}-{benchmark_ticker} total perf")
258
+
259
+ tre = pd.Series(np.nanmean(pnl_diff, axis=0) / np.nanstd(pnl_diff, axis=0), index=tickers_union, name='TRE')
260
+
261
+ tre_table = pd.concat([total_diff, tre,
262
+ total_strategy_perf, total_benchmark_perf,
263
+ af * strategy_turnover.mean(0).rename(f"{strategy_ticker} an turnover"),
264
+ af * benchmark_turnover.mean(0).rename(f"{benchmark_ticker} an turnover"),
265
+ af * strategy_cost.mean(0).rename(f"{strategy_ticker} an cost"),
266
+ af * benchmark_cost.mean(0).rename(f"{benchmark_ticker} an cost"),
267
+ ], axis=1)
268
+
269
+ return tre_table
270
+
271
+ # """
272
+ # plot methods
273
+ # """
115
274
  def add_regime_shadows(self,
116
275
  ax: plt.Subplot,
117
276
  regime_benchmark: str,
@@ -144,7 +303,8 @@ class MultiPortfolioData:
144
303
  ax=ax,
145
304
  **kwargs)
146
305
  if regime_benchmark is not None:
147
- self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index, regime_params=regime_params)
306
+ self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index,
307
+ regime_params=regime_params)
148
308
 
149
309
  def plot_rolling_perf(self,
150
310
  rolling_perf_stat: RollingPerfStat = RollingPerfStat.SHARPE,
@@ -172,6 +332,7 @@ class MultiPortfolioData:
172
332
  time_period=time_period,
173
333
  roll_periods=sharpe_rolling_window,
174
334
  legend_stats=legend_stats,
335
+ title=sharpe_title,
175
336
  trend_line=None, # qis.TrendLine.ZERO_SHADOWS,
176
337
  ax=ax,
177
338
  **kwargs)
@@ -180,7 +341,7 @@ class MultiPortfolioData:
180
341
  self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index,
181
342
  regime_params=regime_params)
182
343
  return fig
183
-
344
+
184
345
  def plot_periodic_returns(self,
185
346
  time_period: TimePeriod = None,
186
347
  heatmap_freq: str = 'YE',
@@ -243,7 +404,8 @@ class MultiPortfolioData:
243
404
  prices = self.get_navs(time_period=time_period, add_benchmarks_to_navs=add_benchmarks_to_navs)
244
405
  cdr.plot_rolling_drawdowns(prices=prices, dd_legend_type=dd_legend_type, ax=ax, **kwargs)
245
406
  if regime_benchmark is not None:
246
- self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index, regime_params=regime_params)
407
+ self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index,
408
+ regime_params=regime_params)
247
409
 
248
410
  def plot_rolling_time_under_water(self,
249
411
  time_period: TimePeriod = None,
@@ -256,28 +418,8 @@ class MultiPortfolioData:
256
418
  prices = self.get_navs(time_period=time_period, add_benchmarks_to_navs=add_benchmarks_to_navs)
257
419
  cdr.plot_rolling_time_under_water(prices=prices, dd_legend_type=dd_legend_type, ax=ax, **kwargs)
258
420
  if regime_benchmark is not None:
259
- self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index, regime_params=regime_params)
260
-
261
- def get_ra_perf_table(self,
262
- benchmark: str = None,
263
- time_period: TimePeriod = None,
264
- drop_benchmark: bool = False,
265
- is_convert_to_str: bool = True,
266
- perf_params: PerfParams = PERF_PARAMS,
267
- perf_columns: List[PerfStat] = rpt.BENCHMARK_TABLE_COLUMNS,
268
- **kwargs
269
- ) -> pd.DataFrame:
270
- if benchmark is None:
271
- benchmark = self.benchmark_prices.columns[0]
272
- prices = self.get_navs(benchmark=benchmark, time_period=time_period)
273
- ra_perf_table = ppt.get_ra_perf_benchmark_columns(prices=prices,
274
- benchmark=benchmark,
275
- drop_benchmark=drop_benchmark,
276
- is_convert_to_str=is_convert_to_str,
277
- perf_params=perf_params,
278
- perf_columns=perf_columns,
279
- **kwargs)
280
- return ra_perf_table
421
+ self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=prices.index,
422
+ regime_params=regime_params)
281
423
 
282
424
  def plot_ra_perf_table(self,
283
425
  benchmark: str = None,
@@ -291,7 +433,8 @@ class MultiPortfolioData:
291
433
  if benchmark is None:
292
434
  benchmark = self.benchmark_prices.columns[0]
293
435
  prices = self.get_navs(benchmark=benchmark, time_period=time_period)
294
- ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to {benchmark}: {qis.get_time_period(prices).to_str()}"
436
+ ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to {benchmark}: " \
437
+ f"{qis.get_time_period(prices).to_str()}"
295
438
  ppt.plot_ra_perf_table_benchmark(prices=prices,
296
439
  benchmark=benchmark,
297
440
  perf_params=perf_params,
@@ -341,11 +484,12 @@ class MultiPortfolioData:
341
484
  benchmark_price = benchmark_price.reindex(index=strategy_prices.index, method='ffill')
342
485
  if add_ac: # otherwise tables look too bad
343
486
  ac_prices = pd.concat(ac_prices, axis=1)
344
- prices = pd.concat([benchmark_price, strategy_prices, ac_prices],axis=1)
487
+ prices = pd.concat([benchmark_price, strategy_prices, ac_prices], axis=1)
345
488
  else:
346
489
  prices = pd.concat([strategy_prices, benchmark_price], axis=1)
347
490
 
348
- ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to {benchmark_price.name}: {qis.get_time_period(prices).to_str()}"
491
+ ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to " \
492
+ f"{benchmark_price.name}: {qis.get_time_period(prices).to_str()}"
349
493
  ppt.plot_ra_perf_table_benchmark(prices=prices,
350
494
  benchmark=str(benchmark_price.name),
351
495
  perf_params=perf_params,
@@ -476,7 +620,8 @@ class MultiPortfolioData:
476
620
  **kwargs) -> None:
477
621
  costs = []
478
622
  for portfolio in self.portfolio_datas:
479
- costs.append(portfolio.get_costs(roll_period=cost_rolling_period, freq=freq_cost, is_agg=True, is_norm_costs=is_norm_costs).rename(portfolio.nav.name))
623
+ costs.append(portfolio.get_costs(roll_period=cost_rolling_period, freq=freq_cost, is_agg=True,
624
+ is_norm_costs=is_norm_costs).rename(portfolio.nav.name))
480
625
  costs = pd.concat(costs, axis=1)
481
626
  if time_period is not None:
482
627
  costs = time_period.locate(costs)
@@ -814,90 +959,3 @@ class MultiPortfolioData:
814
959
  regime_params=regime_params)
815
960
 
816
961
  return figs
817
-
818
- def compute_tracking_error_table(self,
819
- strategy_idx: int = 0,
820
- benchmark_idx: int = 1,
821
- freq: Optional[str] = 'B',
822
- time_period: TimePeriod = None,
823
- af: float = 260,
824
- is_norm_costs: bool = True,
825
- **kwargs
826
- ) -> pd.DataFrame:
827
-
828
- strategy_pnl = self.portfolio_datas[strategy_idx].get_attribution_table_by_instrument(time_period=time_period, freq=freq)
829
- benchmark_pnl = self.portfolio_datas[benchmark_idx].get_attribution_table_by_instrument(time_period=time_period, freq=freq)
830
-
831
- tickers_union = qis.merge_lists_unique(list1=strategy_pnl.columns.to_list(), list2=benchmark_pnl.columns.to_list())
832
- strategy_pnl = strategy_pnl.reindex(columns=tickers_union)
833
- benchmark_pnl = benchmark_pnl.reindex(columns=tickers_union).reindex(index=strategy_pnl.index)
834
- pnl_diff = strategy_pnl.subtract(benchmark_pnl)
835
-
836
- strategy_weights = self.portfolio_datas[strategy_idx].get_weights(time_period=time_period, freq=None, is_input_weights=True)
837
- strategy_turnover = self.portfolio_datas[strategy_idx].get_turnover(time_period=time_period, freq=None, roll_period=None, add_total=False)
838
- strategy_cost = self.portfolio_datas[strategy_idx].get_costs(time_period=time_period, freq=freq, roll_period=None,
839
- add_total=False, is_norm_costs=is_norm_costs)
840
- strategy_ticker = self.portfolio_datas[strategy_idx].ticker
841
-
842
- benchmark_weights = self.portfolio_datas[benchmark_idx].get_weights(time_period=time_period, freq=None, is_input_weights=True)
843
- benchmark_turnover = self.portfolio_datas[benchmark_idx].get_turnover(time_period=time_period, freq=None, roll_period=None, add_total=False)
844
- benchmark_cost = self.portfolio_datas[benchmark_idx].get_costs(time_period=time_period, freq=freq, roll_period=None,
845
- add_total=False, is_norm_costs=is_norm_costs)
846
- benchmark_ticker = self.portfolio_datas[benchmark_idx].ticker
847
-
848
- # compute stats
849
- total_strategy_perf = strategy_pnl.cumsum(0).iloc[-1, :].rename(f"{strategy_ticker} total perf")
850
- total_benchmark_perf = benchmark_pnl.cumsum(0).iloc[-1, :].rename(f"{benchmark_ticker} total perf")
851
- total_diff = total_strategy_perf.subtract(total_benchmark_perf).rename(f"{strategy_ticker}-{benchmark_ticker} total perf")
852
-
853
- tre = pd.Series(np.nanmean(pnl_diff, axis=0) / np.nanstd(pnl_diff, axis=0), index=tickers_union, name='TRE')
854
-
855
- tre_table = pd.concat([total_diff, tre,
856
- total_strategy_perf, total_benchmark_perf,
857
- af*strategy_turnover.mean(0).rename(f"{strategy_ticker} an turnover"),
858
- af*benchmark_turnover.mean(0).rename(f"{benchmark_ticker} an turnover"),
859
- af*strategy_cost.mean(0).rename(f"{strategy_ticker} an cost"),
860
- af*benchmark_cost.mean(0).rename(f"{benchmark_ticker} an cost"),
861
- ], axis=1)
862
-
863
- return tre_table
864
-
865
- def get_aligned_weights(self,
866
- strategy_idx: int = 0,
867
- benchmark_idx: int = 1,
868
- freq: Optional[str] = 'B',
869
- time_period: TimePeriod = None,
870
- is_grouped: bool = False,
871
- **kwargs
872
- ) -> Tuple[pd.DataFrame, pd.DataFrame]:
873
- strategy_weights = self.portfolio_datas[strategy_idx].get_weights(time_period=time_period, freq=None, is_input_weights=True, is_grouped=is_grouped)
874
- benchmark_weights = self.portfolio_datas[benchmark_idx].get_weights(time_period=time_period, freq=None, is_input_weights=True, is_grouped=is_grouped)
875
- tickers_union = qis.merge_lists_unique(list1=strategy_weights.columns.to_list(), list2=benchmark_weights.columns.to_list())
876
- if is_grouped and self.portfolio_datas[benchmark_idx].group_order is not None: # replace with ac order of benchmark
877
- tickers_union = self.portfolio_datas[benchmark_idx].group_order
878
- strategy_weights = strategy_weights.reindex(columns=tickers_union)
879
- benchmark_weights = benchmark_weights.reindex(columns=tickers_union)
880
- return strategy_weights, benchmark_weights
881
-
882
- def get_aligned_turnover(self,
883
- strategy_idx: int = 0,
884
- benchmark_idx: int = 1,
885
- turnover_rolling_period: Optional[int] = 260,
886
- freq_turnover: Optional[str] = 'B',
887
- time_period: TimePeriod = None,
888
- is_grouped: bool = False,
889
- **kwargs
890
- ) -> Tuple[pd.DataFrame, pd.DataFrame]:
891
-
892
- strategy_turnover = self.portfolio_datas[strategy_idx].get_turnover(time_period=time_period, freq=freq_turnover,
893
- roll_period=turnover_rolling_period,
894
- add_total=False, is_grouped=is_grouped)
895
- benchmark_turnover = self.portfolio_datas[benchmark_idx].get_turnover(time_period=time_period, freq=freq_turnover,
896
- roll_period=turnover_rolling_period,
897
- add_total=False, is_grouped=is_grouped)
898
- tickers_union = qis.merge_lists_unique(list1=strategy_turnover.columns.to_list(), list2=benchmark_turnover.columns.to_list())
899
- if is_grouped and self.portfolio_datas[benchmark_idx].group_order is not None: # replace with ac order of benchmark
900
- tickers_union = self.portfolio_datas[benchmark_idx].group_order
901
- strategy_turnover = strategy_turnover.reindex(columns=tickers_union)
902
- benchmark_turnover = benchmark_turnover.reindex(columns=tickers_union)
903
- return strategy_turnover, benchmark_turnover
@@ -1216,7 +1216,11 @@ class PortfolioData:
1216
1216
  ) -> None:
1217
1217
  weights = self.get_weights(is_input_weights=True, freq=None, is_grouped=is_grouped)
1218
1218
  if time_period is not None:
1219
- weights_1 = time_period.locate(weights).iloc[0, :]
1219
+ weights_1 = time_period.locate(weights)
1220
+ if len(weights_1.index) > 0:
1221
+ weights_1 = weights_1.iloc[0, :]
1222
+ else:
1223
+ weights_1 = weights.iloc[-1, :]
1220
1224
  else:
1221
1225
  weights_1 = weights.iloc[-1, :]
1222
1226
  if add_top_bar_values is None:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes