iflow-mcp_saintdoresh-yfinance-trader 0.1.0__py3-none-any.whl
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.
- iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/METADATA +220 -0
- iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/RECORD +6 -0
- iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/WHEEL +5 -0
- iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/top_level.txt +1 -0
- main.py +224 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iflow-mcp_saintdoresh-yfinance-trader
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: YFinance Trader MCP Tool for Claude Desktop
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: yfinance>=0.2.36
|
|
10
|
+
Requires-Dist: fastapi>=0.103.1
|
|
11
|
+
Requires-Dist: uvicorn>=0.23.2
|
|
12
|
+
Requires-Dist: pydantic>=2.4.2
|
|
13
|
+
Requires-Dist: requests>=2.31.0
|
|
14
|
+
Requires-Dist: pandas>=2.1.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
17
|
+
Requires-Dist: black>=23.1.0; extra == "dev"
|
|
18
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
19
|
+
|
|
20
|
+
# YFinance Trader MCP Tool for Claude Desktop
|
|
21
|
+
|
|
22
|
+
An MCP (Model Context Protocol) tool that provides stock market data and trading capabilities using the yfinance library, specifically adapted for Claude Desktop.
|
|
23
|
+
|
|
24
|
+
> **Credit**: This project was inspired by [mcp-stocks](https://github.com/luigiajah/mcp-stocks) by Luigi Ajah, which is a similar implementation for Cursor. This adaptation modifies the original concept to work with Claude Desktop.
|
|
25
|
+
|
|
26
|
+
## Tutorial
|
|
27
|
+
|
|
28
|
+
For a detailed guide on setting up and using this tool, check out our Medium tutorial:
|
|
29
|
+
[Tutorial: Using Claude Desktop with YFinance Trader MCP Tool to Access Real-Time Stock Market Data](https://medium.com/@saintdoresh/tutorial-using-claude-desktop-with-yfinance-trader-mcp-tool-to-access-real-time-stock-market-data-904cd1e1ba09)
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Real-time stock quotes
|
|
34
|
+
- Company information and financial metrics
|
|
35
|
+
- Historical price data
|
|
36
|
+
- Symbol search functionality
|
|
37
|
+
- Analyst recommendations
|
|
38
|
+
- Insider transaction tracking
|
|
39
|
+
|
|
40
|
+
## Setup
|
|
41
|
+
|
|
42
|
+
1. Ensure you have Python 3.10 or higher installed
|
|
43
|
+
|
|
44
|
+
2. Install dependencies:
|
|
45
|
+
```bash
|
|
46
|
+
pip install -r requirements.txt
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Integration with Claude Desktop
|
|
50
|
+
|
|
51
|
+
1. Configure your MCP settings in Claude Desktop by adding the following to your MCP configuration:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"yfinance-trader": {
|
|
57
|
+
"command": "py",
|
|
58
|
+
"args": ["-3.13", "path/to/your/main.py"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. Replace the path with the full path to your main.py file
|
|
65
|
+
3. Restart Claude Desktop if needed
|
|
66
|
+
|
|
67
|
+
## Available Tools
|
|
68
|
+
|
|
69
|
+
### 1. get_stock_quote
|
|
70
|
+
Get real-time stock quote information:
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"symbol": "AAPL",
|
|
74
|
+
"price": 150.25,
|
|
75
|
+
"change": 2.5,
|
|
76
|
+
"changePercent": 1.67,
|
|
77
|
+
"volume": 1234567,
|
|
78
|
+
"timestamp": "2024-03-20T10:30:00"
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. get_company_overview
|
|
83
|
+
Get company information and key metrics:
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"name": "Apple Inc.",
|
|
87
|
+
"sector": "Technology",
|
|
88
|
+
"industry": "Consumer Electronics",
|
|
89
|
+
"marketCap": 2500000000000,
|
|
90
|
+
"peRatio": 25.4,
|
|
91
|
+
"forwardPE": 24.2,
|
|
92
|
+
"dividendYield": 0.65,
|
|
93
|
+
"52WeekHigh": 182.94,
|
|
94
|
+
"52WeekLow": 124.17
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. get_time_series_daily
|
|
99
|
+
Get historical daily price data:
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"symbol": "AAPL",
|
|
103
|
+
"timeSeriesDaily": [
|
|
104
|
+
{
|
|
105
|
+
"date": "2024-03-20T00:00:00",
|
|
106
|
+
"open": 150.25,
|
|
107
|
+
"high": 152.30,
|
|
108
|
+
"low": 149.80,
|
|
109
|
+
"close": 151.75,
|
|
110
|
+
"volume": 12345678
|
|
111
|
+
}
|
|
112
|
+
// ... more data points
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 4. search_symbol
|
|
118
|
+
Search for stocks and other securities:
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"results": [
|
|
122
|
+
{
|
|
123
|
+
"symbol": "AAPL",
|
|
124
|
+
"name": "Apple Inc.",
|
|
125
|
+
"type": "EQUITY",
|
|
126
|
+
"exchange": "NASDAQ"
|
|
127
|
+
}
|
|
128
|
+
// ... more results
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 5. get_recommendations
|
|
134
|
+
Get analyst recommendations for a stock:
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"symbol": "AAPL",
|
|
138
|
+
"recommendations": [
|
|
139
|
+
{
|
|
140
|
+
"period": "2024-03-15T00:00:00",
|
|
141
|
+
"strongBuy": 15,
|
|
142
|
+
"buy": 20,
|
|
143
|
+
"hold": 8,
|
|
144
|
+
"sell": 2,
|
|
145
|
+
"strongSell": 0
|
|
146
|
+
}
|
|
147
|
+
// ... more periods
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 6. get_insider_transactions
|
|
153
|
+
Get insider trading information:
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"symbol": "AAPL",
|
|
157
|
+
"transactions": [
|
|
158
|
+
{
|
|
159
|
+
"date": "2024-03-15T00:00:00",
|
|
160
|
+
"insider": "John Doe",
|
|
161
|
+
"position": "Director",
|
|
162
|
+
"transactionType": "Buy",
|
|
163
|
+
"shares": 1000,
|
|
164
|
+
"value": 150250.00,
|
|
165
|
+
"url": "https://finance.yahoo.com/...",
|
|
166
|
+
"text": "Purchase of 1000 shares",
|
|
167
|
+
"startDate": "2024-03-15",
|
|
168
|
+
"ownership": "Direct"
|
|
169
|
+
}
|
|
170
|
+
// ... more transactions
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Sample Queries
|
|
176
|
+
|
|
177
|
+
You can ask Claude Desktop questions like:
|
|
178
|
+
- "What is the current stock price and daily change for AAPL?"
|
|
179
|
+
- "Can you give me a company overview for Microsoft (MSFT)?"
|
|
180
|
+
- "Show me the historical price data for Tesla (TSLA) over the last 3 months."
|
|
181
|
+
- "Search for stocks related to 'NVDA'."
|
|
182
|
+
- "What are the analyst recommendations for Amazon (AMZN)?"
|
|
183
|
+
- "Have there been any recent insider transactions for Google (GOOGL)?"
|
|
184
|
+
|
|
185
|
+
## Cryptocurrency Support
|
|
186
|
+
|
|
187
|
+
Limited cryptocurrency data is available using special ticker formats:
|
|
188
|
+
- BTC-USD for Bitcoin
|
|
189
|
+
- ETH-USD for Ethereum
|
|
190
|
+
- DOGE-USD for Dogecoin
|
|
191
|
+
|
|
192
|
+
## Error Handling
|
|
193
|
+
|
|
194
|
+
All tools include proper error handling and will return an error message if something goes wrong:
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"error": "Failed to fetch quote for INVALID_SYMBOL"
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Troubleshooting
|
|
202
|
+
|
|
203
|
+
If the MCP server is not working in Claude Desktop:
|
|
204
|
+
1. Make sure the server is running - you should see output when you start the script
|
|
205
|
+
2. Verify the path in your settings is correct and absolute
|
|
206
|
+
3. Make sure Python 3.10+ is in your system PATH
|
|
207
|
+
4. Check that all dependencies are installed
|
|
208
|
+
5. Try restarting Claude Desktop
|
|
209
|
+
6. Check logs for any error messages
|
|
210
|
+
|
|
211
|
+
## Differences from the original mcp-stocks project
|
|
212
|
+
|
|
213
|
+
- Uses the MCP library directly instead of FastAPI
|
|
214
|
+
- Adapted for Claude Desktop instead of Cursor
|
|
215
|
+
- Modified error handling and response formats
|
|
216
|
+
- Updated configuration approach
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT License
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
main.py,sha256=zSa9WZmI8ABNBFZc6ooKv97egLnKcPJIWSuzN4sc5lA,7538
|
|
2
|
+
iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/METADATA,sha256=n0bwHyFqopcGDKQKv03ApVIW2-CJF8llwi526fX6MyQ,5685
|
|
3
|
+
iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
4
|
+
iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/entry_points.txt,sha256=BZv3T9vMtq-5MmLxZXR31RMpDtC2NLxUNwfOCVbSbZo,46
|
|
5
|
+
iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/top_level.txt,sha256=ZAMgPdWghn6xTRBO6Kc3ML1y3ZrZLnjZlqbboKXc_AE,5
|
|
6
|
+
iflow_mcp_saintdoresh_yfinance_trader-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
main
|
main.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
import yfinance as yf
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from mcp.server.fastmcp import FastMCP
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
# Configure logging
|
|
9
|
+
logging.basicConfig(level=logging.INFO)
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# Initialize FastMCP server
|
|
13
|
+
mcp = FastMCP("yfinance-trader")
|
|
14
|
+
|
|
15
|
+
@mcp.tool("get_stock_quote")
|
|
16
|
+
async def get_stock_quote(symbol: str) -> Dict[str, Any]:
|
|
17
|
+
"""Get real-time stock quote information.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
symbol (str): Stock symbol (e.g., AAPL, MSFT, GOOGL)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Dict containing current stock price and related information
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
stock = yf.Ticker(symbol)
|
|
27
|
+
info = stock.info
|
|
28
|
+
return {
|
|
29
|
+
"symbol": symbol,
|
|
30
|
+
"price": info.get("regularMarketPrice", 0),
|
|
31
|
+
"change": info.get("regularMarketChange", 0),
|
|
32
|
+
"changePercent": info.get("regularMarketChangePercent", 0),
|
|
33
|
+
"volume": info.get("regularMarketVolume", 0),
|
|
34
|
+
"timestamp": datetime.now().isoformat()
|
|
35
|
+
}
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"Error fetching quote for {symbol}: {str(e)}")
|
|
38
|
+
return {"error": f"Failed to fetch quote for {symbol}"}
|
|
39
|
+
|
|
40
|
+
@mcp.tool("get_company_overview")
|
|
41
|
+
async def get_company_overview(symbol: str) -> Dict[str, Any]:
|
|
42
|
+
"""Get company information, financial ratios, and other key metrics.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
symbol (str): Stock symbol (e.g., AAPL, MSFT, GOOGL)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Dict containing company information and key metrics
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
stock = yf.Ticker(symbol)
|
|
52
|
+
info = stock.info
|
|
53
|
+
return {
|
|
54
|
+
"name": info.get("longName", ""),
|
|
55
|
+
"sector": info.get("sector", ""),
|
|
56
|
+
"industry": info.get("industry", ""),
|
|
57
|
+
"marketCap": info.get("marketCap", 0),
|
|
58
|
+
"peRatio": info.get("trailingPE", 0),
|
|
59
|
+
"forwardPE": info.get("forwardPE", 0),
|
|
60
|
+
"dividendYield": info.get("dividendYield", 0),
|
|
61
|
+
"52WeekHigh": info.get("fiftyTwoWeekHigh", 0),
|
|
62
|
+
"52WeekLow": info.get("fiftyTwoWeekLow", 0)
|
|
63
|
+
}
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Error fetching company overview for {symbol}: {str(e)}")
|
|
66
|
+
return {"error": f"Failed to fetch company overview for {symbol}"}
|
|
67
|
+
|
|
68
|
+
@mcp.tool("get_time_series_daily")
|
|
69
|
+
async def get_time_series_daily(symbol: str, outputsize: str = "compact") -> Dict[str, Any]:
|
|
70
|
+
"""Get daily time series stock data.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
symbol (str): Stock symbol (e.g., AAPL, MSFT, GOOGL)
|
|
74
|
+
outputsize (str): Output size: 'compact' (latest 100 data points) or 'full' (up to 20 years of data)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dict containing historical daily price data
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
stock = yf.Ticker(symbol)
|
|
81
|
+
period = "3mo" if outputsize == "compact" else "max"
|
|
82
|
+
history = stock.history(period=period)
|
|
83
|
+
|
|
84
|
+
data = []
|
|
85
|
+
for date, row in history.iterrows():
|
|
86
|
+
data.append({
|
|
87
|
+
"date": date.isoformat(),
|
|
88
|
+
"open": row["Open"],
|
|
89
|
+
"high": row["High"],
|
|
90
|
+
"low": row["Low"],
|
|
91
|
+
"close": row["Close"],
|
|
92
|
+
"volume": row["Volume"]
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"symbol": symbol,
|
|
97
|
+
"timeSeriesDaily": data
|
|
98
|
+
}
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.error(f"Error fetching time series for {symbol}: {str(e)}")
|
|
101
|
+
return {"error": f"Failed to fetch time series for {symbol}"}
|
|
102
|
+
|
|
103
|
+
@mcp.tool("search_symbol")
|
|
104
|
+
async def search_symbol(keywords: str) -> Dict[str, Any]:
|
|
105
|
+
"""Search for stocks, ETFs, mutual funds, or other securities.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
keywords (str): Keywords to search for (e.g., apple, microsoft, tech)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dict containing search results
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
tickers = yf.Tickers(keywords)
|
|
115
|
+
results = []
|
|
116
|
+
|
|
117
|
+
for symbol in keywords.split():
|
|
118
|
+
try:
|
|
119
|
+
info = tickers.tickers[symbol].info
|
|
120
|
+
results.append({
|
|
121
|
+
"symbol": symbol,
|
|
122
|
+
"name": info.get("longName", ""),
|
|
123
|
+
"type": info.get("quoteType", ""),
|
|
124
|
+
"exchange": info.get("exchange", "")
|
|
125
|
+
})
|
|
126
|
+
except:
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
return {"results": results}
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"Error searching for {keywords}: {str(e)}")
|
|
132
|
+
return {"error": f"Failed to search for {keywords}"}
|
|
133
|
+
|
|
134
|
+
@mcp.tool("get_recommendations")
|
|
135
|
+
async def get_recommendations(symbol: str) -> Dict[str, Any]:
|
|
136
|
+
"""Get analyst recommendations for a stock.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
symbol (str): Stock symbol (e.g., AAPL, MSFT, GOOGL)
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Dict containing analyst recommendations including strongBuy, buy, hold, sell, strongSell counts
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
stock = yf.Ticker(symbol)
|
|
146
|
+
recommendations = stock.recommendations
|
|
147
|
+
|
|
148
|
+
if recommendations is None or recommendations.empty:
|
|
149
|
+
return {
|
|
150
|
+
"symbol": symbol,
|
|
151
|
+
"recommendations": []
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Convert the recommendations DataFrame to a list of dictionaries
|
|
155
|
+
recs = []
|
|
156
|
+
for index, row in recommendations.iterrows():
|
|
157
|
+
rec_data = {
|
|
158
|
+
"period": index.isoformat() if hasattr(index, "isoformat") else str(index),
|
|
159
|
+
"strongBuy": int(row.get("strongBuy", 0)),
|
|
160
|
+
"buy": int(row.get("buy", 0)),
|
|
161
|
+
"hold": int(row.get("hold", 0)),
|
|
162
|
+
"sell": int(row.get("sell", 0)),
|
|
163
|
+
"strongSell": int(row.get("strongSell", 0))
|
|
164
|
+
}
|
|
165
|
+
recs.append(rec_data)
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
"symbol": symbol,
|
|
169
|
+
"recommendations": recs
|
|
170
|
+
}
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error(f"Error fetching recommendations for {symbol}: {str(e)}")
|
|
173
|
+
return {"error": f"Failed to fetch recommendations for {symbol}"}
|
|
174
|
+
|
|
175
|
+
@mcp.tool("get_insider_transactions")
|
|
176
|
+
async def get_insider_transactions(symbol: str) -> Dict[str, Any]:
|
|
177
|
+
"""Get insider transactions for a company.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
symbol (str): Stock symbol (e.g., AAPL, MSFT, GOOGL)
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Dict containing recent insider transactions
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
stock = yf.Ticker(symbol)
|
|
187
|
+
insider = stock.insider_transactions
|
|
188
|
+
|
|
189
|
+
if insider is None or insider.empty:
|
|
190
|
+
return {
|
|
191
|
+
"symbol": symbol,
|
|
192
|
+
"transactions": []
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
transactions = []
|
|
196
|
+
for index, row in insider.iterrows():
|
|
197
|
+
transaction = {
|
|
198
|
+
"date": index.isoformat() if hasattr(index, "isoformat") else str(index),
|
|
199
|
+
"insider": row.get("Insider", ""),
|
|
200
|
+
"position": row.get("Position", ""),
|
|
201
|
+
"transactionType": row.get("Transaction", ""),
|
|
202
|
+
"shares": int(row.get("Shares", 0)),
|
|
203
|
+
"value": float(row.get("Value", 0)),
|
|
204
|
+
"url": row.get("URL", ""),
|
|
205
|
+
"text": row.get("Text", ""),
|
|
206
|
+
"startDate": row.get("Start Date", ""),
|
|
207
|
+
"ownership": row.get("Ownership", "")
|
|
208
|
+
}
|
|
209
|
+
transactions.append(transaction)
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
"symbol": symbol,
|
|
213
|
+
"transactions": transactions
|
|
214
|
+
}
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.error(f"Error fetching insider transactions for {symbol}: {str(e)}")
|
|
217
|
+
return {"error": f"Failed to fetch insider transactions for {symbol}"}
|
|
218
|
+
|
|
219
|
+
def main():
|
|
220
|
+
"""Main entry point for the MCP server."""
|
|
221
|
+
mcp.run()
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|