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"""

以下是数据的最后一列:

{text} """ 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'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n{text}\n \n
symboldatetimedeltacloseopenhighlowvolumedjtrading_daydelta累计终极平滑值趋势方向POC最终趋势
' 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] 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) # 监听所有网络接口