From 878901826c4c51e5c226cb600519768990d42c6b Mon Sep 17 00:00:00 2001 From: lz_db Date: Thu, 4 Dec 2025 22:03:02 +0800 Subject: [PATCH] xm --- .gitignore | 6 + sync/__pycache__/account_sync.cpython-311.pyc | Bin 9585 -> 9062 bytes sync/__pycache__/order_sync.cpython-311.pyc | Bin 13351 -> 17527 bytes sync/account_sync.py | 12 +- sync/order_sync.py | 437 +++++++++++------- .../__pycache__/redis_client.cpython-311.pyc | Bin 23340 -> 29036 bytes utils/redis_client.py | 136 +++++- 数据库.sql | 204 ++++++++ 8 files changed, 604 insertions(+), 191 deletions(-) create mode 100644 .gitignore create mode 100644 数据库.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6895f0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +test.py +__pycache__ +logs +.env +*.log +.DS_Store \ No newline at end of file diff --git a/sync/__pycache__/account_sync.cpython-311.pyc b/sync/__pycache__/account_sync.cpython-311.pyc index 924a14605f16b6642b760808a7261f364a6bbf2f..68ddf98c3e9309cb2ff778d3863e0fd1888c4bba 100644 GIT binary patch delta 1125 zcmZ{iO=uHA6o6-v-DIB!GkAHo_i6>dhk>)y|h?7^yIu*w6>(f%*UJe-n{o_-fUg}c{TB- zwpK-aY&;t%eQjM%Xn1n@SY{X_Gi1tkw5;S5AxG?3Sqfd~jSon1qp%uP@ zk&BzLTgXtCKObod`O=LNb$#hdspuNmmt1S!Wg6D;dp3eUNZhZ+`;9+_aSeMbzrd<@ zCbB4vHt|SA1~hUQGHC|6jl+*pi_(%aLo`sOI4C%F!5}T5Yhc^zAWnNm{7xj2;F^lT za!H-7B=w8}4kBv&ZdDMpOy)`f`C%%dK84RMW>=I(ipHVJBgK z3IOluD^r$RoQ4|rT^uez#sJp<2i|sHn-l+CXao*k;_%g?Gh&+-FcWna5?Z4J>qlT^b^iB9GowEyeaoQ?B3|=}}c1+8rS%;MKffytcfgxEC1bV)s z40Df)ZH1^tRhet3b#?DtL+RDMb5oiNVP0)@F3oQV%k}|wQQIFf C?DF>j delta 1581 zcmah}O>7%Q6rS<=XT6RcJKM-^O5C_jn;1jW&;X^PO;e#(f|3J41uch_>)q6*yIwlG zhPV_t66HrACAf1)g*a3~P$49$VnC_{5=cEFk)R^1l@MGK!L3y}!G-t6DTES>+1YR3 zyq|gVerDH>-g-Q7IUZLCjQE-H%18G!6B)YD94VCTh!(MBO{T;M7$GaFMa5Uq6!?a% zm>tt%c8}I0dh7~am%`i#&F$;X-t zxr0GJc)a3ze(56{bJS7o7acw zXU*TPDCddRYxKaL@z#6vszg(KIVi!hGx z0Kz_i+AuGsv;2|t8;x!%>>*T60QixjVdzE6>VE9l#$%(2S}c{ETGi8`Dn0~?4$4@mp3?bbX7@G`V+^E!$9N~9c}VC4J>B{|^8SUQ*7QncjO?zwVc6_A!jlMHb97g;BsQes z2pS#(@RgENHR2TyI>%ji#OJcvtp`yUVWwNNJlB_)^AbCXMp=F(n+}V9I?TV#?xXzE z?O)KHB?`hj18>Kx@ZK&1JV7W~BIn2@zS6H=kdx9Y^c-J|r1@+>ouw~JC&-fYdJyg# zg`N-8dckD%wJ%pcxwf`+d2Qi?)y4Pm0;Nxxp1xi-SD$5$tuxc8xNHdY^MOuiDWKf| zz>zl3yFzKQjb03j_GUla6Dt46d>LSss}oul9mvicV#Rk$VDR65$yH zk;o)c2>1tc|KyXo)m=|hDKvVM{1-ZQsGV2&fgNKFCZjoCLO=Mt3qJsHxo|6nUF$3E z8LMcO%BEcxjm6@~P1z3oh1@+3v-J+yIixttzt8uF{3vMDEb|aE!4xa){tR$aqLjAz z^&Pv6k!A8fvrS_2;@LoqE|X1Wo8*=^n>@91%hZbj3R&44-ebY diff --git a/sync/__pycache__/order_sync.cpython-311.pyc b/sync/__pycache__/order_sync.cpython-311.pyc index 492ac4cdce3de0d32f992a20cb88c56b0072ee48..657b9c4fe44e4b9b87fbdf85cbb443e1b6a1fa11 100644 GIT binary patch literal 17527 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 b3={abO!ZJCFm`PkJ@qG?^XEMIc List[Dict]: - """收集所有账号的订单数据""" - all_orders = [] - - try: - # 按交易所分组账号 - account_groups = self._group_accounts_by_exchange(accounts) - - # 并发收集每个交易所的数据 - tasks = [] - for exchange_id, account_list in account_groups.items(): - task = self._collect_exchange_orders(exchange_id, account_list) - tasks.append(task) - - # 等待所有任务完成并合并结果 - results = await asyncio.gather(*tasks, return_exceptions=True) - - for result in results: - if isinstance(result, list): - all_orders.extend(result) - - except Exception as e: - logger.error(f"收集订单数据失败: {e}") - - return all_orders - - def _group_accounts_by_exchange(self, accounts: Dict[str, Dict]) -> Dict[str, List[Dict]]: - """按交易所分组账号""" - groups = {} - for account_id, account_info in accounts.items(): - exchange_id = account_info.get('exchange_id') - if exchange_id: - if exchange_id not in groups: - groups[exchange_id] = [] - groups[exchange_id].append(account_info) - return groups - - async def _collect_exchange_orders(self, exchange_id: str, account_list: List[Dict]) -> List[Dict]: - """收集某个交易所的订单数据""" - orders_list = [] - - try: - # 并发获取每个账号的数据 - tasks = [] - for account_info in account_list: - k_id = int(account_info['k_id']) - st_id = account_info.get('st_id', 0) - task = self._get_recent_orders_from_redis(k_id, st_id, exchange_id) - tasks.append(task) - - results = await asyncio.gather(*tasks, return_exceptions=True) - - for result in results: - if isinstance(result, list): - orders_list.extend(result) - - logger.debug(f"交易所 {exchange_id}: 收集到 {len(orders_list)} 条订单") - - except Exception as e: - logger.error(f"收集交易所 {exchange_id} 订单数据失败: {e}") - - return orders_list - - async def _get_recent_orders_from_redis(self, k_id: int, st_id: int, exchange_id: str) -> List[Dict]: - """从Redis获取最近N天的订单数据""" - try: - redis_key = f"{exchange_id}:orders:{k_id}" - - # 计算最近N天的日期 - today = datetime.now() - recent_dates = [] - for i in range(self.recent_days): - date = today - timedelta(days=i) - date_format = date.strftime('%Y-%m-%d') - recent_dates.append(date_format) - - # 使用scan获取所有符合条件的key - cursor = 0 - recent_keys = [] - - while True: - cursor, keys = self.redis_client.client.hscan(redis_key, cursor, count=1000) - - for key, _ in keys.items(): - key_str = key.decode('utf-8') if isinstance(key, bytes) else key - - if key_str == 'positions': - continue - - # 检查是否以最近N天的日期开头 - for date_format in recent_dates: - if key_str.startswith(date_format + '_'): - recent_keys.append(key_str) - break - - if cursor == 0: - break - - if not recent_keys: - return [] - - # 批量获取订单数据 - orders_list = [] - - # 分批获取,避免单次hgetall数据量太大 - chunk_size = 500 - for i in range(0, len(recent_keys), chunk_size): - chunk_keys = recent_keys[i:i + chunk_size] - - # 使用hmget批量获取 - chunk_values = self.redis_client.client.hmget(redis_key, chunk_keys) - - for key, order_json in zip(chunk_keys, chunk_values): - if not order_json: - continue - - try: - order = json.loads(order_json) - - # 验证时间 - order_time = order.get('time', 0) - if order_time >= int(time.time()) - self.recent_days * 24 * 3600: - # 添加账号信息 - order['k_id'] = k_id - order['st_id'] = st_id - order['exchange_id'] = exchange_id - orders_list.append(order) - - except json.JSONDecodeError as e: - logger.debug(f"解析订单JSON失败: key={key}, error={e}") - continue - - return orders_list - - except Exception as e: - logger.error(f"获取Redis订单数据失败: k_id={k_id}, error={e}") - return [] - + + + async def _sync_orders_batch_to_db(self, all_orders: List[Dict]) -> Tuple[bool, int]: - """批量同步订单数据到数据库""" + """批量同步订单数据到数据库 + + Args: + all_orders: 订单数据列表 + + Returns: + Tuple[bool, int]: (是否成功, 处理的订单数量) + """ + if not all_orders: + return True, 0 + + session = self.db_manager.get_session() + + processed_count = 0 + errors = [] + try: - if not all_orders: - return True, 0 - - # 转换数据 - converted_orders = [] - for order in all_orders: + # 按批次处理 + for i in range(0, len(all_orders), self.batch_size): + batch_orders = all_orders[i:i + self.batch_size] + try: - order_dict = self._convert_order_data(order) + session.begin() - # 检查完整性 - required_fields = ['order_id', 'symbol', 'side', 'time'] - if not all(order_dict.get(field) for field in required_fields): + # 转换数据并准备批量插入 + converted_orders = [] + for raw_order in batch_orders: + try: + converted = self._convert_order_data(raw_order) + + # 检查必要字段 + if not all([ + converted.get('order_id'), + converted.get('symbol'), + converted.get('k_id'), + converted.get('side') + ]): + logger.warning(f"订单缺少必要字段: {raw_order}") + continue + + converted_orders.append(converted) + + except Exception as e: + logger.error(f"转换订单数据失败: {raw_order}, error={e}") + continue + + if not converted_orders: + session.commit() continue - converted_orders.append(order_dict) + # 批量插入或更新 + upsert_sql = text(""" + INSERT INTO deh_strategy_order_new + (st_id, k_id, asset, order_id, symbol, side, price, time, + order_qty, last_qty, avg_price, exchange_id) + VALUES + (:st_id, :k_id, :asset, :order_id, :symbol, :side, :price, :time, + :order_qty, :last_qty, :avg_price, :exchange_id) + ON DUPLICATE KEY UPDATE + price = VALUES(price), + time = VALUES(time), + order_qty = VALUES(order_qty), + last_qty = VALUES(last_qty), + avg_price = VALUES(avg_price) + """) + + result = session.execute(upsert_sql, converted_orders) + processed_count += len(converted_orders) + + # 计算统计信息 + batch_size = len(converted_orders) + total_affected = result.rowcount + updated_count = max(0, total_affected - batch_size) + inserted_count = batch_size - updated_count + + logger.debug(f"订单批次 {i//self.batch_size + 1}: " + f"处理 {batch_size} 条, " + f"插入 {inserted_count} 条, " + f"更新 {updated_count} 条") + + session.commit() except Exception as e: - logger.error(f"转换订单数据失败: {order}, error={e}") - continue + session.rollback() + error_msg = f"订单批次 {i//self.batch_size + 1} 处理失败: {str(e)}" + logger.error(error_msg, exc_info=True) + errors.append(error_msg) + # 继续处理下一个批次 - if not converted_orders: - return True, 0 - - # 使用批量工具同步 - from utils.batch_order_sync import BatchOrderSync - batch_tool = BatchOrderSync(self.db_manager, self.batch_size) - - success, processed_count = batch_tool.sync_orders_batch(converted_orders) + if errors: + logger.error(f"订单同步完成但有错误: {len(errors)} 个错误") + for error in errors[:5]: # 只打印前5个错误 + logger.error(f"错误详情: {error}") + if len(errors) > 5: + logger.error(f"...还有 {len(errors) - 5} 个错误") + success = len(errors) == 0 return success, processed_count except Exception as e: - logger.error(f"批量同步订单到数据库失败: {e}") - return False, 0 + logger.error(f"订单批量同步失败: {e}", exc_info=True) + return False, processed_count + + finally: + session.close() + async def _sync_orders_batch_to_db_enhanced(self, all_orders: List[Dict]) -> Tuple[bool, Dict]: + """增强版:批量同步订单数据到数据库(带详细统计) + + Args: + all_orders: 订单数据列表 + + Returns: + Tuple[bool, Dict]: (是否成功, 统计结果) + """ + if not all_orders: + return True, {'total': 0, 'processed': 0, 'inserted': 0, 'updated': 0, 'errors': []} + + session = self.db_manager.get_session() + + results = { + 'total': len(all_orders), + 'processed': 0, + 'inserted': 0, + 'updated': 0, + 'errors': [], + 'invalid_orders': 0 + } + + try: + logger.info(f"开始同步 {results['total']} 条订单数据,批次大小: {self.batch_size}") + + # 按批次处理 + total_batches = (len(all_orders) + self.batch_size - 1) // self.batch_size + + for batch_idx in range(total_batches): + start_idx = batch_idx * self.batch_size + end_idx = start_idx + self.batch_size + batch_orders = all_orders[start_idx:end_idx] + + logger.debug(f"处理批次 {batch_idx + 1}/{total_batches}: " + f"订单 {start_idx + 1}-{min(end_idx, len(all_orders))}") + + try: + session.begin() + + # 转换数据 + converted_orders = [] + batch_invalid = 0 + + for raw_order in batch_orders: + try: + converted = self._convert_order_data(raw_order) + + # 验证必要字段 + required_fields = ['order_id', 'symbol', 'k_id', 'side'] + missing_fields = [field for field in required_fields if not converted.get(field)] + + if missing_fields: + logger.warning(f"订单缺少必要字段 {missing_fields}: {raw_order}") + batch_invalid += 1 + continue + + # 验证字段长度(防止数据库错误) + order_id = converted.get('order_id', '') + if len(order_id) > 765: # 根据表结构限制 + converted['order_id'] = order_id[:765] + logger.warning(f"order_id过长已截断: {order_id}") + + symbol = converted.get('symbol', '') + if len(symbol) > 120: + converted['symbol'] = symbol[:120] + + side = converted.get('side', '') + if len(side) > 120: + converted['side'] = side[:120] + + converted_orders.append(converted) + + except Exception as e: + logger.error(f"处理订单失败: {raw_order}, error={e}") + batch_invalid += 1 + continue + + results['invalid_orders'] += batch_invalid + + if not converted_orders: + session.commit() + continue + + # 批量插入或更新 + upsert_sql = text(""" + INSERT INTO deh_strategy_order_new + (st_id, k_id, asset, order_id, symbol, side, price, time, + order_qty, last_qty, avg_price, exchange_id) + VALUES + (:st_id, :k_id, :asset, :order_id, :symbol, :side, :price, :time, + :order_qty, :last_qty, :avg_price, :exchange_id) + ON DUPLICATE KEY UPDATE + price = VALUES(price), + time = VALUES(time), + order_qty = VALUES(order_qty), + last_qty = VALUES(last_qty), + avg_price = VALUES(avg_price), + updated_at = CURRENT_TIMESTAMP + """) + + result = session.execute(upsert_sql, converted_orders) + + # 统计本批次结果 + batch_size = len(converted_orders) + total_affected = result.rowcount + batch_updated = max(0, total_affected - batch_size) + batch_inserted = batch_size - batch_updated + + # 累加到总结果 + results['processed'] += batch_size + results['inserted'] += batch_inserted + results['updated'] += batch_updated + + logger.info(f"批次 {batch_idx + 1} 完成: " + f"有效 {batch_size} 条, " + f"无效 {batch_invalid} 条, " + f"插入 {batch_inserted} 条, " + f"更新 {batch_updated} 条") + + session.commit() + + except Exception as e: + session.rollback() + error_msg = f"批次 {batch_idx + 1} 处理失败: {str(e)}" + logger.error(error_msg, exc_info=True) + results['errors'].append(error_msg) + # 继续处理下一个批次 + + # 最终统计 + success_rate = results['processed'] / results['total'] * 100 if results['total'] > 0 else 0 + + logger.info(f"订单同步完成: " + f"总数={results['total']}, " + f"处理={results['processed']}({success_rate:.1f}%), " + f"插入={results['inserted']}, " + f"更新={results['updated']}, " + f"无效={results['invalid_orders']}, " + f"错误={len(results['errors'])}") + + success = len(results['errors']) == 0 + return success, results + + except Exception as e: + logger.error(f"订单批量同步失败: {e}", exc_info=True) + results['errors'].append(f"同步过程失败: {str(e)}") + return False, results + + finally: + session.close() + def _convert_order_data(self, data: Dict) -> Dict: """转换订单数据格式""" try: # 安全转换函数 def safe_float(value): - if value is None: + if value is None or value == '': return None try: return float(value) @@ -233,7 +318,7 @@ class OrderSyncBatch(BaseSync): return None def safe_int(value): - if value is None: + if value is None or value == '': return None try: return int(float(value)) @@ -246,9 +331,9 @@ class OrderSyncBatch(BaseSync): return str(value) return { - 'st_id': safe_int(data.get('st_id'), 0), - 'k_id': safe_int(data.get('k_id'), 0), - 'asset': 'USDT', + 'st_id': safe_int(data.get('st_id')) or 0, + 'k_id': safe_int(data.get('k_id')) or 0, + 'asset': safe_str(data.get('asset')) or 'USDT', 'order_id': safe_str(data.get('order_id')), 'symbol': safe_str(data.get('symbol')), 'side': safe_str(data.get('side')), @@ -263,8 +348,4 @@ class OrderSyncBatch(BaseSync): except Exception as e: logger.error(f"转换订单数据异常: {data}, error={e}") return {} - - async def sync(self): - """兼容旧接口""" - accounts = self.get_accounts_from_redis() - await self.sync_batch(accounts) \ No newline at end of file + \ No newline at end of file diff --git a/utils/__pycache__/redis_client.cpython-311.pyc b/utils/__pycache__/redis_client.cpython-311.pyc index e95d5ff6c9c814a77692b6eee0611309a032fc02..1cff97424c1790025c35c1499fd0cb41ea2a5956 100644 GIT binary patch delta 8176 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 PyWFy%%kXcK4kq^rBNI>X%KbVw$)%s6`yl@#>f^ zt`F+thM*yC3>p~^#Y}N?(9HUpSV7zpw8RU8g>h@p8n*>)@uFZ++#a;Eac#^IcLW`A zXV8f_6?YHE>yC50epbbCFVLUTf-WBI;sSJ z%OB^CpkM`^Se4za=|ja%I(x#gQ{bawS1c+d(oLaILKqK)Y}r3)?FhNZySfzel6HMH zauZ%ZjC|xB{WK~igNBk7Ra9C;6#UprQPxG6jtTp%3Kd>CdDgHJ`LcgASX8b6os1g* zmIJH+Xe3{n9<`LvuB>I^xRe|>2VB*_L4pW7^(*V(I&#rmhibE*nt!3zmBWN`@~W?# z%vx7^Rs*Sy2?^Xvdw30nfDTua&#awAYbhb>DEL{!Ub5NNf!1e_*?y#=o3uK*(H=73 z2+&O)b!=_`Rd^dHP+?|noezY%vky9ts!=zY_Z%qM zKsjS(3jR;dK6~S->5m>i=f@j?=^)4KE^^dcg6hdHz2QnQRxz6`-9gEKg)NP{VW^Xo z`dZNzveUN#ttC(R+-7h)Zlf^KKpxrdC2#rcXfP}LKGdQK@><18w3Gb1Vh7qt+Wd8N z5#E2-rkko|Jr+{2a74hBpk;VL72X8B%>a29niVrT$u<9aGc#rjOjt`EY;o$C742kI zWj*>~c3H!scoSdxPSOYGU)gwY$);$!CjR_)FgY5kj0vbiHNWQLJsp#2D z8tbaj@3QyR9YQwd%^{c_1~`~~yMB$T><~z5G4N&0;ZT&vCt&m^0ECn_bR&U;8%lQU zqKtqM?*aY;6lDD&A(j%bC>zA^un>wS(s&96_W>}YZiXPBS1cQXA!Tv$PD8zi<&d5- zl^n`tWRMt_m!s?1Wy>Ro254%<7rh`DP~+Vo&lZUTgQg-dz|nq3W}Aft&zMe1(h zNLonnN*Hf$FOZAn7E;mDfQCqKOQmiAQAbBHEnJ~ft_W>ieOMHCDe1W6Ny<1)mD(_wrL7>0$#F-s03<>iuN;lO%{ zvL=DO8X8SVi1*5k?2$`+2rLg7dx zIhIH(Rz3%lnQMojHw&=TtCT02IWpc-NKeA8o3@~y>=T=|sw?syfLQmBLACqW`R8t& zBsYHWh<}WH*-?jHAf=st^NXMsGF>huQ`<{dC?RJi!=t{`7xYKjjlDMCQ zkvn4XHN~4p)I2) za~s^`j|Z%JLzK%HB`xU)xyd<~%`%PluNkEK%$PD}Be9x1Zpl20Xq}}$rDY1Ff*B)e zX>=0jF1s#cSs-h8J+E6_lQV^q1#6^2-tZ8@XsSPLB<&vO0m;G}mrS%u2FW@>T{{YO zf;CBhSOXm@h$V|;q$*6j@-g!Tr!h}3&%y&5fzgxrIHRsauU0jEa~_z z(W!5$xpW~ItF%$7R%z+dg&xNZt7T?o9Z~P)#?c6uv8SsS1UXJJ&e&OGZH7Y9yw$38NZgEra$J*I$+0A4 zPRY^D?LSWwV3BV!F3H6g<$WXiC~eQvyej8Y3*|d*!$(ug>4-Hyl5tC}s#{fLJc~s$ zUdhQ)(zV>eo0gdAliZRAGE4F@^Z{myl~k;`T`H#Gc9v_Bb;fn8M@mX0m4v8DujJuP zQn6IRdrlcLrILNd4Ss4YUHr5lZt7LZzKf$_ZCcRDaWs5&5I*wqL6^}hAthX-W1rJG z!bMbjIVDiLa{r{KcVGpE91bAaylZcNSEy@8f6u-5$Yv}==$(}wau18LJ~k0bN8i07|O9_Bh?a)!_kS~ zfEkx44B*SqRV2vPZB=D2Q@TH3P--AMAz>aa< zlH&7o^n#R<2d>{ z`@)VJXtLMFJ>N#*jrtDVmSWY#Mns8=P1UsjW)bw?w0G($amfe7rDAuFOM9ugxW}fw zY|>HAWt*0AF1xzcQ{sxvw2f0=DK6>VpuWNj?=Qx`D zGv(k504)2Mmm%}1@T)q z6*Y(7BgTPx^gd}B_?#TmxX5($#UA!nW}@j4NhT6PB&}SPSA(7^&;!*m_X?ip0XA z7!l~@DHK51L%D2Y?d;taLxEuc6{mHTruFj%Oe&dIu1a%2pTDXxD9#^8E@wE#Mp zU>QqCV`5Y84F~@Wc)Ik%sc2$^IgYqeHOts8dO?P*=cT=G3>)J~ek>+z!t<1bSc;#M n;Z#$T?<%+Gd!5^H_0C^1mm1t0( List[Dict]: + """收集所有账号的订单数据""" + all_orders = [] + + try: + # 按交易所分组账号 + account_groups = self._group_accounts_by_exchange(accounts) + + # 并发收集每个交易所的数据 + tasks = [] + for exchange_id, account_list in account_groups.items(): + task = self._collect_exchange_orders(exchange_id, account_list) + tasks.append(task) + + # 等待所有任务完成并合并结果 + results = await asyncio.gather(*tasks, return_exceptions=True) + + for result in results: + if isinstance(result, list): + all_orders.extend(result) + + except Exception as e: + logger.error(f"收集订单数据失败: {e}") + + return all_orders + async def _collect_exchange_orders(self, exchange_id: str, account_list: List[Dict]) -> List[Dict]: + """收集某个交易所的订单数据""" + orders_list = [] + + try: + # 并发获取每个账号的数据 + tasks = [] + for account_info in account_list: + k_id = int(account_info['k_id']) + st_id = account_info.get('st_id', 0) + task = self._get_recent_orders_from_redis(k_id, st_id, exchange_id) + tasks.append(task) + + results = await asyncio.gather(*tasks, return_exceptions=True) + + for result in results: + if isinstance(result, list): + orders_list.extend(result) + + logger.debug(f"交易所 {exchange_id}: 收集到 {len(orders_list)} 条订单") + + except Exception as e: + logger.error(f"收集交易所 {exchange_id} 订单数据失败: {e}") + + return orders_list + + async def _get_recent_orders_from_redis(self, k_id: int, st_id: int, exchange_id: str) -> List[Dict]: + """从Redis获取最近N天的订单数据""" + try: + redis_key = f"{exchange_id}:orders:{k_id}" + recent_days = SYNC_CONFIG['recent_days'] + # 计算最近N天的日期 + today = datetime.now() + recent_dates = [] + for i in range(recent_days): + date = today - timedelta(days=i) + date_format = date.strftime('%Y-%m-%d') + recent_dates.append(date_format) + + # 使用scan获取所有符合条件的key + cursor = 0 + recent_keys = [] + + while True: + cursor, keys = self.client.hscan(redis_key, cursor, count=1000) + + for key, _ in keys.items(): + key_str = key.decode('utf-8') if isinstance(key, bytes) else key + + if key_str == 'positions': + continue + + # 检查是否以最近N天的日期开头 + for date_format in recent_dates: + if key_str.startswith(date_format + '_'): + recent_keys.append(key_str) + break + + if cursor == 0: + break + + if not recent_keys: + return [] + + # 批量获取订单数据 + orders_list = [] + + # 分批获取,避免单次hgetall数据量太大 + chunk_size = 500 + for i in range(0, len(recent_keys), chunk_size): + chunk_keys = recent_keys[i:i + chunk_size] + + # 使用hmget批量获取 + chunk_values = self.client.hmget(redis_key, chunk_keys) + + for key, order_json in zip(chunk_keys, chunk_values): + if not order_json: + continue + + try: + order = json.loads(order_json) + + # 验证时间 + order_time = order.get('time', 0) + if order_time >= int(time.time()) - recent_days * 24 * 3600: + # 添加账号信息 + order['k_id'] = k_id + order['st_id'] = st_id + order['exchange_id'] = exchange_id + orders_list.append(order) + + except json.JSONDecodeError as e: + logger.debug(f"解析订单JSON失败: key={key}, error={e}") + continue + + return orders_list + + except Exception as e: + logger.error(f"获取Redis订单数据失败: k_id={k_id}, error={e}") + return [] + def close(self): """关闭连接池""" diff --git a/数据库.sql b/数据库.sql new file mode 100644 index 0000000..feb8147 --- /dev/null +++ b/数据库.sql @@ -0,0 +1,204 @@ +/* + Navicat Premium Data Transfer + + Source Server : lz_mysql_host + Source Server Type : MySQL + Source Server Version : 50740 + Source Host : localhost:3306 + Source Schema : lz_app_test + + Target Server Type : MySQL + Target Server Version : 50740 + File Encoding : 65001 + + Date: 04/12/2025 21:57:38 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for deh_strategy_asset_new +-- ---------------------------- +DROP TABLE IF EXISTS `deh_strategy_asset_new`; +CREATE TABLE `deh_strategy_asset_new` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id', + `st_id` int(11) NOT NULL COMMENT '策略id', + `asset` varchar(32) NOT NULL COMMENT '资产名称', + `win_rate` decimal(11,8) NOT NULL DEFAULT '0.00000000' COMMENT '胜率', + `aror` decimal(12,8) NOT NULL DEFAULT '0.00000000' COMMENT '年化收益', + `networth` decimal(12,8) NOT NULL DEFAULT '1.00000000' COMMENT '净值', + `max_down` decimal(12,8) NOT NULL DEFAULT '0.00000000' COMMENT '最大回撤', + `profit` decimal(20,8) NOT NULL DEFAULT '0.00000000' COMMENT '总收益', + `profit_rate` decimal(12,8) NOT NULL DEFAULT '0.00000000' COMMENT '总收益率', + `current_num` decimal(20,8) NOT NULL DEFAULT '0.00000000' COMMENT '资产数量', + `usd_num` decimal(20,8) NOT NULL DEFAULT '0.00000000' COMMENT '价值美元数', + `deposit` decimal(20,8) NOT NULL DEFAULT '0.00000000' COMMENT '总充值', + `withdrawal` decimal(20,8) NOT NULL DEFAULT '0.00000000' COMMENT '总提现', + `other` decimal(20,8) NOT NULL DEFAULT '0.00000000' COMMENT '其他总变动', + `time` int(11) NOT NULL COMMENT 'unix时间戳', + PRIMARY KEY (`id`) USING BTREE, + KEY `st_id` (`st_id`) USING BTREE, + KEY `asset` (`asset`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='策略资产表'; + +-- ---------------------------- +-- Table structure for deh_strategy_kx_new +-- ---------------------------- +DROP TABLE IF EXISTS `deh_strategy_kx_new`; +CREATE TABLE `deh_strategy_kx_new` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id', + `st_id` int(11) DEFAULT NULL COMMENT '策略id', + `k_id` int(11) DEFAULT '0' COMMENT '对应strategy_key 的ID', + `asset` varchar(32) DEFAULT NULL COMMENT '资产名', + `balance` decimal(20,8) DEFAULT '0.00000000' COMMENT '当日账户金额', + `withdrawal` decimal(20,8) DEFAULT '0.00000000' COMMENT '当日提现', + `deposit` decimal(20,8) DEFAULT '0.00000000' COMMENT '当日充值', + `other` decimal(20,8) DEFAULT '0.00000000' COMMENT '当日其他', + `profit` decimal(20,8) DEFAULT '0.00000000' COMMENT '当日利润', + `time` int(11) DEFAULT NULL COMMENT '时间', + `up_time` datetime DEFAULT NULL COMMENT '最后更新时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `kid_asset_time` (`k_id`,`asset`,`time`) USING BTREE, + KEY `st_id_asset_time` (`st_id`,`asset`,`time`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=259 DEFAULT CHARSET=utf8; + +-- ---------------------------- +-- Table structure for deh_strategy_networth +-- ---------------------------- +DROP TABLE IF EXISTS `deh_strategy_networth`; +CREATE TABLE `deh_strategy_networth` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `st_id` int(11) DEFAULT '0' COMMENT '策略ID对应deh_strategy表的ID', + `asset` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '资产名', + `share` decimal(20,8) DEFAULT '0.00000000' COMMENT '份额-净值算法', + `net_worth` decimal(13,8) DEFAULT '0.00000000' COMMENT '净值-净值算法', + `total_profit` decimal(20,8) DEFAULT NULL COMMENT '累计利润', + `time` int(10) DEFAULT '0' COMMENT '日期', + `up_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + PRIMARY KEY (`id`), + KEY `net_worth_index` (`st_id`,`asset`,`time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for deh_strategy_order_new +-- ---------------------------- +DROP TABLE IF EXISTS `deh_strategy_order_new`; +CREATE TABLE `deh_strategy_order_new` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `st_id` int(11) DEFAULT NULL COMMENT '策略id', + `k_id` int(11) DEFAULT '0' COMMENT '对应strategy_key 的ID', + `asset` varchar(32) DEFAULT NULL COMMENT '资产名称', + `order_id` varchar(765) DEFAULT NULL COMMENT '订单id', + `symbol` varchar(120) DEFAULT NULL COMMENT '交易对', + `side` varchar(120) DEFAULT NULL COMMENT '订单方向', + `price` float DEFAULT NULL COMMENT '订单价格', + `time` int(11) DEFAULT NULL COMMENT '订单时间', + `order_qty` float DEFAULT NULL COMMENT '订单挂单数量', + `last_qty` float DEFAULT NULL COMMENT '订单成交数量', + `avg_price` float DEFAULT NULL COMMENT '订单成交均价', + `exchange_id` int(11) DEFAULT NULL COMMENT '交易所id', + PRIMARY KEY (`id`), + UNIQUE KEY `order_index` (`order_id`,`symbol`,`k_id`,`side`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1269 DEFAULT CHARSET=utf8 COMMENT='订单表'; + +-- ---------------------------- +-- Table structure for deh_strategy_position_new +-- ---------------------------- +DROP TABLE IF EXISTS `deh_strategy_position_new`; +CREATE TABLE `deh_strategy_position_new` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id', + `st_id` int(11) NOT NULL COMMENT '策略id', + `k_id` int(11) DEFAULT '0' COMMENT '对应strategy_key 的ID', + `asset` varchar(32) DEFAULT '' COMMENT '使用资产名称,如BTC或USDT', + `symbol` varchar(50) NOT NULL COMMENT '交易对', + `price` float DEFAULT NULL COMMENT '持仓均价', + `side` varchar(10) NOT NULL COMMENT '方向', + `sum` float NOT NULL COMMENT '仓位(张数)', + `asset_num` decimal(20,8) DEFAULT '0.00000000' COMMENT '资产数量', + `asset_profit` decimal(20,8) DEFAULT NULL COMMENT '利润数量', + `leverage` int(11) DEFAULT '0' COMMENT '杠杆倍数', + `uptime` int(11) NOT NULL COMMENT '更新时间', + `profit_price` decimal(20,8) DEFAULT '0.00000000' COMMENT '止盈价格', + `stop_price` decimal(20,8) DEFAULT '0.00000000' COMMENT '止损价格', + `liquidation_price` decimal(20,8) DEFAULT '0.00000000' COMMENT '强平价格', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `st_id_asset_sum` (`k_id`,`st_id`,`symbol`,`side`) USING BTREE, + KEY `k_id` (`k_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='持仓表'; + +-- ---------------------------- +-- Event structure for auto_del +-- ---------------------------- +DROP EVENT IF EXISTS `auto_del`; +delimiter ;; +CREATE EVENT `auto_del` +ON SCHEDULE +EVERY '1' MINUTE STARTS '2021-11-01 00:00:00' +DO BEGIN + + +DELETE FROM deh_strategy_kx_new WHERE CONCAT(k_id,asset,time) IN ( +SELECT p_key FROM ( +SELECT CONCAT(k_id,asset,time) AS p_key FROM deh_strategy_kx_new GROUP BY CONCAT(k_id,asset,time) HAVING count(CONCAT(k_id,asset,time)) > 1) +AS tmp +) AND id NOT IN ( +SELECT id FROM ( +SELECT min(id) AS id FROM deh_strategy_kx_new GROUP BY CONCAT(k_id,asset,time) HAVING count(CONCAT(k_id,asset,time)) > 1) +AS tmp1 +); + + +DELETE FROM deh_strategy_networth WHERE CONCAT(st_id,asset,time) IN ( +SELECT p_key FROM ( +SELECT CONCAT(st_id,asset,time) AS p_key FROM deh_strategy_networth GROUP BY CONCAT(st_id,asset,time) HAVING count(CONCAT(st_id,asset,time)) > 1) +AS tmp +) AND id NOT IN ( +SELECT id FROM ( +SELECT min(id) AS id FROM deh_strategy_networth GROUP BY CONCAT(st_id,asset,time) HAVING count(CONCAT(st_id,asset,time)) > 1) +AS tmp1 +); + + + +DELETE FROM deh_strategy_asset_new WHERE CONCAT(st_id,asset) IN ( +SELECT p_key FROM ( +SELECT CONCAT(st_id,asset) AS p_key FROM deh_strategy_asset_new GROUP BY CONCAT(st_id,asset) HAVING count(CONCAT(st_id,asset)) > 1) +AS tmp +) AND id NOT IN ( +SELECT id FROM ( +SELECT min(id) AS id FROM deh_strategy_asset_new GROUP BY CONCAT(st_id,asset) HAVING count(CONCAT(st_id,asset)) > 1) +AS tmp1 +); + + +END +;; +delimiter ; + +-- ---------------------------- +-- Event structure for auto_del_order +-- ---------------------------- +DROP EVENT IF EXISTS `auto_del_order`; +delimiter ;; +CREATE EVENT `auto_del_order` +ON SCHEDULE +EVERY '1' HOUR STARTS '2021-11-01 00:00:00' +DO BEGIN + -- DELETE FROM `deh_strategy_order_new` WHERE `time` < UNIX_TIMESTAMP(NOW() - INTERVAL 3 DAY); + DELETE FROM `deh_strategy_order_new` WHERE `time` < UNIX_TIMESTAMP(NOW() - INTERVAL 3 DAY) AND st_id IN (SELECT st_id FROM `deh_strategy` WHERE is_ia=1); + DELETE FROM `deh_strategy_order_new` WHERE `time` < UNIX_TIMESTAMP(NOW() - INTERVAL 30 DAY) AND st_id IN (SELECT st_id FROM `deh_strategy` WHERE is_ia=0); + DELETE from `deh_strategy_order_new` where order_id in ( + select order_id from ( + SELECT order_id FROM `deh_strategy_order_new` GROUP BY order_id HAVING count(order_id) > 1) + as tmp + ) and id not in ( + select id from ( + SELECT min(id) as id FROM `deh_strategy_order_new` GROUP BY order_id HAVING count(order_id) > 1) + as tmp1 + ); +END +;; +delimiter ; + +SET FOREIGN_KEY_CHECKS = 1;