Add web application for real-time CSV data visualization and analysis

- Created Flask web application (app.py) to display and analyze trading data
- Implemented data loading, processing, and email notification features
- Added HTML templates for interactive data viewing
- Included ultimate smoother and trend detection algorithms
- Configured dynamic file selection and data rendering
This commit is contained in:
Win_home
2025-03-02 17:02:41 +08:00
parent 7a1f38968c
commit e449354b69
3 changed files with 519 additions and 0 deletions

214
web/app.py Normal file
View File

@@ -0,0 +1,214 @@
from flask import Flask, render_template, jsonify
import pandas as pd
import numpy as np
import os
import ast
import time
app = Flask(__name__)
# 加入邮件通知
import smtplib
from email.mime.text import MIMEText # 导入 MIMEText 类发送纯文本邮件
from email.mime.multipart import (
MIMEMultipart,
)
import akshare as ak
# from email.mime.application import MIMEApplication
# 配置邮件信息
receivers = ["240884432@qq.com"] # 设置邮件接收人地址
subject = "TD_Simnow_Signal" # 设置邮件主题 订单流策略交易信号
# 配置邮件服务器信息
smtp_server = "smtp.qq.com" # 设置发送邮件的 SMTP 服务器地址
smtp_port = 465 # 设置发送邮件的 SMTP 服务器端口号,一般为 25 端口 465
sender = "240884432@qq.com" # 设置发送邮件的邮箱地址
username = "240884432@qq.com" # 设置发送邮件的邮箱用户名
password = "osjyjmbqrzxtbjbf" # zrmpcgttataabhjh设置发送邮件的邮箱密码或授权码
last_sent_time = 0
time_period = 48
# current_dir = os.path.dirname(os.path.abspath(__file__))
# os.chdir(current_dir)
# print("已更改为新的工作目录:", current_dir)
# 获取当前工作目录
current_directory = os.getcwd()
print("当前工作目录:", current_directory)
# 设置新的工作目录
new_directory = "C:/simnow_trader/traderdata"
os.chdir(new_directory)
# 验证新的工作目录
updated_directory = os.getcwd()
print("已更改为新的工作目录:", updated_directory)
# 获取当前文件夹中所有包含"ofdata"字符的CSV文件
def get_csv_files():
files = {}
for filename in os.listdir():
if "ofdata" in filename and filename.endswith(".csv"):
files[filename] = os.path.join(os.getcwd(), filename)
return files
def send_mail(text):
global last_sent_time
# 检查时间间隔
current_time = time.time()
if current_time - last_sent_time < 60:
print("current_time:", current_time)
print("last_sent_time:", last_sent_time)
print("一分钟内已发送过邮件,本次跳过")
return # 直接退出,不执行发送
msg = MIMEMultipart()
msg["From"] = sender
msg["To"] = ";".join(receivers)
msg["Subject"] = subject
html_content = f"""
<html>
<body>
<p>以下是数据的最后一列:</p>
{text}
</body>
</html>
"""
msg.attach(MIMEText(html_content, "html"))
smtp = smtplib.SMTP_SSL(smtp_server, smtp_port)
smtp.login(username, password)
smtp.sendmail(sender, receivers, msg.as_string())
smtp.quit()
# 根据文件路径加载数据只读取前12列
def load_data(file_path):
df = pd.read_csv(file_path, usecols=range(12)) # 只读取前12列
df = df.drop_duplicates(subset="datetime", keep="first").reset_index(drop=True)
df = df[df["high"] != df["low"]]
df["delta"] = df["delta"].astype(float)
df["datetime"] = pd.to_datetime(
df["datetime"], format="mixed"
) # , dayfirst=True, format='mixed'
df["delta累计"] = df.groupby(df["datetime"].dt.date)["delta"].cumsum()
df = df.fillna("缺值")
df["终极平滑值"], df["趋势方向"] = ultimate_smoother(df["close"], time_period)
df["datetime"] = df["datetime"].dt.strftime("%Y-%m-%d %H:%M:%S")
df["POC"] = add_poc_column(df)
if len(df) >= 5 * time_period and (
df["趋势方向"].iloc[-1] != df["趋势方向"].iloc[-2]
):
table_text = (
df.iloc[:, 3:].tail(1).to_html(index=False)
) # price,Ask,Bid,symbol,datetime,delta,close,open,high,low,volume,dj
send_mail(table_text)
else:
pass
return df.iloc[-20:].to_dict(orient="records")
def safe_literal_eval(x):
"""带异常处理的安全转换"""
try:
return ast.literal_eval(x)
except:
return [] # 返回空列表作为占位符
def add_poc_column(df):
# 安全转换列数据
df["price"] = df["price"].apply(safe_literal_eval)
df["Ask"] = df["Ask"].apply(lambda x: list(map(int, safe_literal_eval(x))))
df["Bid"] = df["Bid"].apply(lambda x: list(map(int, safe_literal_eval(x))))
# 定义处理函数(带数据验证)
def find_poc(row):
# 验证三个列表长度一致且非空
if not (len(row["price"]) == len(row["Ask"]) == len(row["Bid"]) > 0):
return "缺值" # 返回空值标记异常数据
sums = [a + b for a, b in zip(row["Ask"], row["Bid"])]
try:
max_index = sums.index(max(sums))
return row["price"][max_index]
except ValueError:
return "缺值" # 处理空求和列表情况
# 应用处理函数
df["POC"] = df.apply(find_poc, axis=1)
# 可选:统计异常数据
error_count = df["POC"].isnull().sum()
if error_count > 0:
print(f"警告:发现 {error_count} 行异常数据已标记为NaN")
return df["POC"]
def ultimate_smoother(price, period):
# 初始化变量(修正角度单位为弧度)
a1 = np.exp(-1.414 * np.pi / period)
b1 = 2 * a1 * np.cos(1.414 * np.pi / period) # 将180改为np.pi
c2 = b1
c3 = -(a1**2)
c1 = (1 + c2 - c3) / 4
# 准备输出序列
us = np.zeros(len(price))
us_new = np.zeros(len(price))
trend = [None] * (len(price))
ma_close = np.zeros(len(price))
# 前4个点用原始价格初始化
for i in range(len(price)):
if i < 4:
us[i] = price[i]
else:
# 应用递归公式
us[i] = (
(1 - c1) * price[i]
+ (2 * c1 - c2) * price[i - 1]
- (c1 + c3) * price[i - 2]
+ c2 * us[i - 1]
+ c3 * us[i - 2]
)
us_new = np.around(us, decimals=2)
ma_close = price.rolling(window=5 * period).mean()
if us_new[i] > price[i] and ma_close[i] > price[i]:
trend[i] = "空头趋势"
elif us_new[i] < price[i] and ma_close[i] < price[i]:
trend[i] = "多头趋势"
else:
trend[i] = "无趋势"
return us_new, trend
@app.route("/")
def index():
files = get_csv_files() # 获取所有符合条件的文件
# 默认显示第一个文件的数据
first_file = list(files.keys())[0] if files else None
data = load_data(files[first_file]) if first_file else []
return render_template("index.html", data=data, files=files, file_name=first_file)
@app.route("/data/<file_name>")
def switch_data(file_name):
files = get_csv_files() # 获取所有符合条件的文件
if file_name in files:
data = load_data(files[file_name])
return jsonify(data)
return jsonify({"error": "File not found"}), 404
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True) # 监听所有网络接口

106
web/templates/index.html Normal file
View File

@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>订单流实时数据监控</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.5/css/dataTables.bootstrap5.min.css">
<style>
table {
width: 100%;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
th, td {
padding: 8px;
text-align: center;
}
button {
margin: 10px;
padding: 10px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Real-Time CSV Data Viewer</h1>
<!-- 动态生成按钮 -->
{% for file_name, file_path in files.items() %}
<button onclick="loadData('{{ file_name }}')">{{ file_name }}</button>
{% endfor %}
<h3>Data for {{ file_name }}</h3>
<table id="data-table">
<thead>
<tr>
<th>Symbol</th>
<th>Datetime</th>
<th>Delta</th>
<th>Close</th>
<th>Open</th>
<th>High</th>
<th>Low</th>
<th>Volume</th>
<th>DJ</th>
<th>Delta累计</th>
<th>POC</th>
<th>终极平滑值</th>
<th>趋势方向</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.symbol }}</td>
<td>{{ row.datetime }}</td>
<td>{{ row.delta }}</td>
<td>{{ row.close }}</td>
<td>{{ row.open }}</td>
<td>{{ row.high }}</td>
<td>{{ row.low }}</td>
<td>{{ row.volume }}</td>
<td>{{ row.dj }}</td>
<td>{{ row.delta累计 }}</td>
<td>{{ row.POC }}</td>
<td>{{ row.终极平滑值 }}</td>
<td>{{ row.趋势方向 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
function loadData(fileName) {
fetch('/data/' + fileName)
.then(response => response.json())
.then(data => {
let tableBody = document.querySelector("#data-table tbody");
tableBody.innerHTML = ''; // 清空现有数据行
data.forEach(row => {
let rowElement = document.createElement('tr');
rowElement.innerHTML = `
<td>${row.symbol}</td>
<td>${row.datetime}</td>
<td>${row.delta}</td>
<td>${row.close}</td>
<td>${row.open}</td>
<td>${row.high}</td>
<td>${row.low}</td>
<td>${row.volume}</td>
<td>${row.dj}</td>
<td>${row.delta累计}</td>
<td>${row.POC}</td>
<td>${row.终极平滑值}</td>
<td>${row.趋势方向}</td>
`;
tableBody.appendChild(rowElement);
});
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,199 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSV Data Viewer</title>
<style>
table {
width: 100%;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
th, td {
padding: 8px;
text-align: center;
}
button {
margin: 10px;
padding: 10px;
cursor: pointer;
}
select {
width: 100%;
padding: 5px;
text-align: center;
}
</style>
</head>
<body>
<h1>Real-Time CSV Data Viewer</h1>
<!-- 动态生成文件切换按钮 -->
{% for file_name, file_path in files.items() %}
<button onclick="loadData('{{ file_name }}')">{{ file_name }}</button>
{% endfor %}
<h3>Data for {{ file_name }}</h3>
<table id="data-table">
<thead>
<tr>
<th>
<select id="filter-price" onchange="filterTable()">
<option value="">Price</option>
</select>
</th>
<th>
<select id="filter-Ask" onchange="filterTable()">
<option value="">Ask</option>
</select>
</th>
<th>
<select id="filter-Bid" onchange="filterTable()">
<option value="">Bid</option>
</select>
</th>
<th>
<select id="filter-symbol" onchange="filterTable()">
<option value="">Symbol</option>
</select>
</th>
<th>
<select id="filter-datetime" onchange="filterTable()">
<option value="">Datetime</option>
</select>
</th>
<th>
<select id="filter-delta" onchange="filterTable()">
<option value="">Delta</option>
</select>
</th>
<th>
<select id="filter-close" onchange="filterTable()">
<option value="">Close</option>
</select>
</th>
<th>
<select id="filter-open" onchange="filterTable()">
<option value="">Open</option>
</select>
</th>
<th>
<select id="filter-high" onchange="filterTable()">
<option value="">High</option>
</select>
</th>
<th>
<select id="filter-low" onchange="filterTable()">
<option value="">Low</option>
</select>
</th>
<th>
<select id="filter-volume" onchange="filterTable()">
<option value="">Volume</option>
</select>
</th>
<th>
<select id="filter-dj" onchange="filterTable()">
<option value="">DJ</option>
</select>
</th>
</tr>
</thead>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.price }}</td>
<td>{{ row.Ask }}</td>
<td>{{ row.Bid }}</td>
<td>{{ row.symbol }}</td>
<td>{{ row.datetime }}</td>
<td>{{ row.delta }}</td>
<td>{{ row.close }}</td>
<td>{{ row.open }}</td>
<td>{{ row.high }}</td>
<td>{{ row.low }}</td>
<td>{{ row.volume }}</td>
<td>{{ row.dj }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
function loadData(fileName) {
fetch('/data/' + fileName)
.then(response => response.json())
.then(data => {
let tableBody = document.querySelector("#data-table tbody");
tableBody.innerHTML = ''; // 清空现有数据行
data.forEach(row => {
let rowElement = document.createElement('tr');
rowElement.innerHTML = `
<td>${row.price}</td>
<td>${row.Ask}</td>
<td>${row.Bid}</td>
<td>${row.symbol}</td>
<td>${row.datetime}</td>
<td>${row.delta}</td>
<td>${row.close}</td>
<td>${row.open}</td>
<td>${row.high}</td>
<td>${row.low}</td>
<td>${row.volume}</td>
<td>${row.dj}</td>
`;
tableBody.appendChild(rowElement);
});
populateFilters(data);
});
}
function populateFilters(data) {
let columns = ["price", "Ask", "Bid", "symbol", "datetime", "delta", "close", "open", "high", "low", "volume", "dj"];
columns.forEach(col => {
let uniqueValues = [...new Set(data.map(row => row[col]))];
let select = document.getElementById(`filter-${col}`);
select.innerHTML = '<option value="">All</option>';
uniqueValues.forEach(value => {
select.innerHTML += `<option value="${value}">${value}</option>`;
});
});
}
function filterTable() {
let filters = {
price: document.getElementById("filter-price").value,
Ask: document.getElementById("filter-Ask").value,
Bid: document.getElementById("filter-Bid").value,
symbol: document.getElementById("filter-symbol").value,
datetime: document.getElementById("filter-datetime").value,
delta: document.getElementById("filter-delta").value,
close: document.getElementById("filter-close").value,
open: document.getElementById("filter-open").value,
high: document.getElementById("filter-high").value,
low: document.getElementById("filter-low").value,
volume: document.getElementById("filter-volume").value,
dj: document.getElementById("filter-dj").value
};
let rows = document.querySelectorAll("#data-table tbody tr");
rows.forEach(row => {
let cells = row.children;
let show = true;
Object.keys(filters).forEach((col, index) => {
if (filters[col] && cells[index].textContent !== filters[col]) {
show = false;
}
});
row.style.display = show ? "" : "none";
});
}
</script>
</body>
</html>