20250408修改

This commit is contained in:
2025-04-09 17:18:30 +08:00
parent f925dff46b
commit aaf2224484
146 changed files with 157794 additions and 5718 deletions

View File

@@ -0,0 +1,339 @@
from flask import Flask, render_template, jsonify, make_response
from flask_socketio import SocketIO
import pandas as pd
import numpy as np
import os
import ast
import time
from datetime import datetime
import requests
# 加入邮件通知
import smtplib
from email.mime.text import MIMEText # 导入 MIMEText 类发送纯文本邮件
from email.mime.multipart import (
MIMEMultipart,
)
# import akshare as ak
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
# 添加安全响应头
@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
# 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
count = 0
time_period = 30
delta_sum_trend=0
delta_trend=0
dj_trend = 0
delta_rate = 0.8
dj_rate = 0.8
# 获取当前工作目录
current_directory = os.getcwd()
print("当前工作目录:", current_directory)
# 设置新的工作目录
new_directory = r"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(subject, text):
global last_sent_time, count
# 检查时间间隔
current_time = time.time()
print('count:',count)
if count == 1 and current_time - last_sent_time <1:
print("current_time:",current_time)
print("last_sent_time:",last_sent_time)
print("一分钟内已发送过邮件,本次跳过")
return
elif count ==1 and current_time - last_sent_time >1:
count = 0
if count == 0 and current_time - last_sent_time < 1:
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())
count = 1
smtp.quit()
# 根据文件路径加载数据只读取前12列
def load_data(file_path):
df = pd.read_csv(file_path, usecols=range(12)).iloc[-1200:] # 只读取前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='ISO8601')#, dayfirst=True, format='mixed'
# df['delta累计'] = df.groupby(df['datetime'].dt.date)['delta'].cumsum()
# 自定义分组逻辑前一日21:00至当日15:00为一天
def get_trading_day(dt):
# 如果时间在21:00之后属于下一个交易日
if dt.hour >= 21:
return (dt + pd.Timedelta(days=1)).date()
# 如果时间在15:00之前属于当前交易日
elif dt.hour < 15:
return dt.date()
# 15:00-21:00之间的数据属于当前交易日
else:
return dt.date()
# 添加交易日列并转换为字符串
df['trading_day'] = df['datetime'].apply(get_trading_day)
df['trading_day'] = df['trading_day'].astype(str) # 将日期转换为字符串
# 按交易日计算delta累计
df['delta累计'] = df.groupby('trading_day')['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)
df['最终趋势'] = finall_trend(df['delta累计'],df['趋势方向'])
# print(df.tail(1))
# print(type(df['delta累计'].iloc[-1]))
def send_feishu_message(text):
headers = {
"Content-Type": "application/json"
}
data = {
"msg_type": "text",
"content": {
"text": text
}
}
response = requests.post("https://open.feishu.cn/open-apis/bot/v2/hook/8608dfa4-e599-462a-8dba-6ac72873dd27", headers=headers, json=data)
if response.status_code != 200:
print(f"飞书消息发送失败,状态码: {response.status_code}, 响应内容: {response.text}")
if df['delta累计'].iloc[-2] < 0 and df['delta累计'].iloc[-1] > 0 and df['趋势方向'].iloc[-1] == '多头趋势':
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_feishu_message("日内delta累计多头信号\n" + table_text)
elif df['delta累计'].iloc[-2] > 0 and df['delta累计'].iloc[-1] < 0 and df['趋势方向'].iloc[-1] == '空头趋势':
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_feishu_message("日内delta累计空头信号\n" + table_text)
else:
pass
# djValues[i] >= maxDJ * 0.8 && ultimateValues[i] > ma120[i]
if df['dj'].iloc[-1] >= 0.8 * max(df['dj'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '多头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_mail("dj多头信号",table_text)
elif df['dj'].iloc[-1] <= 0.8 * min(df['dj'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '空头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_mail("dj空头信号",table_text)
else:
pass
# deltaValues[i] >= maxDelta * 0.8 && ultimateValues[i] > ma120[i])
if df['delta'].iloc[-1] >= 0.8 * max(df['delta'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '多头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_mail("delta多头信号",table_text)
elif df['delta'].iloc[-1] <= 0.8 * min(df['delta'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '空头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_mail("delta空头信号",table_text)
else:
pass
return df.to_dict(orient="records")#.iloc[-48:]
# return df.iloc[-60:].iloc[::-1].to_dict(orient="records")
def finall_trend(delta_sum,trend):
f_trend = [None]*(len(delta_sum))
# delta_sum = delta_sum.astype(float)
for i in range(len(delta_sum)):
if (delta_sum[i] == '缺值') or (trend[i] == '缺值'):
f_trend[i] = '方向不明'
# return f_trend
else:
if delta_sum[i] > 0 and (trend[i] == '多头趋势'):
f_trend[i] = '强多头'
elif delta_sum[i] < 0 and (trend[i] == '空头趋势'):
f_trend[i] = '强空头'
else:
f_trend[i] = '方向不明'
return f_trend
def safe_literal_eval(x):
"""带异常处理的安全转换"""
try:
return ast.literal_eval(x)
except ValueError:
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.iloc[i]
else:
# 应用递归公式
us[i] = (1 - c1) * price.iloc[i] + (2 * c1 - c2) * price.iloc[i-1] \
- (c1 + c3) * price.iloc[i-2] + c2 * us[i-1] + c3 * us[i-2]
us_new = np.around(us, decimals=2)
ma_close = price.rolling(window=4*period).mean()#5*
# 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] = '无趋势'
if us_new[i] < ma_close.iloc[i]:
trend[i] = '空头趋势'
elif us_new[i] > ma_close.iloc[i]:
trend[i] = '多头趋势'
else:
trend[i] = '无趋势'
return us_new,trend
@app.route("/")
def index():
return render_template("index.html")
@app.route("/kline")
def kline():
return render_template("kline.html")
@app.route("/api/data")
def get_data():
try:
files = get_csv_files()
data = {}
for symbol, filename in files.items():
loaded_data = load_data(filename)
if loaded_data:
data[symbol] = loaded_data
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)})
def should_update():
"""检查是否应该在当前时间更新数据"""
now = datetime.now()
# 检查是否是整点5分钟
if now.minute % 2 == 0:
# 检查是否在5秒内
if now.second < 2:
return True
return False
def background_thread():
"""后台线程在每整点5分钟的5秒内发送数据更新"""
while True:
if should_update():
files = get_csv_files()
data = {}
for file_name, file_path in files.items():
data[file_name] = load_data(file_path)
socketio.emit('data_update', data)
print(f"数据更新完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
time.sleep(1) # 每秒检查一次
@socketio.on('connect')
def handle_connect():
print('Client connected')
# 启动后台线程
socketio.start_background_task(background_thread)
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected')
if __name__ == "__main__":
socketio.run(app, host='0.0.0.0', port=5000, debug=True) # 监听所有网络接口

View File

@@ -0,0 +1,344 @@
from flask import Flask, render_template, jsonify, make_response
from flask_socketio import SocketIO
import pandas as pd
import numpy as np
import os
import ast
import time
from datetime import datetime
import requests
# 加入邮件通知
import smtplib
from email.mime.text import MIMEText # 导入 MIMEText 类发送纯文本邮件
from email.mime.multipart import (
MIMEMultipart,
)
# import akshare as ak
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
# 添加安全响应头
@app.after_request
def add_security_headers(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
# 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
count = 0
time_period = 30
delta_sum_trend=0
delta_trend=0
dj_trend = 0
delta_rate = 0.8
dj_rate = 0.8
# 获取当前工作目录
current_directory = os.getcwd()
print("当前工作目录:", current_directory)
# 设置新的工作目录
new_directory = r"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(subject, text):
global last_sent_time, count
# 检查时间间隔
current_time = time.time()
print('count:',count)
if count == 1 and current_time - last_sent_time <1:
print("current_time:",current_time)
print("last_sent_time:",last_sent_time)
print("一分钟内已发送过邮件,本次跳过")
return
elif count ==1 and current_time - last_sent_time >1:
count = 0
if count == 0 and current_time - last_sent_time < 1:
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())
count = 1
smtp.quit()
# 根据文件路径加载数据只读取前12列
def load_data(file_path):
df = pd.read_csv(file_path, usecols=range(12)).iloc[-1200:] # 只读取前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='ISO8601')#, dayfirst=True, format='mixed'
# df['delta累计'] = df.groupby(df['datetime'].dt.date)['delta'].cumsum()
# 自定义分组逻辑前一日21:00至当日15:00为一天
def get_trading_day(dt):
# 如果时间在21:00之后属于下一个交易日
if dt.hour >= 21:
return (dt + pd.Timedelta(days=1)).date()
# 如果时间在15:00之前属于当前交易日
elif dt.hour < 15:
return dt.date()
# 15:00-21:00之间的数据属于当前交易日
else:
return dt.date()
# 添加交易日列并转换为字符串
df['trading_day'] = df['datetime'].apply(get_trading_day)
df['trading_day'] = df['trading_day'].astype(str) # 将日期转换为字符串
# 按交易日计算delta累计
df['delta累计'] = df.groupby('trading_day')['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)
df['最终趋势'] = finall_trend(df['delta累计'],df['趋势方向'])
# print(df.tail(1))
# print(type(df['delta累计'].iloc[-1]))
def send_feishu_message(text):
headers = {
"Content-Type": "application/json"
}
table_html = f'<table border="1" class="dataframe">\n <thead>\n <tr style="text-align: right;">\n <th>symbol</th>\n <th>datetime</th>\n <th>delta</th>\n <th>close</th>\n <th>open</th>\n <th>high</th>\n <th>low</th>\n <th>volume</th>\n <th>dj</th>\n <th>trading_day</th>\n <th>delta累计</th>\n <th>终极平滑值</th>\n <th>趋势方向</th>\n <th>POC</th>\n <th>最终趋势</th>\n </tr>\n </thead>\n <tbody>\n{text}\n </tbody>\n</table>'
data = {
"msg_type": "text",
"content": {
"text": table_html
}
}
response = requests.post("https://open.feishu.cn/open-apis/bot/v2/hook/8608dfa4-e599-462a-8dba-6ac72873dd27", headers=headers, json=data)
if response.status_code != 200:
print(f"飞书消息发送失败,状态码: {response.status_code}, 响应内容: {response.text}")
if df['delta累计'].iloc[-2] < 0 and df['delta累计'].iloc[-1] > 0 and df['趋势方向'].iloc[-1] == '多头趋势':
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_feishu_message("日内delta累计多头信号\n" + table_text)
elif df['delta累计'].iloc[-2] > 0 and df['delta累计'].iloc[-1] < 0 and df['趋势方向'].iloc[-1] == '空头趋势':
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
send_feishu_message("日内delta累计空头信号\n" + table_text)
else:
pass
# djValues[i] >= maxDJ * 0.8 && ultimateValues[i] > ma120[i]
if df['dj'].iloc[-1] >= 0.8 * max(df['dj'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '多头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
# send_mail("dj多头信号",table_text)
send_feishu_message("dj多头信号\n" + table_text)
elif df['dj'].iloc[-1] <= 0.8 * min(df['dj'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '空头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
# send_mail("dj空头信号",table_text)
send_feishu_message("dj空头信号\n" + table_text)
else:
pass
# deltaValues[i] >= maxDelta * 0.8 && ultimateValues[i] > ma120[i])
if df['delta'].iloc[-1] >= 0.8 * max(df['delta'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '多头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
# send_mail("delta多头信号",table_text)
send_feishu_message("delta多头信号\n" + table_text)
elif df['delta'].iloc[-1] <= 0.8 * min(df['delta'].iloc[-121:-1] ) and df['趋势方向'].iloc[-1] == '空头趋势' :
table_text = df.iloc[:,3:].tail(1).to_html(index=False)
# send_mail("delta空头信号",table_text)
send_feishu_message("delta空头信号\n" + table_text)
else:
pass
return df.to_dict(orient="records")#.iloc[-48:]
# return df.iloc[-60:].iloc[::-1].to_dict(orient="records")
def finall_trend(delta_sum,trend):
f_trend = [None]*(len(delta_sum))
# delta_sum = delta_sum.astype(float)
for i in range(len(delta_sum)):
if (delta_sum[i] == '缺值') or (trend[i] == '缺值'):
f_trend[i] = '方向不明'
# return f_trend
else:
if delta_sum[i] > 0 and (trend[i] == '多头趋势'):
f_trend[i] = '强多头'
elif delta_sum[i] < 0 and (trend[i] == '空头趋势'):
f_trend[i] = '强空头'
else:
f_trend[i] = '方向不明'
return f_trend
def safe_literal_eval(x):
"""带异常处理的安全转换"""
try:
return ast.literal_eval(x)
except ValueError:
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.iloc[i]
else:
# 应用递归公式
us[i] = (1 - c1) * price.iloc[i] + (2 * c1 - c2) * price.iloc[i-1] \
- (c1 + c3) * price.iloc[i-2] + c2 * us[i-1] + c3 * us[i-2]
us_new = np.around(us, decimals=2)
ma_close = price.rolling(window=4*period).mean()#5*
# 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] = '无趋势'
if us_new[i] < ma_close.iloc[i]:
trend[i] = '空头趋势'
elif us_new[i] > ma_close.iloc[i]:
trend[i] = '多头趋势'
else:
trend[i] = '无趋势'
return us_new,trend
@app.route("/")
def index():
return render_template("index.html")
@app.route("/kline")
def kline():
return render_template("kline.html")
@app.route("/api/data")
def get_data():
try:
files = get_csv_files()
data = {}
for symbol, filename in files.items():
loaded_data = load_data(filename)
if loaded_data:
data[symbol] = loaded_data
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)})
def should_update():
"""检查是否应该在当前时间更新数据"""
now = datetime.now()
# 检查是否是整点5分钟
if now.minute % 2 == 0:
# 检查是否在5秒内
if now.second < 2:
return True
return False
def background_thread():
"""后台线程在每整点5分钟的5秒内发送数据更新"""
while True:
if should_update():
files = get_csv_files()
data = {}
for file_name, file_path in files.items():
data[file_name] = load_data(file_path)
socketio.emit('data_update', data)
print(f"数据更新完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
time.sleep(1) # 每秒检查一次
@socketio.on('connect')
def handle_connect():
print('Client connected')
# 启动后台线程
socketio.start_background_task(background_thread)
@socketio.on('disconnect')
def handle_disconnect():
print('Client disconnected')
if __name__ == "__main__":
socketio.run(app, host='0.0.0.0', port=5000, debug=True) # 监听所有网络接口

View File

@@ -0,0 +1,135 @@
<!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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<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;
}
.active-symbol {
background-color: #e0e0e0;
}
</style>
</head>
<body>
<h1>Real-Time CSV Data Viewer</h1>
<div id="symbol-buttons">
<!-- 动态生成按钮 -->
</div>
<h3>Data for <span id="current-symbol">Loading...</span></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>
<th>最终趋势</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
let currentSymbol = null;
const socket = io();
const symbolButtons = document.getElementById('symbol-buttons');
const currentSymbolDisplay = document.getElementById('current-symbol');
const tableBody = document.querySelector("#data-table tbody");
// 初始化数据
fetch('/api/data')
.then(response => response.json())
.then(data => {
updateSymbolButtons(data);
if (Object.keys(data).length > 0) {
currentSymbol = Object.keys(data)[0];
updateTable(data[currentSymbol]);
}
});
// WebSocket事件处理
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('data_update', (data) => {
updateSymbolButtons(data);
if (currentSymbol && data[currentSymbol]) {
updateTable(data[currentSymbol]);
}
});
function updateSymbolButtons(data) {
symbolButtons.innerHTML = '';
Object.keys(data).forEach(symbol => {
const button = document.createElement('button');
button.textContent = symbol;
button.onclick = () => {
currentSymbol = symbol;
updateTable(data[symbol]);
};
if (symbol === currentSymbol) {
button.classList.add('active-symbol');
}
symbolButtons.appendChild(button);
});
}
function updateTable(data) {
currentSymbolDisplay.textContent = currentSymbol;
tableBody.innerHTML = '';
data.forEach(row => {
const 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 !== undefined ? row.dj : ''}</td>
<td>${row.delta累计 || ''}</td>
<td>${row.POC || ''}</td>
<td>${row.终极平滑值 || ''}</td>
<td>${row.趋势方向 || ''}</td>
<td>${row.最终趋势 || ''}</td>
`;
tableBody.appendChild(rowElement);
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,531 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>实时K线图</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
#kline-chart {
width: 100%;
height: 800px;
margin: 20px auto;
}
.symbol-selector {
margin: 20px;
text-align: center;
}
button {
margin: 10px;
padding: 10px;
cursor: pointer;
}
.active-symbol {
background-color: #e0e0e0;
}
body {
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
</style>
</head>
<body>
<div class="symbol-selector" id="symbol-buttons">
<!-- 动态生成按钮 -->
</div>
<div id="kline-chart"></div>
<script>
let currentSymbol = null;
const socket = io();
const symbolButtons = document.getElementById('symbol-buttons');
let chart = null;
// 初始化图表
function initChart() {
if (!chart) {
chart = echarts.init(document.getElementById('kline-chart'));
}
}
// 初始化数据
fetch('/api/data')
.then(response => response.json())
.then(data => {
updateSymbolButtons(data);
if (Object.keys(data).length > 0) {
currentSymbol = Object.keys(data)[0];
updateChart(data[currentSymbol]);
}
})
.catch(error => {
console.error('Error fetching data:', error);
});
// WebSocket事件处理
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('data_update', (data) => {
updateSymbolButtons(data);
if (currentSymbol && data[currentSymbol]) {
updateChart(data[currentSymbol]);
}
});
function updateSymbolButtons(data) {
symbolButtons.innerHTML = '';
Object.keys(data).forEach(symbol => {
const button = document.createElement('button');
button.textContent = symbol;
button.onclick = () => {
currentSymbol = symbol;
updateChart(data[symbol]);
};
if (symbol === currentSymbol) {
button.classList.add('active-symbol');
}
symbolButtons.appendChild(button);
});
}
function updateChart(data) {
initChart();
// 准备数据
const dates = data.map(item => item.datetime);
const klineData = data.map(item => [
parseFloat(item.open),
parseFloat(item.close),
parseFloat(item.low),
parseFloat(item.high)
]);
const volumes = data.map(item => parseFloat(item.volume));
const ultimateValues = data.map(item => parseFloat(item.终极平滑值));
const deltaSums = data.map(item => parseFloat(item.delta累计));
const djValues = data.map(item => parseFloat(item.dj));
const deltaValues = data.map(item => parseFloat(item.delta));
// 处理POC数据将缺值替换为前一个有效值
let pocValues = data.map(item => item.POC);
let lastValidPoc = null;
pocValues = pocValues.map(value => {
if (value === '缺值') {
return lastValidPoc;
} else {
lastValidPoc = parseFloat(value);
return lastValidPoc;
}
});
// 计算120日均线
const closes = data.map(item => parseFloat(item.close));
const ma120 = calculateMA(closes, 120);
// 处理 delta 累计数据,用于标记箭头
const arrowMarks = [];
for (let i = 1; i < deltaSums.length; i++) {
if (deltaSums[i - 1] < 0 && deltaSums[i] > 0 && ultimateValues[i] > ma120[i]) {
// 前一个值小于0后一个值大于0标记向上箭头
arrowMarks.push({
coord: [dates[i], data[i].low - 0.1], // 标记在 K 线下方
symbol: 'path://M0,10 L5,0 L10,10 Z',
symbolSize: [10, 10],
symbolOffset: [0, 5],
itemStyle: {
color: 'red'
}
});
} else if (deltaSums[i - 1] > 0 && deltaSums[i] < 0 && ultimateValues[i] < ma120[i] ) {
// 前一个值大于0后一个值小于0标记向下箭头
arrowMarks.push({
coord: [dates[i], data[i].high + 0.1], // 标记在 K 线上方
symbol: 'path://M0,0 L5,10 L10,0 Z',
symbolSize: [10, 10],
symbolOffset: [0, -5],
itemStyle: {
color: 'green'
}
});
}
}
// 处理 dj 数据,用于标记圆
const circleMarks = [];
for (let i = 0; i < djValues.length; i++) {
let startIndex = Math.max(0, i - 119);
let recentDJValues = djValues.slice(startIndex, i + 1);
let maxDJ = Math.max(...recentDJValues);
let minDJ = Math.min(...recentDJValues);
if (djValues[i] >= maxDJ * 0.8 && ultimateValues[i] > ma120[i]) {
// dj 大于等于最近120个dj值的最大值的80%,标记向上的红色圆
circleMarks.push({
coord: [dates[i], data[i].low - 5.1], // 标记在 K 线下方
symbol: 'circle',
symbolSize: 10,
symbolOffset: [0, 5],
itemStyle: {
color: 'red'
}
});
} else if (djValues[i] <= minDJ * 0.8 && ultimateValues[i] < ma120[i]) {
// dj 小于等于最近120个dj值的最小值的80%,标记向下的绿色圆
circleMarks.push({
coord: [dates[i], data[i].high + 5.1], // 标记在 K 线上方
symbol: 'circle',
symbolSize: 10,
symbolOffset: [0, -5],
itemStyle: {
color: 'green'
}
});
}
}
// 处理 delta 值数据,用于标记方块
const squareMarks = [];
for (let i = 0; i < deltaValues.length; i++) {
let startIndex = Math.max(0, i - 119);
let recentDeltaValues = deltaValues.slice(startIndex, i + 1);
let maxDelta = Math.max(...recentDeltaValues);
let minDelta = Math.min(...recentDeltaValues);
if (deltaValues[i] >= maxDelta * 0.8 && ultimateValues[i] > ma120[i]) {
// delta 值大于等于最近120个delta值的最大值的80%,标记向上的红色方块
squareMarks.push({
coord: [dates[i], data[i].low - 10.1],
symbol: 'rect',
symbolSize: 10,
symbolOffset: [0, 5],
itemStyle: {
color: 'red'
}
});
} else if (deltaValues[i] <= minDelta * 0.8 && ultimateValues[i] < ma120[i]) {
// delta 值小于等于最近120个delta值的最小值的80%,标记向上的绿色方块
squareMarks.push({
coord: [dates[i], data[i].high + 10.1],
symbol: 'rect',
symbolSize: 10,
symbolOffset: [0, -5],
itemStyle: {
color: 'green'
}
});
}
}
// 合并箭头标记、圆标记和方块标记
const allMarks = arrowMarks.concat(circleMarks).concat(squareMarks);
// 配置图表选项
const option = {
title: {
text: `${currentSymbol} K线图`,
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['K线', '120日均线', '终极平滑值', 'POC', '成交量', 'Delta累计', 'DJ值', 'Delta值'],
top: 30
},
grid: [
{
left: '10%',
right: '8%',
height: '40%'
},
{
left: '10%',
right: '8%',
top: '50%',
height: '10%'
},
{
left: '10%',
right: '8%',
top: '60%',
height: '10%'
},
{
left: '10%',
right: '8%',
top: '70%',
height: '10%'
},
{
left: '10%',
right: '8%',
top: '80%',
height: '10%'
}
],
xAxis: [
{
type: 'category',
data: dates,
scale: true,
boundaryGap: false,
axisLine: {onZero: false},
splitLine: {show: false},
splitNumber: 20,
gridIndex: 0
},
{
type: 'category',
gridIndex: 1,
data: dates,
axisLabel: {show: false}
},
{
type: 'category',
gridIndex: 2,
data: dates,
axisLabel: {show: false}
},
{
type: 'category',
gridIndex: 3,
data: dates,
axisLabel: {show: false}
},
{
type: 'category',
gridIndex: 4,
data: dates,
axisLabel: {show: true}
}
],
yAxis: [
{
scale: true,
splitArea: {
show: true
},
gridIndex: 0
},
{
scale: true,
gridIndex: 1,
splitNumber: 2,
axisLabel: {show: true},
axisLine: {show: true},
splitLine: {show: false}
},
{
scale: true,
gridIndex: 2,
splitNumber: 2,
axisLabel: {show: true},
axisLine: {show: true},
splitLine: {show: false}
},
{
scale: true,
gridIndex: 3,
splitNumber: 2,
axisLabel: {show: true},
axisLine: {show: true},
splitLine: {show: false}
},
{
scale: true,
gridIndex: 4,
splitNumber: 2,
axisLabel: {show: true},
axisLine: {show: true},
splitLine: {show: false}
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1, 2, 3, 4],
start: 50,
end: 100
},
{
show: true,
xAxisIndex: [0, 1, 2, 3, 4],
type: 'slider',
bottom: '2%',
start: 50,
end: 100
}
],
series: [
{
name: 'K线',
type: 'candlestick',
data: klineData,
itemStyle: {
color: 'none', // 空心 K 线,填充颜色设为无
color0: 'none',
borderColor: '#ef232a',
borderColor0: '#14b143',
borderWidth: 1
},
// 添加标记点
markPoint: {
data: allMarks
}
},
{
name: '120日均线',
type: 'line',
data: ma120,
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: '终极平滑值',
type: 'line',
data: ultimateValues,
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: 'POC',
type: 'line',
data: pocValues,
smooth: true,
lineStyle: {
color: '#FFD700',
width: 2,
opacity: 0.8
},
symbol: 'circle',
symbolSize: 6
},
{
name: '成交量',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
data: volumes
},
{
name: 'Delta累计',
type: 'line',
xAxisIndex: 2,
yAxisIndex: 2,
data: deltaSums,
smooth: true,
lineStyle: {
color: '#4169E1',
width: 2,
opacity: 0.8
},
markLine: {
silent: true,
data: [
{
yAxis: 0,
lineStyle: {
color: '#999',
type: 'dashed'
}
}
]
}
},
{
name: 'DJ值',
type: 'line',
xAxisIndex: 3,
yAxisIndex: 3,
data: djValues,
smooth: true,
lineStyle: {
color: '#9932CC',
width: 2,
opacity: 0.8
},
markLine: {
silent: true,
data: [
{
yAxis: 0,
lineStyle: {
color: '#999',
type: 'dashed'
}
}
]
}
},
{
name: 'Delta值',
type: 'line',
xAxisIndex: 4,
yAxisIndex: 4,
data: deltaValues,
smooth: true,
lineStyle: {
color: '#FF8C00',
width: 2,
opacity: 0.8
},
markLine: {
silent: true,
data: [
{
yAxis: 0,
lineStyle: {
color: '#999',
type: 'dashed'
}
}
]
}
}
]
};
// 使用配置项显示图表
chart.setOption(option);
}
function calculateMA(data, dayCount) {
const result = [];
for (let i = 0, len = data.length; i < len; i++) {
if (i < dayCount - 1) {
result.push('-');
continue;
}
let sum = 0;
for (let j = 0; j < dayCount; j++) {
sum += data[i - j];
}
result.push(+(sum / dayCount).toFixed(2));
}
return result;
}
// 响应窗口大小变化
window.addEventListener('resize', function() {
if (chart) {
chart.resize();
}
});
// 初始化图表
initChart();
</script>
</body>
</html>