guohanghui commited on
Commit
cbe58e6
·
verified ·
1 Parent(s): 8e14879

Update pyfolio/mcp_output/mcp_plugin/mcp_service.py

Browse files
pyfolio/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -2,11 +2,14 @@ import os
2
  import sys
3
  import pandas as pd
4
  from typing import Optional, Dict, List, Any
 
 
5
 
6
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
7
  sys.path.insert(0, source_path)
8
 
9
  from fastmcp import FastMCP
 
10
  from pyfolio.tears import (
11
  create_full_tear_sheet,
12
  create_simple_tear_sheet,
@@ -44,46 +47,122 @@ def _convert_to_dataframe(data: Dict[str, Any]) -> pd.DataFrame:
44
  return pd.DataFrame(data)
45
  return data
46
 
47
- @mcp.tool(name="generate_full_tear_sheet", description="Generate a comprehensive tear sheet for portfolio analysis.")
48
- def generate_full_tear_sheet(returns: list, positions: dict = None, transactions: dict = None, benchmark_rets: list = None) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  """
50
- Generate a comprehensive tear sheet for portfolio analysis.
51
 
52
  Args:
53
  returns: List of daily returns
54
- positions: Dictionary of positions over time (optional)
55
- transactions: Dictionary of transactions (optional)
56
- benchmark_rets: List of benchmark returns (optional)
57
 
58
  Returns:
59
- Dictionary with success status and result/error message
60
  """
61
  try:
62
- returns_series = _convert_to_series(returns, "returns")
63
- positions_df = _convert_to_dataframe(positions) if positions else None
64
- transactions_df = _convert_to_dataframe(transactions) if transactions else None
65
- benchmark_series = _convert_to_series(benchmark_rets, "benchmark") if benchmark_rets else None
66
 
67
- create_full_tear_sheet(returns_series, positions_df, transactions_df, benchmark_series)
68
- return {"success": True, "result": "Full tear sheet generated successfully.", "error": None}
 
 
 
69
  except Exception as e:
70
  return {"success": False, "result": None, "error": str(e)}
71
 
72
- @mcp.tool(name="generate_simple_tear_sheet", description="Generate a basic tear sheet for portfolio analysis.")
73
  def generate_simple_tear_sheet(returns: list) -> dict:
74
  """
75
- Generate a basic tear sheet for portfolio analysis.
76
 
77
  Args:
78
  returns: List of daily returns
79
 
80
  Returns:
81
- Dictionary with success status and result/error message
82
  """
83
  try:
84
  returns_series = _convert_to_series(returns)
85
- create_simple_tear_sheet(returns_series)
86
- return {"success": True, "result": "Simple tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  except Exception as e:
88
  return {"success": False, "result": None, "error": str(e)}
89
 
@@ -96,12 +175,28 @@ def generate_returns_tear_sheet(returns: list) -> dict:
96
  returns: List of daily returns
97
 
98
  Returns:
99
- Dictionary with success status and result/error message
100
  """
101
  try:
102
  returns_series = _convert_to_series(returns)
103
- create_returns_tear_sheet(returns_series)
104
- return {"success": True, "result": "Returns tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  except Exception as e:
106
  return {"success": False, "result": None, "error": str(e)}
107
 
@@ -118,8 +213,19 @@ def generate_position_tear_sheet(positions: dict) -> dict:
118
  """
119
  try:
120
  positions_df = _convert_to_dataframe(positions)
121
- create_position_tear_sheet(positions_df)
122
- return {"success": True, "result": "Position tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
123
  except Exception as e:
124
  return {"success": False, "result": None, "error": str(e)}
125
 
@@ -136,8 +242,19 @@ def generate_transaction_tear_sheet(transactions: dict) -> dict:
136
  """
137
  try:
138
  transactions_df = _convert_to_dataframe(transactions)
139
- create_txn_tear_sheet(transactions_df)
140
- return {"success": True, "result": "Transaction tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
141
  except Exception as e:
142
  return {"success": False, "result": None, "error": str(e)}
143
 
@@ -154,8 +271,19 @@ def generate_round_trip_tear_sheet(round_trips: dict) -> dict:
154
  """
155
  try:
156
  round_trips_df = _convert_to_dataframe(round_trips)
157
- create_round_trip_tear_sheet(round_trips_df)
158
- return {"success": True, "result": "Round trip tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
159
  except Exception as e:
160
  return {"success": False, "result": None, "error": str(e)}
161
 
@@ -173,8 +301,19 @@ def generate_interesting_times_tear_sheet(returns: list, events: list) -> dict:
173
  """
174
  try:
175
  returns_series = _convert_to_series(returns)
176
- create_interesting_times_tear_sheet(returns_series, events)
177
- return {"success": True, "result": "Interesting times tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
178
  except Exception as e:
179
  return {"success": False, "result": None, "error": str(e)}
180
 
@@ -193,8 +332,19 @@ def generate_capacity_tear_sheet(returns: list, positions: dict) -> dict:
193
  try:
194
  returns_series = _convert_to_series(returns)
195
  positions_df = _convert_to_dataframe(positions)
196
- create_capacity_tear_sheet(returns_series, positions_df)
197
- return {"success": True, "result": "Capacity tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
198
  except Exception as e:
199
  return {"success": False, "result": None, "error": str(e)}
200
 
@@ -213,8 +363,19 @@ def generate_performance_attribution_tear_sheet(returns: list, factors: dict) ->
213
  try:
214
  returns_series = _convert_to_series(returns)
215
  factors_df = _convert_to_dataframe(factors)
216
- create_perf_attrib_tear_sheet(returns_series, factors_df)
217
- return {"success": True, "result": "Performance attribution tear sheet generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
218
  except Exception as e:
219
  return {"success": False, "result": None, "error": str(e)}
220
 
 
2
  import sys
3
  import pandas as pd
4
  from typing import Optional, Dict, List, Any
5
+ import io
6
+ import contextlib
7
 
8
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
9
  sys.path.insert(0, source_path)
10
 
11
  from fastmcp import FastMCP
12
+ from pyfolio import timeseries
13
  from pyfolio.tears import (
14
  create_full_tear_sheet,
15
  create_simple_tear_sheet,
 
47
  return pd.DataFrame(data)
48
  return data
49
 
50
+ def _calculate_performance_stats(returns_series: pd.Series) -> Dict[str, float]:
51
+ """Calculate key performance statistics."""
52
+ try:
53
+ stats = {}
54
+ stats['total_return'] = timeseries.cum_returns_final(returns_series)
55
+ stats['annual_return'] = timeseries.annual_return(returns_series)
56
+ stats['annual_volatility'] = timeseries.annual_volatility(returns_series)
57
+ stats['sharpe_ratio'] = timeseries.sharpe_ratio(returns_series)
58
+ stats['max_drawdown'] = timeseries.max_drawdown(returns_series)
59
+ stats['calmar_ratio'] = timeseries.calmar_ratio(returns_series)
60
+ stats['stability'] = timeseries.stability_of_timeseries(returns_series)
61
+ stats['omega_ratio'] = timeseries.omega_ratio(returns_series)
62
+ stats['sortino_ratio'] = timeseries.sortino_ratio(returns_series)
63
+ stats['skew'] = timeseries.stats.skew(returns_series)
64
+ stats['kurtosis'] = timeseries.stats.kurtosis(returns_series)
65
+ stats['tail_ratio'] = timeseries.tail_ratio(returns_series)
66
+
67
+ # Convert numpy types to Python types for JSON serialization
68
+ return {k: float(v) if pd.notna(v) else None for k, v in stats.items()}
69
+ except Exception as e:
70
+ return {"error": str(e)}
71
+
72
+ @mcp.tool(name="calculate_statistics", description="Calculate key performance statistics for a returns series.")
73
+ def calculate_statistics(returns: list) -> dict:
74
  """
75
+ Calculate key performance statistics for a returns series.
76
 
77
  Args:
78
  returns: List of daily returns
 
 
 
79
 
80
  Returns:
81
+ Dictionary with performance statistics including total return, Sharpe ratio, max drawdown, etc.
82
  """
83
  try:
84
+ returns_series = _convert_to_series(returns)
85
+ stats = _calculate_performance_stats(returns_series)
 
 
86
 
87
+ return {
88
+ "success": True,
89
+ "result": stats,
90
+ "error": None
91
+ }
92
  except Exception as e:
93
  return {"success": False, "result": None, "error": str(e)}
94
 
95
+ @mcp.tool(name="generate_simple_tear_sheet", description="Generate a basic tear sheet with performance statistics.")
96
  def generate_simple_tear_sheet(returns: list) -> dict:
97
  """
98
+ Generate a basic tear sheet for portfolio analysis with performance statistics.
99
 
100
  Args:
101
  returns: List of daily returns
102
 
103
  Returns:
104
+ Dictionary with success status and performance statistics
105
  """
106
  try:
107
  returns_series = _convert_to_series(returns)
108
+
109
+ # Calculate performance statistics
110
+ stats = _calculate_performance_stats(returns_series)
111
+
112
+ # Capture tear sheet output
113
+ output = io.StringIO()
114
+ with contextlib.redirect_stdout(output):
115
+ create_simple_tear_sheet(returns_series)
116
+
117
+ return {
118
+ "success": True,
119
+ "result": {
120
+ "statistics": stats,
121
+ "message": "Simple tear sheet generated successfully.",
122
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
123
+ },
124
+ "error": None
125
+ }
126
+ except Exception as e:
127
+ return {"success": False, "result": None, "error": str(e)}
128
+
129
+ @mcp.tool(name="generate_full_tear_sheet", description="Generate a comprehensive tear sheet for portfolio analysis.")
130
+ def generate_full_tear_sheet(returns: list, positions: dict = None, transactions: dict = None, benchmark_rets: list = None) -> dict:
131
+ """
132
+ Generate a comprehensive tear sheet for portfolio analysis.
133
+
134
+ Args:
135
+ returns: List of daily returns
136
+ positions: Dictionary of positions over time (optional)
137
+ transactions: Dictionary of transactions (optional)
138
+ benchmark_rets: List of benchmark returns (optional)
139
+
140
+ Returns:
141
+ Dictionary with success status and performance statistics
142
+ """
143
+ try:
144
+ returns_series = _convert_to_series(returns, "returns")
145
+ positions_df = _convert_to_dataframe(positions) if positions else None
146
+ transactions_df = _convert_to_dataframe(transactions) if transactions else None
147
+ benchmark_series = _convert_to_series(benchmark_rets, "benchmark") if benchmark_rets else None
148
+
149
+ # Calculate performance statistics
150
+ stats = _calculate_performance_stats(returns_series)
151
+
152
+ # Capture tear sheet output
153
+ output = io.StringIO()
154
+ with contextlib.redirect_stdout(output):
155
+ create_full_tear_sheet(returns_series, positions_df, transactions_df, benchmark_series)
156
+
157
+ return {
158
+ "success": True,
159
+ "result": {
160
+ "statistics": stats,
161
+ "message": "Full tear sheet generated successfully.",
162
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None # Limit output
163
+ },
164
+ "error": None
165
+ }
166
  except Exception as e:
167
  return {"success": False, "result": None, "error": str(e)}
168
 
 
175
  returns: List of daily returns
176
 
177
  Returns:
178
+ Dictionary with success status and performance statistics
179
  """
180
  try:
181
  returns_series = _convert_to_series(returns)
182
+
183
+ # Calculate performance statistics
184
+ stats = _calculate_performance_stats(returns_series)
185
+
186
+ # Capture tear sheet output
187
+ output = io.StringIO()
188
+ with contextlib.redirect_stdout(output):
189
+ create_returns_tear_sheet(returns_series)
190
+
191
+ return {
192
+ "success": True,
193
+ "result": {
194
+ "statistics": stats,
195
+ "message": "Returns tear sheet generated successfully.",
196
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
197
+ },
198
+ "error": None
199
+ }
200
  except Exception as e:
201
  return {"success": False, "result": None, "error": str(e)}
202
 
 
213
  """
214
  try:
215
  positions_df = _convert_to_dataframe(positions)
216
+
217
+ output = io.StringIO()
218
+ with contextlib.redirect_stdout(output):
219
+ create_position_tear_sheet(positions_df)
220
+
221
+ return {
222
+ "success": True,
223
+ "result": {
224
+ "message": "Position tear sheet generated successfully.",
225
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
226
+ },
227
+ "error": None
228
+ }
229
  except Exception as e:
230
  return {"success": False, "result": None, "error": str(e)}
231
 
 
242
  """
243
  try:
244
  transactions_df = _convert_to_dataframe(transactions)
245
+
246
+ output = io.StringIO()
247
+ with contextlib.redirect_stdout(output):
248
+ create_txn_tear_sheet(transactions_df)
249
+
250
+ return {
251
+ "success": True,
252
+ "result": {
253
+ "message": "Transaction tear sheet generated successfully.",
254
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
255
+ },
256
+ "error": None
257
+ }
258
  except Exception as e:
259
  return {"success": False, "result": None, "error": str(e)}
260
 
 
271
  """
272
  try:
273
  round_trips_df = _convert_to_dataframe(round_trips)
274
+
275
+ output = io.StringIO()
276
+ with contextlib.redirect_stdout(output):
277
+ create_round_trip_tear_sheet(round_trips_df)
278
+
279
+ return {
280
+ "success": True,
281
+ "result": {
282
+ "message": "Round trip tear sheet generated successfully.",
283
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
284
+ },
285
+ "error": None
286
+ }
287
  except Exception as e:
288
  return {"success": False, "result": None, "error": str(e)}
289
 
 
301
  """
302
  try:
303
  returns_series = _convert_to_series(returns)
304
+
305
+ output = io.StringIO()
306
+ with contextlib.redirect_stdout(output):
307
+ create_interesting_times_tear_sheet(returns_series, events)
308
+
309
+ return {
310
+ "success": True,
311
+ "result": {
312
+ "message": "Interesting times tear sheet generated successfully.",
313
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
314
+ },
315
+ "error": None
316
+ }
317
  except Exception as e:
318
  return {"success": False, "result": None, "error": str(e)}
319
 
 
332
  try:
333
  returns_series = _convert_to_series(returns)
334
  positions_df = _convert_to_dataframe(positions)
335
+
336
+ output = io.StringIO()
337
+ with contextlib.redirect_stdout(output):
338
+ create_capacity_tear_sheet(returns_series, positions_df)
339
+
340
+ return {
341
+ "success": True,
342
+ "result": {
343
+ "message": "Capacity tear sheet generated successfully.",
344
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
345
+ },
346
+ "error": None
347
+ }
348
  except Exception as e:
349
  return {"success": False, "result": None, "error": str(e)}
350
 
 
363
  try:
364
  returns_series = _convert_to_series(returns)
365
  factors_df = _convert_to_dataframe(factors)
366
+
367
+ output = io.StringIO()
368
+ with contextlib.redirect_stdout(output):
369
+ create_perf_attrib_tear_sheet(returns_series, factors_df)
370
+
371
+ return {
372
+ "success": True,
373
+ "result": {
374
+ "message": "Performance attribution tear sheet generated successfully.",
375
+ "tear_sheet_output": output.getvalue()[:1000] if output.getvalue() else None
376
+ },
377
+ "error": None
378
+ }
379
  except Exception as e:
380
  return {"success": False, "result": None, "error": str(e)}
381