CI / tests (py3.10) (push) Has been cancelled
CI / tests (py3.11) (push) Has been cancelled
CI / tests (py3.12) (push) Has been cancelled
CI / tests (py3.13) (push) Has been cancelled
CI / clean-install smoke (push) Has been cancelled
CI / ruff (strict, full repo) (push) Has been cancelled
- 捕获stdout输出同步到前端日志 - 添加等待提示(3-8分钟) - 过滤有意义的进度信息
641 lines
23 KiB
Python
641 lines
23 KiB
Python
"""TradingAgents Web 可视化页面
|
|
|
|
提供Web界面让用户输入股票代码,运行TradingAgents分析,查看结果。
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import threading
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from flask import Flask, render_template_string, request, jsonify
|
|
|
|
# 确保项目根目录在路径中
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
app = Flask(__name__)
|
|
|
|
# 存储运行中的任务
|
|
tasks = {}
|
|
|
|
# HTML模板
|
|
HTML_TEMPLATE = """
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>TradingAgents AI 交易分析</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: linear-gradient(135deg, #0a0a2e 0%, #1a1a4e 50%, #0d0d3d 100%);
|
|
color: #e0e0e0;
|
|
min-height: 100vh;
|
|
}
|
|
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
|
|
/* Header */
|
|
.header {
|
|
text-align: center;
|
|
padding: 40px 0 30px;
|
|
}
|
|
.header h1 {
|
|
font-size: 2.5em;
|
|
background: linear-gradient(120deg, #00d4ff, #7c3aed, #f472b6);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
margin-bottom: 10px;
|
|
}
|
|
.header p {
|
|
color: #8888aa;
|
|
font-size: 1.1em;
|
|
}
|
|
|
|
/* Input Form */
|
|
.form-card {
|
|
background: rgba(255,255,255,0.05);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 16px;
|
|
padding: 30px;
|
|
margin-bottom: 30px;
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
.form-row {
|
|
display: flex;
|
|
gap: 15px;
|
|
align-items: flex-end;
|
|
flex-wrap: wrap;
|
|
}
|
|
.form-group {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
}
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
color: #aaaacc;
|
|
font-size: 0.9em;
|
|
}
|
|
.form-group input, .form-group select {
|
|
width: 100%;
|
|
padding: 12px 16px;
|
|
background: rgba(255,255,255,0.08);
|
|
border: 1px solid rgba(255,255,255,0.15);
|
|
border-radius: 10px;
|
|
color: #fff;
|
|
font-size: 1em;
|
|
transition: all 0.3s;
|
|
}
|
|
.form-group input:focus, .form-group select:focus {
|
|
outline: none;
|
|
border-color: #7c3aed;
|
|
box-shadow: 0 0 20px rgba(124, 58, 237, 0.3);
|
|
}
|
|
.form-group input::placeholder { color: #666688; }
|
|
|
|
.btn {
|
|
padding: 12px 30px;
|
|
border: none;
|
|
border-radius: 10px;
|
|
font-size: 1em;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #7c3aed, #3b82f6);
|
|
color: white;
|
|
}
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px rgba(124, 58, 237, 0.4);
|
|
}
|
|
.btn-primary:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
}
|
|
|
|
/* Preset Buttons */
|
|
.presets {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.preset-btn {
|
|
padding: 6px 14px;
|
|
background: rgba(255,255,255,0.06);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 20px;
|
|
color: #aaaacc;
|
|
font-size: 0.85em;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
.preset-btn:hover {
|
|
background: rgba(124, 58, 237, 0.2);
|
|
border-color: #7c3aed;
|
|
color: #fff;
|
|
}
|
|
|
|
/* Status */
|
|
.status-card {
|
|
background: rgba(255,255,255,0.03);
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
}
|
|
.status-card.active { display: block; }
|
|
.status-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 15px;
|
|
}
|
|
.spinner {
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid rgba(124, 58, 237, 0.3);
|
|
border-top-color: #7c3aed;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
.log-box {
|
|
background: rgba(0,0,0,0.3);
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 0.85em;
|
|
color: #8888aa;
|
|
line-height: 1.6;
|
|
}
|
|
.log-box .info { color: #3b82f6; }
|
|
.log-box .success { color: #10b981; }
|
|
.log-box .error { color: #ef4444; }
|
|
|
|
/* Results */
|
|
.results-card {
|
|
background: rgba(255,255,255,0.05);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 16px;
|
|
padding: 30px;
|
|
margin-bottom: 20px;
|
|
display: none;
|
|
}
|
|
.results-card.active { display: block; }
|
|
|
|
.result-section {
|
|
margin-bottom: 25px;
|
|
padding-bottom: 25px;
|
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
}
|
|
.result-section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }
|
|
.result-section h3 {
|
|
color: #7c3aed;
|
|
margin-bottom: 15px;
|
|
font-size: 1.2em;
|
|
}
|
|
|
|
.decision-box {
|
|
background: linear-gradient(135deg, rgba(124, 58, 237, 0.15), rgba(59, 130, 246, 0.15));
|
|
border: 1px solid rgba(124, 58, 237, 0.3);
|
|
border-radius: 12px;
|
|
padding: 25px;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
.decision-label {
|
|
font-size: 0.9em;
|
|
color: #8888aa;
|
|
margin-bottom: 10px;
|
|
}
|
|
.decision-value {
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
}
|
|
.decision-buy { color: #10b981; }
|
|
.decision-sell { color: #ef4444; }
|
|
.decision-hold { color: #f59e0b; }
|
|
|
|
.agent-report {
|
|
background: rgba(255,255,255,0.03);
|
|
border: 1px solid rgba(255,255,255,0.08);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
}
|
|
.agent-report h4 {
|
|
color: #3b82f6;
|
|
margin-bottom: 12px;
|
|
font-size: 1.05em;
|
|
}
|
|
.agent-report pre {
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
font-size: 0.9em;
|
|
line-height: 1.6;
|
|
color: #ccccee;
|
|
}
|
|
|
|
/* Footer */
|
|
.footer {
|
|
text-align: center;
|
|
padding: 30px 0;
|
|
color: #555577;
|
|
font-size: 0.85em;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.header h1 { font-size: 1.8em; }
|
|
.form-row { flex-direction: column; }
|
|
.form-group { min-width: 100%; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🤖 TradingAgents</h1>
|
|
<p>AI 多智能体交易分析系统 · Powered by MiMo-v2.5-pro</p>
|
|
</div>
|
|
|
|
<div class="form-card">
|
|
<form id="analysisForm">
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>📊 股票代码</label>
|
|
<input type="text" id="ticker" placeholder="例: AAPL, 600519.SS, BTC-USD" required>
|
|
</div>
|
|
<div class="form-group" style="max-width: 200px;">
|
|
<label>📅 分析日期</label>
|
|
<input type="date" id="date" value="{{ default_date }}">
|
|
</div>
|
|
<div class="form-group" style="max-width: 150px;">
|
|
<label> </label>
|
|
<button type="submit" class="btn btn-primary" id="submitBtn">
|
|
🚀 开始分析
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="presets">
|
|
<span class="preset-btn" onclick="setTicker('AAPL')">🍎 AAPL</span>
|
|
<span class="preset-btn" onclick="setTicker('NVDA')">💚 NVDA</span>
|
|
<span class="preset-btn" onclick="setTicker('600519.SS')">🍷 茅台</span>
|
|
<span class="preset-btn" onclick="setTicker('000001.SZ')">🏦 平安</span>
|
|
<span class="preset-btn" onclick="setTicker('0700.HK')">🐧 腾讯</span>
|
|
<span class="preset-btn" onclick="setTicker('BTC-USD')">₿ BTC</span>
|
|
<span class="preset-btn" onclick="setTicker('SPY')">📈 SPY</span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="status-card" id="statusCard">
|
|
<div class="status-header">
|
|
<div class="spinner"></div>
|
|
<span id="statusText">正在初始化分析...</span>
|
|
</div>
|
|
<div class="log-box" id="logBox"></div>
|
|
</div>
|
|
|
|
<div class="results-card" id="resultsCard">
|
|
<div class="decision-box">
|
|
<div class="decision-label">📊 交易决策</div>
|
|
<div class="decision-value" id="decisionValue">--</div>
|
|
</div>
|
|
|
|
<div class="result-section">
|
|
<h3>📈 技术分析师</h3>
|
|
<div class="agent-report"><pre id="techReport">等待分析...</pre></div>
|
|
</div>
|
|
|
|
<div class="result-section">
|
|
<h3>💭 情绪分析师</h3>
|
|
<div class="agent-report"><pre id="sentimentReport">等待分析...</pre></div>
|
|
</div>
|
|
|
|
<div class="result-section">
|
|
<h3>📰 新闻分析师</h3>
|
|
<div class="agent-report"><pre id="newsReport">等待分析...</pre></div>
|
|
</div>
|
|
|
|
<div class="result-section">
|
|
<h3>🔬 研究员辩论</h3>
|
|
<div class="agent-report"><pre id="debateReport">等待分析...</pre></div>
|
|
</div>
|
|
|
|
<div class="result-section">
|
|
<h3>⚠️ 风险评估</h3>
|
|
<div class="agent-report"><pre id="riskReport">等待分析...</pre></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
TradingAgents v1.0 · 数据来源: Yahoo Finance, StockTwits, Reddit, Polymarket, FRED<br>
|
|
⚠️ 仅供参考,不构成投资建议
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function setTicker(t) {
|
|
document.getElementById('ticker').value = t;
|
|
}
|
|
|
|
document.getElementById('analysisForm').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
|
|
const ticker = document.getElementById('ticker').value.trim();
|
|
const date = document.getElementById('date').value;
|
|
|
|
if (!ticker) return;
|
|
|
|
const btn = document.getElementById('submitBtn');
|
|
const statusCard = document.getElementById('statusCard');
|
|
const resultsCard = document.getElementById('resultsCard');
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = '⏳ 分析中...';
|
|
statusCard.classList.add('active');
|
|
resultsCard.classList.remove('active');
|
|
|
|
const logBox = document.getElementById('logBox');
|
|
logBox.innerHTML = '<span class="info">⏳ 提交分析任务...</span><br>';
|
|
|
|
try {
|
|
const resp = await fetch('/api/analyze', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({ticker, date})
|
|
});
|
|
|
|
const data = await resp.json();
|
|
|
|
if (data.error) {
|
|
logBox.innerHTML += `<span class="error">❌ ${data.error}</span><br>`;
|
|
btn.disabled = false;
|
|
btn.textContent = '🚀 开始分析';
|
|
return;
|
|
}
|
|
|
|
const taskId = data.task_id;
|
|
logBox.innerHTML += `<span class="info">📋 任务ID: ${taskId}</span><br>`;
|
|
|
|
// Poll for results
|
|
pollTask(taskId);
|
|
|
|
} catch(err) {
|
|
logBox.innerHTML += `<span class="error">❌ 请求失败: ${err.message}</span><br>`;
|
|
btn.disabled = false;
|
|
btn.textContent = '🚀 开始分析';
|
|
}
|
|
});
|
|
|
|
async function pollTask(taskId) {
|
|
const logBox = document.getElementById('logBox');
|
|
const btn = document.getElementById('submitBtn');
|
|
const statusCard = document.getElementById('statusCard');
|
|
const resultsCard = document.getElementById('resultsCard');
|
|
|
|
const maxPolls = 120;
|
|
for (let i = 0; i < maxPolls; i++) {
|
|
await new Promise(r => setTimeout(r, 5000));
|
|
|
|
try {
|
|
const resp = await fetch(`/api/status/${taskId}`);
|
|
const data = await resp.json();
|
|
|
|
// Update logs
|
|
if (data.logs && data.logs.length > 0) {
|
|
logBox.innerHTML = data.logs.map(l => {
|
|
const cls = l.includes('✅') ? 'success' : l.includes('❌') ? 'error' : 'info';
|
|
return `<span class="${cls}">${l}</span>`;
|
|
}).join('<br>');
|
|
logBox.scrollTop = logBox.scrollHeight;
|
|
}
|
|
|
|
if (data.status === 'completed') {
|
|
btn.disabled = false;
|
|
btn.textContent = '🚀 开始分析';
|
|
|
|
// Show results
|
|
resultsCard.classList.add('active');
|
|
|
|
if (data.result) {
|
|
// Decision
|
|
const decision = data.result.final_decision || 'HOLD';
|
|
const decEl = document.getElementById('decisionValue');
|
|
decEl.textContent = decision;
|
|
decEl.className = 'decision-value ' +
|
|
(decision.includes('BUY') ? 'decision-buy' :
|
|
decision.includes('SELL') ? 'decision-sell' : 'decision-hold');
|
|
|
|
// Reports
|
|
const reports = data.result.reports || {};
|
|
document.getElementById('techReport').textContent = reports.technical || '无数据';
|
|
document.getElementById('sentimentReport').textContent = reports.sentiment || '无数据';
|
|
document.getElementById('newsReport').textContent = reports.news || '无数据';
|
|
document.getElementById('debateReport').textContent = reports.debate || '无数据';
|
|
document.getElementById('riskReport').textContent = reports.risk || '无数据';
|
|
}
|
|
|
|
logBox.innerHTML += '<br><span class="success">✅ 分析完成!</span>';
|
|
return;
|
|
}
|
|
|
|
if (data.status === 'failed') {
|
|
btn.disabled = false;
|
|
btn.textContent = '🚀 开始分析';
|
|
logBox.innerHTML += `<br><span class="error">❌ 分析失败: ${data.error}</span>`;
|
|
return;
|
|
}
|
|
|
|
} catch(err) {
|
|
logBox.innerHTML += `<br><span class="error">❌ 状态查询失败: ${err.message}</span>`;
|
|
}
|
|
}
|
|
|
|
btn.disabled = false;
|
|
btn.textContent = '🚀 开始分析';
|
|
logBox.innerHTML += '<br><span class="error">❌ 分析超时</span>';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
@app.route('/')
|
|
def index():
|
|
default_date = datetime.now().strftime('%Y-%m-%d')
|
|
return render_template_string(HTML_TEMPLATE, default_date=default_date)
|
|
|
|
|
|
@app.route('/api/analyze', methods=['POST'])
|
|
def analyze():
|
|
data = request.json
|
|
ticker = data.get('ticker', '').strip()
|
|
date = data.get('date', datetime.now().strftime('%Y-%m-%d'))
|
|
|
|
if not ticker:
|
|
return jsonify({'error': '请输入股票代码'}), 400
|
|
|
|
task_id = str(uuid.uuid4())[:8]
|
|
tasks[task_id] = {
|
|
'status': 'running',
|
|
'ticker': ticker,
|
|
'date': date,
|
|
'logs': ['⏳ 初始化分析...'],
|
|
'result': None,
|
|
'error': None,
|
|
'started_at': datetime.now().isoformat()
|
|
}
|
|
|
|
# 在后台线程运行分析
|
|
thread = threading.Thread(target=run_analysis, args=(task_id, ticker, date))
|
|
thread.daemon = True
|
|
thread.start()
|
|
|
|
return jsonify({'task_id': task_id, 'status': 'running'})
|
|
|
|
|
|
@app.route('/api/status/<task_id>')
|
|
def task_status(task_id):
|
|
task = tasks.get(task_id)
|
|
if not task:
|
|
return jsonify({'error': '任务不存在'}), 404
|
|
return jsonify(task)
|
|
|
|
|
|
def run_analysis(task_id, ticker, date):
|
|
"""后台运行TradingAgents分析。"""
|
|
import sys
|
|
import io
|
|
|
|
try:
|
|
task = tasks[task_id]
|
|
|
|
def log(msg):
|
|
task['logs'].append(msg)
|
|
# 保持日志不超过50条
|
|
if len(task['logs']) > 50:
|
|
task['logs'] = task['logs'][-50:]
|
|
|
|
# 设置环境变量
|
|
os.environ['OPENAI_COMPATIBLE_API_KEY'] = 'tp-ce6eaqg1gh1uev46i4oexh4sahloyfo2hfpxuc2n9m7yl9dd'
|
|
os.environ['FRED_API_KEY'] = '825283926dd016ea66e96cc919e047da'
|
|
os.environ['ALPHA_VANTAGE_API_KEY'] = 'W5PTA2J5OB16W02L'
|
|
|
|
log(f'📊 开始分析 {ticker} ({date})')
|
|
log(f'🤖 模型: MiMo-v2.5-pro')
|
|
log(f'🌐 数据源: Alpha Vantage + SSH中转代理')
|
|
|
|
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
|
from tradingagents.default_config import DEFAULT_CONFIG
|
|
|
|
config = DEFAULT_CONFIG.copy()
|
|
config['llm_provider'] = 'openai_compatible'
|
|
config['backend_url'] = 'https://token-plan-cn.xiaomimimo.com/v1'
|
|
config['deep_think_llm'] = 'mimo-v2.5-pro'
|
|
config['quick_think_llm'] = 'mimo-v2.5-pro'
|
|
config['max_debate_rounds'] = 1
|
|
config['max_risk_discuss_rounds'] = 1
|
|
|
|
# 配置数据源:Yahoo Finance优先,Alpha Vantage作为备选
|
|
config['data_vendors'] = {
|
|
'core_stock_apis': 'yfinance,alpha_vantage',
|
|
'technical_indicators': 'yfinance,alpha_vantage',
|
|
'fundamental_data': 'yfinance,alpha_vantage',
|
|
'news_data': 'yfinance,alpha_vantage',
|
|
'macro_data': 'fred',
|
|
'prediction_markets': 'polymarket',
|
|
}
|
|
|
|
log('🔧 配置完成,启动分析引擎...')
|
|
log('⏳ 分析通常需要3-8分钟,请耐心等待...')
|
|
|
|
# 捕获stdout输出以获取实时进度
|
|
class LogCapture:
|
|
def __init__(self, task):
|
|
self.task = task
|
|
self.buffer = ''
|
|
def write(self, text):
|
|
self.buffer += text
|
|
if '\n' in self.buffer:
|
|
lines = self.buffer.split('\n')
|
|
self.buffer = lines[-1]
|
|
for line in lines[:-1]:
|
|
line = line.strip()
|
|
if line and len(line) > 5:
|
|
# 只保留有意义的输出
|
|
if any(k in line for k in ['Tool', 'Message', 'Agent', 'failed', 'error', '✅', '❌', '📊', '📈', '💭', '📰', '🔬', '⚠️']):
|
|
self.task['logs'].append(f'📋 {line[:100]}')
|
|
if len(self.task['logs']) > 50:
|
|
self.task['logs'] = self.task['logs'][-50:]
|
|
def flush(self):
|
|
pass
|
|
|
|
# 临时捕获输出
|
|
old_stdout = sys.stdout
|
|
sys.stdout = LogCapture(task)
|
|
|
|
try:
|
|
ta = TradingAgentsGraph(debug=True, config=config)
|
|
|
|
log('📈 技术分析师开始工作...')
|
|
state, decision = ta.propagate(ticker, date)
|
|
finally:
|
|
sys.stdout = old_stdout
|
|
|
|
log(f'✅ 分析完成: {decision}')
|
|
|
|
# 提取各Agent的报告
|
|
reports = {}
|
|
|
|
# 从state中提取各阶段报告
|
|
if 'market_report' in state:
|
|
reports['technical'] = state['market_report'][:2000]
|
|
if 'sentiment_report' in state:
|
|
reports['sentiment'] = state['sentiment_report'][:2000]
|
|
if 'news_report' in state:
|
|
reports['news'] = state['news_report'][:2000]
|
|
if 'investment_debate_state' in state:
|
|
debate = state['investment_debate_state']
|
|
if isinstance(debate, dict):
|
|
reports['debate'] = str(debate.get('judge_decision', ''))[:2000]
|
|
else:
|
|
reports['debate'] = str(debate)[:2000]
|
|
if 'risk_debate_state' in state:
|
|
risk = state['risk_debate_state']
|
|
if isinstance(risk, dict):
|
|
reports['risk'] = str(risk.get('judge_decision', ''))[:2000]
|
|
else:
|
|
reports['risk'] = str(risk)[:2000]
|
|
|
|
task['status'] = 'completed'
|
|
task['result'] = {
|
|
'final_decision': decision,
|
|
'reports': reports
|
|
}
|
|
task['logs'].append(f'✅ 最终决策: {decision}')
|
|
|
|
except Exception as e:
|
|
task = tasks.get(task_id)
|
|
if task:
|
|
task['status'] = 'failed'
|
|
task['error'] = str(e)
|
|
task['logs'].append(f'❌ 错误: {str(e)}')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='0.0.0.0', port=8890, debug=False)
|