guohanghui commited on
Commit
b787e73
·
verified ·
1 Parent(s): 9b3c011

Update pyfolio/mcp_output/mcp_plugin/mcp_service.py

Browse files
pyfolio/mcp_output/mcp_plugin/mcp_service.py CHANGED
@@ -4,6 +4,11 @@ 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)
@@ -32,6 +37,35 @@ from pyfolio.plotting import (
32
 
33
  mcp = FastMCP("pyfolio_service")
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  def _convert_to_series(data: List[float], name: str = "returns") -> pd.Series:
36
  """Convert list to pandas Series with date index."""
37
  if isinstance(data, list):
@@ -389,12 +423,32 @@ def plot_annual_returns_tool(returns: list) -> dict:
389
  returns: List of daily returns
390
 
391
  Returns:
392
- Dictionary with success status and result/error message
393
  """
394
  try:
395
  returns_series = _convert_to_series(returns)
396
- ax = plot_annual_returns(returns_series)
397
- return {"success": True, "result": "Annual returns plot generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  except Exception as e:
399
  return {"success": False, "result": None, "error": str(e)}
400
 
@@ -407,12 +461,32 @@ def plot_monthly_returns_heatmap_tool(returns: list) -> dict:
407
  returns: List of daily returns
408
 
409
  Returns:
410
- Dictionary with success status and result/error message
411
  """
412
  try:
413
  returns_series = _convert_to_series(returns)
414
- ax = plot_monthly_returns_heatmap(returns_series)
415
- return {"success": True, "result": "Monthly returns heatmap generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  except Exception as e:
417
  return {"success": False, "result": None, "error": str(e)}
418
 
@@ -426,12 +500,32 @@ def plot_drawdown_periods_tool(returns: list, top: int = 10) -> dict:
426
  top: Number of top drawdown periods to highlight
427
 
428
  Returns:
429
- Dictionary with success status and result/error message
430
  """
431
  try:
432
  returns_series = _convert_to_series(returns)
433
- ax = plot_drawdown_periods(returns_series, top)
434
- return {"success": True, "result": "Drawdown periods plot generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  except Exception as e:
436
  return {"success": False, "result": None, "error": str(e)}
437
 
@@ -446,13 +540,34 @@ def plot_rolling_returns_tool(returns: list, factor_returns: list = None, live_s
446
  live_start_date: Start date for live trading period (optional)
447
 
448
  Returns:
449
- Dictionary with success status and result/error message
450
  """
451
  try:
452
  returns_series = _convert_to_series(returns)
453
  factor_series = _convert_to_series(factor_returns, "factor") if factor_returns else None
454
- ax = plot_rolling_returns(returns_series, factor_series, live_start_date)
455
- return {"success": True, "result": "Rolling returns plot generated successfully.", "error": None}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  except Exception as e:
457
  return {"success": False, "result": None, "error": str(e)}
458
 
 
4
  from typing import Optional, Dict, List, Any
5
  import io
6
  import contextlib
7
+ import matplotlib
8
+ matplotlib.use('Agg') # Use non-interactive backend
9
+ import matplotlib.pyplot as plt
10
+ import base64
11
+ from datetime import datetime
12
 
13
  source_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "source")
14
  sys.path.insert(0, source_path)
 
37
 
38
  mcp = FastMCP("pyfolio_service")
39
 
40
+ def _save_plot_as_base64(fig=None, filename_prefix="plot") -> str:
41
+ """Save matplotlib plot as base64 encoded image."""
42
+ try:
43
+ if fig is None:
44
+ fig = plt.gcf()
45
+
46
+ # Save to BytesIO
47
+ buffer = io.BytesIO()
48
+ fig.savefig(buffer, format='png', dpi=150, bbox_inches='tight')
49
+ buffer.seek(0)
50
+
51
+ # Convert to base64
52
+ image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
53
+
54
+ # Also save to file
55
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
56
+ filepath = f"/tmp/{filename_prefix}_{timestamp}.png"
57
+ fig.savefig(filepath, format='png', dpi=150, bbox_inches='tight')
58
+
59
+ plt.close(fig) # Close to free memory
60
+
61
+ return {
62
+ "base64": image_base64,
63
+ "filepath": filepath,
64
+ "data_uri": f"data:image/png;base64,{image_base64}"
65
+ }
66
+ except Exception as e:
67
+ return {"error": str(e)}
68
+
69
  def _convert_to_series(data: List[float], name: str = "returns") -> pd.Series:
70
  """Convert list to pandas Series with date index."""
71
  if isinstance(data, list):
 
423
  returns: List of daily returns
424
 
425
  Returns:
426
+ Dictionary with success status, image data, and result/error message
427
  """
428
  try:
429
  returns_series = _convert_to_series(returns)
430
+
431
+ # Create the plot
432
+ fig, ax = plt.subplots(figsize=(10, 6))
433
+ plot_annual_returns(returns_series, ax=ax)
434
+ plt.title('Annual Returns')
435
+
436
+ # Save plot as base64
437
+ image_data = _save_plot_as_base64(fig, "annual_returns")
438
+
439
+ if "error" in image_data:
440
+ return {"success": False, "result": None, "error": image_data["error"]}
441
+
442
+ return {
443
+ "success": True,
444
+ "result": {
445
+ "message": "Annual returns plot generated successfully.",
446
+ "image_base64": image_data["base64"],
447
+ "image_path": image_data["filepath"],
448
+ "data_uri": image_data["data_uri"]
449
+ },
450
+ "error": None
451
+ }
452
  except Exception as e:
453
  return {"success": False, "result": None, "error": str(e)}
454
 
 
461
  returns: List of daily returns
462
 
463
  Returns:
464
+ Dictionary with success status, image data, and result/error message
465
  """
466
  try:
467
  returns_series = _convert_to_series(returns)
468
+
469
+ # Create the plot
470
+ fig, ax = plt.subplots(figsize=(12, 8))
471
+ plot_monthly_returns_heatmap(returns_series, ax=ax)
472
+ plt.title('Monthly Returns Heatmap')
473
+
474
+ # Save plot as base64
475
+ image_data = _save_plot_as_base64(fig, "monthly_heatmap")
476
+
477
+ if "error" in image_data:
478
+ return {"success": False, "result": None, "error": image_data["error"]}
479
+
480
+ return {
481
+ "success": True,
482
+ "result": {
483
+ "message": "Monthly returns heatmap generated successfully.",
484
+ "image_base64": image_data["base64"],
485
+ "image_path": image_data["filepath"],
486
+ "data_uri": image_data["data_uri"]
487
+ },
488
+ "error": None
489
+ }
490
  except Exception as e:
491
  return {"success": False, "result": None, "error": str(e)}
492
 
 
500
  top: Number of top drawdown periods to highlight
501
 
502
  Returns:
503
+ Dictionary with success status, image data, and result/error message
504
  """
505
  try:
506
  returns_series = _convert_to_series(returns)
507
+
508
+ # Create the plot
509
+ fig, ax = plt.subplots(figsize=(12, 6))
510
+ plot_drawdown_periods(returns_series, top=top, ax=ax)
511
+ plt.title(f'Top {top} Drawdown Periods')
512
+
513
+ # Save plot as base64
514
+ image_data = _save_plot_as_base64(fig, "drawdown_periods")
515
+
516
+ if "error" in image_data:
517
+ return {"success": False, "result": None, "error": image_data["error"]}
518
+
519
+ return {
520
+ "success": True,
521
+ "result": {
522
+ "message": "Drawdown periods plot generated successfully.",
523
+ "image_base64": image_data["base64"],
524
+ "image_path": image_data["filepath"],
525
+ "data_uri": image_data["data_uri"]
526
+ },
527
+ "error": None
528
+ }
529
  except Exception as e:
530
  return {"success": False, "result": None, "error": str(e)}
531
 
 
540
  live_start_date: Start date for live trading period (optional)
541
 
542
  Returns:
543
+ Dictionary with success status, image data, and result/error message
544
  """
545
  try:
546
  returns_series = _convert_to_series(returns)
547
  factor_series = _convert_to_series(factor_returns, "factor") if factor_returns else None
548
+
549
+ # Create the plot
550
+ fig, ax = plt.subplots(figsize=(12, 8))
551
+ plot_rolling_returns(returns_series, factor_returns=factor_series,
552
+ live_start_date=live_start_date, ax=ax)
553
+ plt.title('Rolling Returns Analysis')
554
+
555
+ # Save plot as base64
556
+ image_data = _save_plot_as_base64(fig, "rolling_returns")
557
+
558
+ if "error" in image_data:
559
+ return {"success": False, "result": None, "error": image_data["error"]}
560
+
561
+ return {
562
+ "success": True,
563
+ "result": {
564
+ "message": "Rolling returns plot generated successfully.",
565
+ "image_base64": image_data["base64"],
566
+ "image_path": image_data["filepath"],
567
+ "data_uri": image_data["data_uri"]
568
+ },
569
+ "error": None
570
+ }
571
  except Exception as e:
572
  return {"success": False, "result": None, "error": str(e)}
573