哈哈
This commit is contained in:
228
100u_trades.csv
Normal file
228
100u_trades.csv
Normal file
@@ -0,0 +1,228 @@
|
||||
开仓时间,平仓时间,方向,开仓价,平仓价,名义价值,方向盈亏,手续费,返佣,净盈亏,持仓秒,原因
|
||||
2025-01-09 01:47:00,2025-01-09 01:51:00,short,3236.07,3243.77,10000,-23.79,12.00,10.80,-24.99,240,延迟金叉
|
||||
2025-01-19 22:37:00,2025-01-19 23:07:00,long,3345.20,3401.72,10000,168.96,12.00,10.80,167.76,1800,超时(1800s)
|
||||
2025-01-19 23:07:00,2025-01-19 23:19:00,long,3401.72,3387.81,10000,-40.89,12.00,10.80,-42.09,720,止损(-0.41%)
|
||||
2025-01-20 06:36:00,2025-01-20 06:56:00,short,3250.88,3252.83,10000,-6.00,12.00,10.80,-7.20,1200,金叉反转
|
||||
2025-01-20 07:02:00,2025-01-20 07:09:00,short,3220.24,3241.10,10000,-64.78,12.00,10.80,-65.98,420,硬止损(-0.65%)
|
||||
2025-01-20 07:57:00,2025-01-20 08:01:00,short,3205.90,3197.72,10000,25.52,12.00,10.80,24.32,240,延迟金叉
|
||||
2025-01-20 08:01:00,2025-01-20 08:05:00,short,3197.72,3218.22,10000,-64.11,12.00,10.80,-65.31,240,硬止损(-0.64%)
|
||||
2025-01-20 08:10:00,2025-01-20 08:14:00,short,3199.41,3212.78,10000,-41.79,12.00,10.80,-42.99,240,止损(-0.42%)
|
||||
2025-01-20 08:14:00,2025-01-20 08:18:00,short,3212.78,3196.19,10000,51.64,12.00,10.80,50.44,240,延迟金叉
|
||||
2025-01-20 08:18:00,2025-01-20 08:25:00,short,3196.19,3210.09,10000,-43.49,12.00,10.80,-44.69,420,止损(-0.43%)
|
||||
2025-01-20 08:25:00,2025-01-20 08:55:00,short,3210.09,3168.49,10000,129.59,12.00,10.80,128.39,1800,超时(1800s)
|
||||
2025-01-20 08:55:00,2025-01-20 09:02:00,short,3168.49,3188.72,10000,-63.85,12.00,10.80,-65.05,420,硬止损(-0.64%)
|
||||
2025-01-20 09:21:00,2025-01-20 09:25:00,short,3180.13,3186.13,10000,-18.87,12.00,10.80,-20.07,240,延迟金叉
|
||||
2025-01-20 09:25:00,2025-01-20 09:29:00,short,3186.13,3194.15,10000,-25.17,12.00,10.80,-26.37,240,延迟金叉
|
||||
2025-01-20 23:25:00,2025-01-20 23:35:00,long,3363.35,3354.76,10000,-25.54,12.00,10.80,-26.74,600,死叉反转
|
||||
2025-01-21 00:22:00,2025-01-21 00:52:00,long,3343.01,3375.00,10000,95.69,12.00,10.80,94.49,1800,超时(1800s)
|
||||
2025-01-21 01:27:00,2025-01-21 01:53:00,short,3333.15,3310.20,10000,68.85,12.00,10.80,67.65,1560,金叉反转
|
||||
2025-01-21 02:17:00,2025-01-21 02:21:00,short,3286.27,3303.29,10000,-51.79,12.00,10.80,-52.99,240,止损(-0.52%)
|
||||
2025-02-03 07:01:00,2025-02-03 07:31:00,short,2837.73,2824.40,10000,46.97,12.00,10.80,45.77,1800,超时(1800s)
|
||||
2025-02-03 07:31:00,2025-02-03 07:36:00,short,2824.40,2831.96,10000,-26.77,12.00,10.80,-27.97,300,金叉反转
|
||||
2025-02-03 08:14:00,2025-02-03 08:44:00,short,2851.94,2778.69,10000,256.84,12.00,10.80,255.64,1800,超时(1800s)
|
||||
2025-02-03 08:44:00,2025-02-03 08:45:00,short,2778.69,2798.72,10000,-72.08,12.00,10.80,-73.28,60,硬止损(-0.72%)
|
||||
2025-02-03 09:09:00,2025-02-03 09:13:00,short,2820.39,2824.09,10000,-13.12,12.00,10.80,-14.32,240,延迟金叉
|
||||
2025-02-03 09:13:00,2025-02-03 09:43:00,short,2824.09,2750.20,10000,261.64,12.00,10.80,260.44,1800,超时(1800s)
|
||||
2025-02-03 09:43:00,2025-02-03 10:13:00,short,2750.20,2435.30,10000,1145.01,12.00,10.80,1143.81,1800,超时(1800s)
|
||||
2025-02-03 10:13:00,2025-02-03 10:17:00,short,2435.30,2486.09,10000,-208.56,12.00,10.80,-209.76,240,硬止损(-2.09%)
|
||||
2025-02-03 10:32:00,2025-02-03 10:36:00,short,2454.28,2490.54,10000,-147.74,12.00,10.80,-148.94,240,硬止损(-1.48%)
|
||||
2025-02-03 10:53:00,2025-02-03 10:58:00,short,2469.32,2479.43,10000,-40.94,12.00,10.80,-42.14,300,止损(-0.41%)
|
||||
2025-02-03 10:58:00,2025-02-03 11:00:00,short,2479.43,2496.72,10000,-69.73,12.00,10.80,-70.93,120,硬止损(-0.70%)
|
||||
2025-02-03 11:34:00,2025-02-03 11:45:00,short,2510.35,2523.31,10000,-51.63,12.00,10.80,-52.83,660,止损(-0.52%)
|
||||
2025-02-03 12:10:00,2025-02-03 12:40:00,short,2517.23,2480.73,10000,145.00,12.00,10.80,143.80,1800,超时(1800s)
|
||||
2025-02-03 12:40:00,2025-02-03 13:10:00,short,2480.73,2467.64,10000,52.77,12.00,10.80,51.57,1800,超时(1800s)
|
||||
2025-02-03 13:10:00,2025-02-03 13:16:00,short,2467.64,2477.99,10000,-41.94,12.00,10.80,-43.14,360,止损(-0.42%)
|
||||
2025-02-03 13:39:00,2025-02-03 13:48:00,short,2472.19,2482.08,10000,-40.01,12.00,10.80,-41.21,540,止损(-0.40%)
|
||||
2025-02-03 14:05:00,2025-02-03 14:09:00,short,2484.26,2495.61,10000,-45.69,12.00,10.80,-46.89,240,止损(-0.46%)
|
||||
2025-02-03 17:28:00,2025-02-03 17:46:00,long,2584.86,2578.96,10000,-22.83,12.00,10.80,-24.03,1080,死叉反转
|
||||
2025-02-03 22:19:00,2025-02-03 22:30:00,short,2550.76,2557.25,10000,-25.44,12.00,10.80,-26.64,660,金叉反转
|
||||
2025-02-04 00:19:00,2025-02-04 00:30:00,long,2704.94,2688.64,10000,-60.26,12.00,10.80,-61.46,660,硬止损(-0.60%)
|
||||
2025-02-04 13:59:00,2025-02-04 14:02:00,short,2689.52,2706.41,10000,-62.80,12.00,10.80,-64.00,180,硬止损(-0.63%)
|
||||
2025-02-07 21:38:00,2025-02-07 22:08:00,long,2750.61,2786.11,10000,129.06,12.00,10.80,127.86,1800,超时(1800s)
|
||||
2025-02-12 21:31:00,2025-02-12 21:47:00,short,2595.80,2608.47,10000,-48.81,12.00,10.80,-50.01,960,止损(-0.49%)
|
||||
2025-02-12 21:47:00,2025-02-12 22:17:00,short,2608.47,2587.47,10000,80.51,12.00,10.80,79.31,1800,超时(1800s)
|
||||
2025-02-25 07:15:00,2025-02-25 07:19:00,short,2529.28,2540.47,10000,-44.24,12.00,10.80,-45.44,240,止损(-0.44%)
|
||||
2025-02-25 07:49:00,2025-02-25 08:01:00,short,2512.82,2523.61,10000,-42.94,12.00,10.80,-44.14,720,止损(-0.43%)
|
||||
2025-02-25 08:33:00,2025-02-25 08:50:00,short,2496.24,2510.10,10000,-55.52,12.00,10.80,-56.72,1020,止损(-0.56%)
|
||||
2025-02-25 09:18:00,2025-02-25 09:22:00,short,2484.42,2498.00,10000,-54.66,12.00,10.80,-55.86,240,止损(-0.55%)
|
||||
2025-02-25 15:59:00,2025-02-25 16:04:00,short,2367.49,2386.70,10000,-81.14,12.00,10.80,-82.34,300,硬止损(-0.81%)
|
||||
2025-02-25 18:04:00,2025-02-25 18:08:00,long,2403.72,2393.77,10000,-41.39,12.00,10.80,-42.59,240,止损(-0.41%)
|
||||
2025-02-25 18:11:00,2025-02-25 18:30:00,short,2376.73,2389.96,10000,-55.66,12.00,10.80,-56.86,1140,止损(-0.56%)
|
||||
2025-02-25 23:29:00,2025-02-25 23:35:00,short,2373.44,2379.46,10000,-25.36,12.00,10.80,-26.56,360,金叉反转
|
||||
2025-02-27 04:13:00,2025-02-27 04:26:00,short,2290.54,2303.58,10000,-56.93,12.00,10.80,-58.13,780,止损(-0.57%)
|
||||
2025-02-27 04:26:00,2025-02-27 04:30:00,short,2303.58,2309.19,10000,-24.35,12.00,10.80,-25.55,240,延迟金叉
|
||||
2025-02-27 04:30:00,2025-02-27 05:00:00,long,2309.19,2326.81,10000,76.30,12.00,10.80,75.10,1800,超时(1800s)
|
||||
2025-02-28 10:16:00,2025-02-28 10:46:00,short,2203.78,2134.72,10000,313.37,12.00,10.80,312.17,1800,超时(1800s)
|
||||
2025-02-28 10:46:00,2025-02-28 10:52:00,short,2134.72,2144.20,10000,-44.41,12.00,10.80,-45.61,360,止损(-0.44%)
|
||||
2025-02-28 10:52:00,2025-02-28 10:55:00,short,2144.20,2160.90,10000,-77.88,12.00,10.80,-79.08,180,硬止损(-0.78%)
|
||||
2025-02-28 13:24:00,2025-02-28 13:43:00,short,2125.15,2121.70,10000,16.23,12.00,10.80,15.03,1140,金叉反转
|
||||
2025-02-28 13:46:00,2025-02-28 13:50:00,short,2115.35,2114.29,10000,5.01,12.00,10.80,3.81,240,延迟金叉
|
||||
2025-02-28 13:59:00,2025-02-28 14:09:00,long,2130.87,2119.27,10000,-54.44,12.00,10.80,-55.64,600,止损(-0.54%)
|
||||
2025-02-28 14:11:00,2025-02-28 14:31:00,short,2112.73,2120.01,10000,-34.46,12.00,10.80,-35.66,1200,金叉反转
|
||||
2025-02-28 16:45:00,2025-02-28 16:49:00,short,2094.69,2104.60,10000,-47.31,12.00,10.80,-48.51,240,止损(-0.47%)
|
||||
2025-02-28 21:30:00,2025-02-28 22:00:00,long,2146.41,2158.14,10000,54.65,12.00,10.80,53.45,1800,超时(1800s)
|
||||
2025-02-28 22:42:00,2025-02-28 22:57:00,long,2170.87,2161.06,10000,-45.19,12.00,10.80,-46.39,900,止损(-0.45%)
|
||||
2025-03-03 00:13:00,2025-03-03 00:43:00,long,2240.01,2490.06,10000,1116.29,12.00,10.80,1115.09,1800,超时(1800s)
|
||||
2025-03-03 00:43:00,2025-03-03 00:46:00,long,2490.06,2440.04,10000,-200.88,12.00,10.80,-202.08,180,硬止损(-2.01%)
|
||||
2025-03-03 01:06:00,2025-03-03 01:36:00,long,2437.84,2476.92,10000,160.31,12.00,10.80,159.11,1800,超时(1800s)
|
||||
2025-03-03 01:36:00,2025-03-03 01:48:00,long,2476.92,2459.55,10000,-70.13,12.00,10.80,-71.33,720,硬止损(-0.70%)
|
||||
2025-03-03 01:59:00,2025-03-03 02:16:00,long,2479.82,2461.26,10000,-74.84,12.00,10.80,-76.04,1020,硬止损(-0.75%)
|
||||
2025-03-03 02:21:00,2025-03-03 02:33:00,long,2492.94,2480.59,10000,-49.54,12.00,10.80,-50.74,720,止损(-0.50%)
|
||||
2025-03-03 02:33:00,2025-03-03 02:36:00,long,2480.59,2459.17,10000,-86.35,12.00,10.80,-87.55,180,硬止损(-0.86%)
|
||||
2025-03-03 23:33:00,2025-03-03 23:42:00,short,2288.17,2297.07,10000,-38.90,12.00,10.80,-40.10,540,金叉反转
|
||||
2025-03-04 22:35:00,2025-03-04 22:55:00,long,2088.01,2100.66,10000,60.58,12.00,10.80,59.38,1200,死叉反转
|
||||
2025-03-07 08:15:00,2025-03-07 08:45:00,short,2177.44,2114.20,10000,290.43,12.00,10.80,289.23,1800,超时(1800s)
|
||||
2025-03-07 08:45:00,2025-03-07 09:05:00,short,2114.20,2123.71,10000,-44.98,12.00,10.80,-46.18,1200,止损(-0.45%)
|
||||
2025-03-07 09:05:00,2025-03-07 09:09:00,short,2123.71,2135.77,10000,-56.79,12.00,10.80,-57.99,240,止损(-0.57%)
|
||||
2025-03-07 23:49:00,2025-03-07 23:53:00,long,2219.36,2206.59,10000,-57.54,12.00,10.80,-58.74,240,止损(-0.58%)
|
||||
2025-03-07 23:53:00,2025-03-08 00:05:00,long,2206.59,2202.09,10000,-20.39,12.00,10.80,-21.59,720,死叉反转
|
||||
2025-03-08 02:05:00,2025-03-08 02:23:00,long,2176.51,2164.97,10000,-53.02,12.00,10.80,-54.22,1080,止损(-0.53%)
|
||||
2025-03-08 04:57:00,2025-03-08 05:24:00,short,2158.81,2153.19,10000,26.03,12.00,10.80,24.83,1620,金叉反转
|
||||
2025-03-10 02:38:00,2025-03-10 02:56:00,short,2013.16,2022.45,10000,-46.15,12.00,10.80,-47.35,1080,止损(-0.46%)
|
||||
2025-03-11 02:07:00,2025-03-11 02:11:00,short,1916.91,1931.48,10000,-76.01,12.00,10.80,-77.21,240,硬止损(-0.76%)
|
||||
2025-03-11 02:19:00,2025-03-11 02:49:00,short,1918.01,1890.51,10000,143.38,12.00,10.80,142.18,1800,超时(1800s)
|
||||
2025-03-11 02:49:00,2025-03-11 03:16:00,short,1890.51,1863.15,10000,144.72,12.00,10.80,143.52,1620,金叉反转
|
||||
2025-03-11 03:47:00,2025-03-11 03:51:00,short,1862.16,1869.04,10000,-36.95,12.00,10.80,-38.15,240,金叉反转
|
||||
2025-03-11 08:07:00,2025-03-11 08:12:00,long,1886.18,1876.76,10000,-49.94,12.00,10.80,-51.14,300,止损(-0.50%)
|
||||
2025-03-11 21:27:00,2025-03-11 21:28:00,long,1959.11,1926.70,10000,-165.43,12.00,10.80,-166.63,60,硬止损(-1.65%)
|
||||
2025-03-12 16:16:00,2025-03-12 16:17:00,short,1882.36,1897.86,10000,-82.34,12.00,10.80,-83.54,60,硬止损(-0.82%)
|
||||
2025-03-12 16:29:00,2025-03-12 16:59:00,long,1913.89,1927.41,10000,70.64,12.00,10.80,69.44,1800,超时(1800s)
|
||||
2025-03-12 17:10:00,2025-03-12 17:40:00,short,1907.77,1896.72,10000,57.92,12.00,10.80,56.72,1800,超时(1800s)
|
||||
2025-03-13 20:37:00,2025-03-13 21:00:00,short,1893.99,1903.36,10000,-49.47,12.00,10.80,-50.67,1380,止损(-0.49%)
|
||||
2025-03-20 02:06:00,2025-03-20 02:09:00,short,2005.30,2018.91,10000,-67.87,12.00,10.80,-69.07,180,硬止损(-0.68%)
|
||||
2025-03-28 19:25:00,2025-03-28 19:27:00,short,1883.20,1895.03,10000,-62.82,12.00,10.80,-64.02,120,硬止损(-0.63%)
|
||||
2025-04-03 04:14:00,2025-04-03 04:15:00,long,1933.74,1919.83,10000,-71.93,12.00,10.80,-73.13,60,硬止损(-0.72%)
|
||||
2025-04-03 04:27:00,2025-04-03 04:57:00,short,1894.51,1880.55,10000,73.69,12.00,10.80,72.49,1800,超时(1800s)
|
||||
2025-04-04 20:38:00,2025-04-04 20:44:00,short,1769.94,1780.32,10000,-58.65,12.00,10.80,-59.85,360,止损(-0.59%)
|
||||
2025-04-05 00:14:00,2025-04-05 00:18:00,long,1799.52,1791.46,10000,-44.79,12.00,10.80,-45.99,240,止损(-0.45%)
|
||||
2025-04-05 00:18:00,2025-04-05 00:36:00,short,1791.46,1795.09,10000,-20.26,12.00,10.80,-21.46,1080,金叉反转
|
||||
2025-04-06 04:50:00,2025-04-06 04:54:00,short,1783.62,1788.30,10000,-26.24,12.00,10.80,-27.44,240,金叉反转
|
||||
2025-04-06 04:54:00,2025-04-06 05:04:00,long,1788.30,1784.39,10000,-21.86,12.00,10.80,-23.06,600,死叉反转
|
||||
2025-04-07 03:04:00,2025-04-07 03:08:00,short,1621.11,1618.01,10000,19.12,12.00,10.80,17.92,240,延迟金叉
|
||||
2025-04-07 03:08:00,2025-04-07 03:16:00,short,1618.01,1632.03,10000,-86.65,12.00,10.80,-87.85,480,硬止损(-0.87%)
|
||||
2025-04-07 03:24:00,2025-04-07 03:35:00,short,1616.01,1622.66,10000,-41.15,12.00,10.80,-42.35,660,止损(-0.41%)
|
||||
2025-04-07 05:08:00,2025-04-07 05:12:00,short,1581.70,1588.76,10000,-44.64,12.00,10.80,-45.84,240,止损(-0.45%)
|
||||
2025-04-07 06:21:00,2025-04-07 06:27:00,short,1589.29,1597.01,10000,-48.58,12.00,10.80,-49.78,360,止损(-0.49%)
|
||||
2025-04-07 08:28:00,2025-04-07 08:34:00,long,1578.89,1571.56,10000,-46.43,12.00,10.80,-47.63,360,止损(-0.46%)
|
||||
2025-04-07 15:30:00,2025-04-07 15:37:00,short,1446.78,1456.64,10000,-68.15,12.00,10.80,-69.35,420,硬止损(-0.68%)
|
||||
2025-04-07 15:53:00,2025-04-07 15:57:00,short,1454.59,1464.59,10000,-68.75,12.00,10.80,-69.95,240,硬止损(-0.69%)
|
||||
2025-04-07 17:27:00,2025-04-07 17:54:00,long,1491.67,1492.56,10000,5.97,12.00,10.80,4.77,1620,死叉反转
|
||||
2025-04-07 18:35:00,2025-04-07 18:44:00,short,1495.29,1501.60,10000,-42.20,12.00,10.80,-43.40,540,止损(-0.42%)
|
||||
2025-04-07 20:49:00,2025-04-07 21:12:00,long,1510.88,1513.01,10000,14.10,12.00,10.80,12.90,1380,死叉反转
|
||||
2025-04-07 21:48:00,2025-04-07 22:18:00,long,1512.10,1612.18,10000,661.86,12.00,10.80,660.66,1800,超时(1800s)
|
||||
2025-04-07 22:18:00,2025-04-07 22:20:00,long,1612.18,1589.01,10000,-143.72,12.00,10.80,-144.92,120,硬止损(-1.44%)
|
||||
2025-04-07 22:50:00,2025-04-07 23:05:00,long,1571.92,1559.76,10000,-77.36,12.00,10.80,-78.56,900,硬止损(-0.77%)
|
||||
2025-04-07 23:36:00,2025-04-07 23:54:00,long,1558.55,1554.41,10000,-26.56,12.00,10.80,-27.76,1080,死叉反转
|
||||
2025-04-08 00:30:00,2025-04-08 00:39:00,long,1552.32,1545.29,10000,-45.29,12.00,10.80,-46.49,540,止损(-0.45%)
|
||||
2025-04-08 01:01:00,2025-04-08 01:03:00,long,1550.02,1539.41,10000,-68.45,12.00,10.80,-69.65,120,硬止损(-0.68%)
|
||||
2025-04-09 02:40:00,2025-04-09 03:10:00,short,1480.29,1468.67,10000,78.50,12.00,10.80,77.30,1800,超时(1800s)
|
||||
2025-04-09 19:01:00,2025-04-09 19:31:00,short,1468.26,1455.54,10000,86.63,12.00,10.80,85.43,1800,超时(1800s)
|
||||
2025-04-09 22:23:00,2025-04-09 22:40:00,long,1488.82,1482.99,10000,-39.16,12.00,10.80,-40.36,1020,死叉反转
|
||||
2025-04-10 01:20:00,2025-04-10 01:50:00,long,1527.12,1596.62,10000,455.11,12.00,10.80,453.91,1800,超时(1800s)
|
||||
2025-04-10 01:50:00,2025-04-10 02:20:00,long,1596.62,1656.57,10000,375.48,12.00,10.80,374.28,1800,超时(1800s)
|
||||
2025-04-10 02:20:00,2025-04-10 02:34:00,long,1656.57,1648.01,10000,-51.67,12.00,10.80,-52.87,840,止损(-0.52%)
|
||||
2025-04-10 02:34:00,2025-04-10 02:42:00,long,1648.01,1643.59,10000,-26.82,12.00,10.80,-28.02,480,死叉反转
|
||||
2025-04-10 20:41:00,2025-04-10 21:11:00,short,1595.54,1589.17,10000,39.92,12.00,10.80,38.72,1800,超时(1800s)
|
||||
2025-04-11 00:16:00,2025-04-11 00:32:00,short,1495.35,1498.14,10000,-18.66,12.00,10.80,-19.86,960,金叉反转
|
||||
2025-04-11 01:07:00,2025-04-11 01:27:00,short,1496.46,1499.96,10000,-23.39,12.00,10.80,-24.59,1200,金叉反转
|
||||
2025-04-11 16:06:00,2025-04-11 16:18:00,long,1559.46,1552.32,10000,-45.79,12.00,10.80,-46.99,720,止损(-0.46%)
|
||||
2025-04-11 16:18:00,2025-04-11 16:22:00,long,1552.32,1552.60,10000,1.80,12.00,10.80,0.60,240,延迟死叉
|
||||
2025-04-15 21:55:00,2025-04-15 22:25:00,short,1631.26,1610.22,10000,128.98,12.00,10.80,127.78,1800,超时(1800s)
|
||||
2025-04-15 22:25:00,2025-04-15 22:41:00,short,1610.22,1619.31,10000,-56.45,12.00,10.80,-57.65,960,止损(-0.56%)
|
||||
2025-05-09 00:06:00,2025-05-09 00:26:00,long,2050.68,2047.34,10000,-16.29,12.00,10.80,-17.49,1200,死叉反转
|
||||
2025-05-09 19:28:00,2025-05-09 19:33:00,short,2330.98,2348.40,10000,-74.73,12.00,10.80,-75.93,300,硬止损(-0.75%)
|
||||
2025-05-11 01:48:00,2025-05-11 02:12:00,short,2466.18,2478.32,10000,-49.23,12.00,10.80,-50.43,1440,止损(-0.49%)
|
||||
2025-06-06 05:13:00,2025-06-06 05:18:00,short,2415.46,2420.42,10000,-20.53,12.00,10.80,-21.73,300,金叉反转
|
||||
2025-06-06 05:20:00,2025-06-06 05:24:00,short,2413.77,2424.43,10000,-44.16,12.00,10.80,-45.36,240,止损(-0.44%)
|
||||
2025-06-19 02:05:00,2025-06-19 02:25:00,short,2485.49,2496.81,10000,-45.54,12.00,10.80,-46.74,1200,止损(-0.46%)
|
||||
2025-06-22 07:49:00,2025-06-22 07:50:00,long,2291.17,2274.62,10000,-72.23,12.00,10.80,-73.43,60,硬止损(-0.72%)
|
||||
2025-06-22 08:12:00,2025-06-22 08:19:00,short,2275.01,2287.02,10000,-52.79,12.00,10.80,-53.99,420,止损(-0.53%)
|
||||
2025-06-22 08:20:00,2025-06-22 08:28:00,long,2284.86,2282.33,10000,-11.07,12.00,10.80,-12.27,480,死叉反转
|
||||
2025-06-24 00:54:00,2025-06-24 00:58:00,short,2214.42,2217.90,10000,-15.72,12.00,10.80,-16.92,240,金叉反转
|
||||
2025-08-14 21:15:00,2025-08-14 21:36:00,short,4553.08,4565.93,10000,-28.22,12.00,10.80,-29.42,1260,金叉反转
|
||||
2025-08-15 20:39:00,2025-08-15 20:45:00,short,4620.11,4641.67,10000,-46.67,12.00,10.80,-47.87,360,止损(-0.47%)
|
||||
2025-08-20 21:45:00,2025-08-20 22:15:00,short,4161.83,4119.44,10000,101.85,12.00,10.80,100.65,1800,超时(1800s)
|
||||
2025-08-26 20:57:00,2025-08-26 21:01:00,long,4499.56,4472.24,10000,-60.72,12.00,10.80,-61.92,240,硬止损(-0.61%)
|
||||
2025-09-11 20:30:00,2025-09-11 20:31:00,short,4390.02,4421.01,10000,-70.59,12.00,10.80,-71.79,60,硬止损(-0.71%)
|
||||
2025-10-11 06:23:00,2025-10-11 06:28:00,short,3841.99,3861.79,10000,-51.54,12.00,10.80,-52.74,300,止损(-0.52%)
|
||||
2025-10-11 06:28:00,2025-10-11 06:32:00,short,3861.79,3878.52,10000,-43.32,12.00,10.80,-44.52,240,止损(-0.43%)
|
||||
2025-10-11 06:35:00,2025-10-11 06:39:00,long,3883.63,3864.24,10000,-49.93,12.00,10.80,-51.13,240,止损(-0.50%)
|
||||
2025-10-11 06:39:00,2025-10-11 06:46:00,short,3864.24,3875.86,10000,-30.07,12.00,10.80,-31.27,420,金叉反转
|
||||
2025-10-11 06:46:00,2025-10-11 06:51:00,long,3875.86,3858.46,10000,-44.89,12.00,10.80,-46.09,300,止损(-0.45%)
|
||||
2025-10-11 06:52:00,2025-10-11 07:22:00,short,3859.71,3839.30,10000,52.88,12.00,10.80,51.68,1800,超时(1800s)
|
||||
2025-10-11 07:22:00,2025-10-11 07:26:00,short,3839.30,3849.77,10000,-27.27,12.00,10.80,-28.47,240,延迟金叉
|
||||
2025-10-11 07:49:00,2025-10-11 08:06:00,short,3842.57,3858.39,10000,-41.17,12.00,10.80,-42.37,1020,止损(-0.41%)
|
||||
2025-10-11 08:07:00,2025-10-11 08:16:00,long,3862.97,3831.58,10000,-81.26,12.00,10.80,-82.46,540,硬止损(-0.81%)
|
||||
2025-10-11 08:17:00,2025-10-11 08:26:00,short,3816.12,3832.28,10000,-42.35,12.00,10.80,-43.55,540,止损(-0.42%)
|
||||
2025-10-11 08:26:00,2025-10-11 08:40:00,short,3832.28,3839.98,10000,-20.09,12.00,10.80,-21.29,840,金叉反转
|
||||
2025-10-12 23:48:00,2025-10-13 00:18:00,long,4026.01,4066.43,10000,100.40,12.00,10.80,99.20,1800,超时(1800s)
|
||||
2025-10-13 00:18:00,2025-10-13 00:37:00,long,4066.43,4053.59,10000,-31.58,12.00,10.80,-32.78,1140,死叉反转
|
||||
2025-10-13 00:44:00,2025-10-13 00:50:00,long,4119.04,4101.94,10000,-41.51,12.00,10.80,-42.71,360,止损(-0.42%)
|
||||
2025-10-13 00:50:00,2025-10-13 01:20:00,long,4101.94,4131.75,10000,72.67,12.00,10.80,71.47,1800,超时(1800s)
|
||||
2025-10-14 21:37:00,2025-10-14 21:39:00,short,3899.44,3932.77,10000,-85.47,12.00,10.80,-86.67,120,硬止损(-0.85%)
|
||||
2025-10-14 21:45:00,2025-10-14 22:07:00,long,3949.74,3942.13,10000,-19.27,12.00,10.80,-20.47,1320,死叉反转
|
||||
2025-10-15 01:10:00,2025-10-15 01:33:00,long,4120.36,4112.24,10000,-19.71,12.00,10.80,-20.91,1380,死叉反转
|
||||
2025-10-15 21:43:00,2025-10-15 22:03:00,short,4074.45,4066.89,10000,18.55,12.00,10.80,17.35,1200,金叉反转
|
||||
2025-10-16 17:44:00,2025-10-16 18:14:00,long,4017.20,4057.01,10000,99.10,12.00,10.80,97.90,1800,超时(1800s)
|
||||
2025-10-17 21:43:00,2025-10-17 21:48:00,short,3754.67,3771.24,10000,-44.13,12.00,10.80,-45.33,300,止损(-0.44%)
|
||||
2025-10-17 21:48:00,2025-10-17 21:52:00,short,3771.24,3788.20,10000,-44.97,12.00,10.80,-46.17,240,止损(-0.45%)
|
||||
2025-10-17 21:52:00,2025-10-17 22:04:00,long,3788.20,3769.81,10000,-48.55,12.00,10.80,-49.75,720,止损(-0.49%)
|
||||
2025-10-19 16:25:00,2025-10-19 16:26:00,short,3841.58,3864.92,10000,-60.76,12.00,10.80,-61.96,60,硬止损(-0.61%)
|
||||
2025-10-23 00:39:00,2025-10-23 00:59:00,long,3844.42,3834.05,10000,-26.97,12.00,10.80,-28.17,1200,死叉反转
|
||||
2025-10-30 02:35:00,2025-10-30 02:36:00,short,3905.22,3929.07,10000,-61.07,12.00,10.80,-62.27,60,硬止损(-0.61%)
|
||||
2025-11-04 22:42:00,2025-11-04 23:12:00,long,3519.78,3553.41,10000,95.55,12.00,10.80,94.35,1800,超时(1800s)
|
||||
2025-11-05 01:42:00,2025-11-05 02:12:00,short,3390.48,3357.61,10000,96.95,12.00,10.80,95.75,1800,超时(1800s)
|
||||
2025-11-05 02:12:00,2025-11-05 02:42:00,short,3357.61,3299.42,10000,173.31,12.00,10.80,172.11,1800,超时(1800s)
|
||||
2025-11-05 02:42:00,2025-11-05 02:55:00,short,3299.42,3301.70,10000,-6.91,12.00,10.80,-8.11,780,金叉反转
|
||||
2025-11-05 02:56:00,2025-11-05 03:00:00,short,3291.01,3303.63,10000,-38.35,12.00,10.80,-39.55,240,延迟金叉
|
||||
2025-11-05 03:07:00,2025-11-05 03:14:00,short,3290.34,3303.99,10000,-41.49,12.00,10.80,-42.69,420,止损(-0.41%)
|
||||
2025-11-05 04:53:00,2025-11-05 04:58:00,short,3181.02,3202.57,10000,-67.75,12.00,10.80,-68.95,300,硬止损(-0.68%)
|
||||
2025-11-05 05:08:00,2025-11-05 05:38:00,short,3190.86,3077.89,10000,354.04,12.00,10.80,352.84,1800,超时(1800s)
|
||||
2025-11-05 05:38:00,2025-11-05 05:39:00,short,3077.89,3100.99,10000,-75.05,12.00,10.80,-76.25,60,硬止损(-0.75%)
|
||||
2025-11-07 22:43:00,2025-11-07 23:13:00,long,3246.98,3307.59,10000,186.67,12.00,10.80,185.47,1800,超时(1800s)
|
||||
2025-11-07 23:13:00,2025-11-07 23:23:00,long,3307.59,3292.22,10000,-46.47,12.00,10.80,-47.67,600,止损(-0.46%)
|
||||
2025-11-07 23:23:00,2025-11-07 23:37:00,long,3292.22,3292.21,10000,-0.03,12.00,10.80,-1.23,840,死叉反转
|
||||
2025-11-13 11:24:00,2025-11-13 11:54:00,long,3430.47,3461.35,10000,90.02,12.00,10.80,88.82,1800,超时(1800s)
|
||||
2025-11-13 22:51:00,2025-11-13 23:00:00,long,3451.15,3435.67,10000,-44.85,12.00,10.80,-46.05,540,止损(-0.45%)
|
||||
2025-11-13 23:04:00,2025-11-13 23:08:00,short,3411.50,3432.35,10000,-61.12,12.00,10.80,-62.32,240,硬止损(-0.61%)
|
||||
2025-11-14 03:34:00,2025-11-14 04:04:00,short,3208.02,3179.06,10000,90.27,12.00,10.80,89.07,1800,超时(1800s)
|
||||
2025-11-14 04:34:00,2025-11-14 04:36:00,short,3170.62,3191.92,10000,-67.18,12.00,10.80,-68.38,120,硬止损(-0.67%)
|
||||
2025-11-14 22:32:00,2025-11-14 22:34:00,short,3121.48,3147.98,10000,-84.90,12.00,10.80,-86.10,120,硬止损(-0.85%)
|
||||
2025-11-14 22:35:00,2025-11-14 23:02:00,long,3167.25,3176.85,10000,30.31,12.00,10.80,29.11,1620,死叉反转
|
||||
2025-11-14 23:22:00,2025-11-14 23:26:00,long,3187.81,3183.59,10000,-13.24,12.00,10.80,-14.44,240,延迟死叉
|
||||
2025-11-14 23:27:00,2025-11-14 23:57:00,long,3202.22,3193.13,10000,-28.39,12.00,10.80,-29.59,1800,超时(1800s)
|
||||
2025-11-17 01:59:00,2025-11-17 02:28:00,long,3078.97,3092.34,10000,43.42,12.00,10.80,42.22,1740,死叉反转
|
||||
2025-11-17 22:56:00,2025-11-17 23:26:00,short,3151.31,3122.60,10000,91.10,12.00,10.80,89.90,1800,超时(1800s)
|
||||
2025-11-17 23:26:00,2025-11-17 23:28:00,short,3122.60,3153.09,10000,-97.64,12.00,10.80,-98.84,120,硬止损(-0.98%)
|
||||
2025-11-17 23:29:00,2025-11-17 23:33:00,long,3151.40,3129.07,10000,-70.86,12.00,10.80,-72.06,240,硬止损(-0.71%)
|
||||
2025-11-17 23:35:00,2025-11-18 00:05:00,short,3126.51,3086.79,10000,127.04,12.00,10.80,125.84,1800,超时(1800s)
|
||||
2025-11-18 11:38:00,2025-11-18 12:07:00,long,2999.93,3002.35,10000,8.07,12.00,10.80,6.87,1740,死叉反转
|
||||
2025-11-18 12:56:00,2025-11-18 13:02:00,long,2999.15,2985.89,10000,-44.21,12.00,10.80,-45.41,360,止损(-0.44%)
|
||||
2025-11-18 23:09:00,2025-11-18 23:39:00,long,3074.76,3112.69,10000,123.36,12.00,10.80,122.16,1800,超时(1800s)
|
||||
2025-11-18 23:39:00,2025-11-18 23:48:00,long,3112.69,3098.96,10000,-44.11,12.00,10.80,-45.31,540,止损(-0.44%)
|
||||
2025-11-19 23:20:00,2025-11-19 23:50:00,short,3049.98,2978.39,10000,234.72,12.00,10.80,233.52,1800,超时(1800s)
|
||||
2025-11-20 01:45:00,2025-11-20 01:52:00,short,2912.34,2923.97,10000,-39.93,12.00,10.80,-41.13,420,金叉反转
|
||||
2025-11-20 01:54:00,2025-11-20 02:13:00,short,2911.80,2923.91,10000,-41.59,12.00,10.80,-42.79,1140,止损(-0.42%)
|
||||
2025-11-20 22:40:00,2025-11-20 22:47:00,short,2985.75,3000.90,10000,-50.74,12.00,10.80,-51.94,420,止损(-0.51%)
|
||||
2025-11-20 22:47:00,2025-11-20 22:53:00,short,3000.90,2998.43,10000,8.23,12.00,10.80,7.03,360,金叉反转
|
||||
2025-11-21 01:43:00,2025-11-21 01:54:00,short,2820.27,2827.47,10000,-25.53,12.00,10.80,-26.73,660,金叉反转
|
||||
2025-11-21 02:02:00,2025-11-21 02:06:00,short,2822.81,2842.42,10000,-69.47,12.00,10.80,-70.67,240,硬止损(-0.69%)
|
||||
2025-11-21 02:23:00,2025-11-21 02:53:00,short,2830.94,2805.79,10000,88.84,12.00,10.80,87.64,1800,超时(1800s)
|
||||
2025-11-21 18:29:00,2025-11-21 18:33:00,short,2685.30,2702.90,10000,-65.54,12.00,10.80,-66.74,240,硬止损(-0.66%)
|
||||
2025-11-24 22:45:00,2025-11-24 22:57:00,short,2804.71,2818.80,10000,-50.24,12.00,10.80,-51.44,720,止损(-0.50%)
|
||||
2025-11-24 22:58:00,2025-11-24 23:20:00,long,2823.13,2823.65,10000,1.84,12.00,10.80,0.64,1320,死叉反转
|
||||
2025-12-03 23:04:00,2025-12-03 23:08:00,long,3105.04,3087.17,10000,-57.55,12.00,10.80,-58.75,240,止损(-0.58%)
|
||||
2025-12-08 22:37:00,2025-12-08 22:43:00,short,3136.68,3155.59,10000,-60.29,12.00,10.80,-61.49,360,硬止损(-0.60%)
|
||||
2025-12-08 22:44:00,2025-12-08 22:55:00,long,3168.44,3154.66,10000,-43.49,12.00,10.80,-44.69,660,止损(-0.43%)
|
||||
2025-12-11 03:12:00,2025-12-11 03:26:00,long,3391.59,3377.85,10000,-40.51,12.00,10.80,-41.71,840,止损(-0.41%)
|
||||
2025-12-11 03:41:00,2025-12-11 03:55:00,short,3375.89,3382.64,10000,-19.99,12.00,10.80,-21.19,840,金叉反转
|
||||
2025-12-16 21:34:00,2025-12-16 21:39:00,short,2925.00,2937.28,10000,-41.98,12.00,10.80,-43.18,300,止损(-0.42%)
|
||||
2025-12-16 21:39:00,2025-12-16 22:09:00,short,2937.28,2919.85,10000,59.34,12.00,10.80,58.14,1800,超时(1800s)
|
||||
2025-12-16 22:09:00,2025-12-16 22:17:00,short,2919.85,2924.64,10000,-16.40,12.00,10.80,-17.60,480,金叉反转
|
||||
2025-12-16 22:45:00,2025-12-16 22:49:00,short,2921.59,2936.31,10000,-50.38,12.00,10.80,-51.58,240,止损(-0.50%)
|
||||
2025-12-17 22:53:00,2025-12-17 23:23:00,long,2951.05,3014.95,10000,216.53,12.00,10.80,215.33,1800,超时(1800s)
|
||||
2025-12-17 23:23:00,2025-12-17 23:35:00,long,3014.95,2985.55,10000,-97.51,12.00,10.80,-98.71,720,硬止损(-0.98%)
|
||||
2025-12-18 22:20:00,2025-12-18 22:26:00,long,2967.76,2956.28,10000,-38.68,12.00,10.80,-39.88,360,死叉反转
|
||||
2025-12-18 22:56:00,2025-12-18 23:03:00,long,2950.00,2943.04,10000,-23.59,12.00,10.80,-24.79,420,死叉反转
|
||||
2025-12-19 22:44:00,2025-12-19 22:46:00,long,2993.80,2975.79,10000,-60.16,12.00,10.80,-61.36,120,硬止损(-0.60%)
|
||||
2025-12-19 23:21:00,2025-12-19 23:32:00,short,2958.02,2970.69,10000,-42.83,12.00,10.80,-44.03,660,止损(-0.43%)
|
||||
|
14067
EMA-Trend_trades.csv
Normal file
14067
EMA-Trend_trades.csv
Normal file
File diff suppressed because it is too large
Load Diff
15699
Grid+Trend_trades.csv
Normal file
15699
Grid+Trend_trades.csv
Normal file
File diff suppressed because it is too large
Load Diff
924
ai_trades.csv
Normal file
924
ai_trades.csv
Normal file
@@ -0,0 +1,924 @@
|
||||
dir,open_px,close_px,pnl,hold_sec,reason,open_time,close_time
|
||||
long,2177.04,2182.92,27.01,1800,timeout,2025-03-01 11:18:00,2025-03-01 11:48:00
|
||||
long,2182.79,2184.13,6.14,1800,timeout,2025-03-01 11:49:00,2025-03-01 12:19:00
|
||||
long,2184.02,2184.18,0.73,1800,timeout,2025-03-02 14:17:00,2025-03-02 14:47:00
|
||||
short,2258.32,2273.86,-68.81,540,sl,2025-03-02 15:28:00,2025-03-02 15:37:00
|
||||
short,2268.94,2248.27,91.10,300,tp,2025-03-02 15:39:00,2025-03-02 15:44:00
|
||||
long,2229.61,2216.58,-58.44,1200,sl,2025-03-02 15:45:00,2025-03-02 16:05:00
|
||||
short,2264.45,2277.49,-57.59,780,sl,2025-03-02 16:18:00,2025-03-02 16:31:00
|
||||
short,2284.33,2319.47,-153.83,180,hard_sl,2025-03-02 16:32:00,2025-03-02 16:35:00
|
||||
short,2323.27,2352.33,-125.08,120,hard_sl,2025-03-02 16:36:00,2025-03-02 16:38:00
|
||||
short,2448.64,2490.06,-169.16,240,hard_sl,2025-03-02 16:39:00,2025-03-02 16:43:00
|
||||
short,2487.14,2439.78,190.42,240,tp,2025-03-02 16:44:00,2025-03-02 16:48:00
|
||||
short,2461.39,2441.47,80.93,480,tp,2025-03-02 16:51:00,2025-03-02 16:59:00
|
||||
short,2452.95,2409.68,176.40,240,tp,2025-03-02 17:00:00,2025-03-02 17:04:00
|
||||
short,2444.75,2464.13,-79.27,240,hard_sl,2025-03-02 17:12:00,2025-03-02 17:16:00
|
||||
short,2468.30,2492.14,-96.58,1440,hard_sl,2025-03-02 17:17:00,2025-03-02 17:41:00
|
||||
short,2503.30,2468.55,138.82,240,tp,2025-03-02 17:43:00,2025-03-02 17:47:00
|
||||
short,2484.13,2461.26,92.06,960,tp,2025-03-02 18:00:00,2025-03-02 18:16:00
|
||||
short,2492.94,2469.87,92.54,780,tp,2025-03-02 18:21:00,2025-03-02 18:34:00
|
||||
long,2495.97,2482.25,-54.97,480,sl,2025-03-03 00:06:00,2025-03-03 00:14:00
|
||||
long,2480.89,2473.04,-31.64,1800,timeout,2025-03-03 00:17:00,2025-03-03 00:47:00
|
||||
long,2387.41,2368.98,-77.20,600,hard_sl,2025-03-03 06:41:00,2025-03-03 06:51:00
|
||||
long,2364.84,2376.24,48.21,1800,timeout,2025-03-03 06:52:00,2025-03-03 07:22:00
|
||||
long,2337.99,2324.00,-59.84,1800,sl,2025-03-03 08:51:00,2025-03-03 09:21:00
|
||||
long,2340.92,2327.40,-57.76,300,sl,2025-03-03 14:37:00,2025-03-03 14:42:00
|
||||
long,2316.78,2301.21,-67.21,660,sl,2025-03-03 14:43:00,2025-03-03 14:54:00
|
||||
long,2297.93,2276.34,-93.95,120,hard_sl,2025-03-03 14:55:00,2025-03-03 14:57:00
|
||||
long,2289.30,2270.43,-82.43,60,hard_sl,2025-03-03 14:58:00,2025-03-03 14:59:00
|
||||
long,2287.02,2305.80,82.12,1380,tp,2025-03-03 15:00:00,2025-03-03 15:23:00
|
||||
long,2296.52,2297.18,2.87,1800,timeout,2025-03-03 15:26:00,2025-03-03 15:56:00
|
||||
long,2262.18,2242.60,-86.55,780,hard_sl,2025-03-03 18:05:00,2025-03-03 18:18:00
|
||||
long,2236.26,2220.22,-71.73,420,sl,2025-03-03 18:19:00,2025-03-03 18:26:00
|
||||
short,2206.98,2180.77,118.76,660,tp,2025-03-03 18:28:00,2025-03-03 18:39:00
|
||||
long,2194.67,2181.25,-61.15,1500,sl,2025-03-03 18:46:00,2025-03-03 19:11:00
|
||||
long,2180.62,2167.87,-58.47,240,sl,2025-03-03 19:12:00,2025-03-03 19:16:00
|
||||
long,2162.27,2189.42,125.56,840,tp,2025-03-03 19:17:00,2025-03-03 19:31:00
|
||||
long,2113.85,2132.43,87.90,660,tp,2025-03-03 19:55:00,2025-03-03 20:06:00
|
||||
long,2134.06,2121.39,-59.37,780,sl,2025-03-03 20:16:00,2025-03-03 20:29:00
|
||||
long,2103.22,2122.95,93.81,540,tp,2025-03-03 20:39:00,2025-03-03 20:48:00
|
||||
long,2132.49,2134.06,7.36,1800,timeout,2025-03-03 21:02:00,2025-03-03 21:32:00
|
||||
long,2160.97,2149.84,-51.50,1440,sl,2025-03-03 22:51:00,2025-03-03 23:15:00
|
||||
long,2103.41,2122.15,89.09,360,tp,2025-03-04 01:24:00,2025-03-04 01:30:00
|
||||
long,2066.25,2043.03,-112.38,120,hard_sl,2025-03-04 01:39:00,2025-03-04 01:41:00
|
||||
long,2053.46,2031.31,-107.87,180,hard_sl,2025-03-04 01:42:00,2025-03-04 01:45:00
|
||||
short,2042.76,2061.94,-93.89,240,hard_sl,2025-03-04 01:46:00,2025-03-04 01:50:00
|
||||
short,2068.66,2055.08,65.65,480,ai_rev,2025-03-04 01:51:00,2025-03-04 01:59:00
|
||||
long,2055.08,2028.76,-128.07,360,hard_sl,2025-03-04 01:59:00,2025-03-04 02:05:00
|
||||
long,2003.03,2020.56,87.52,240,tp,2025-03-04 02:06:00,2025-03-04 02:10:00
|
||||
long,2032.09,2037.80,28.10,300,ai_rev,2025-03-04 02:11:00,2025-03-04 02:16:00
|
||||
short,2037.80,2054.28,-80.87,540,hard_sl,2025-03-04 02:16:00,2025-03-04 02:25:00
|
||||
short,2054.72,2069.99,-74.32,600,sl,2025-03-04 02:26:00,2025-03-04 02:36:00
|
||||
short,2060.64,2076.04,-74.73,780,sl,2025-03-04 02:41:00,2025-03-04 02:54:00
|
||||
short,2086.14,2082.44,17.74,1800,timeout,2025-03-04 03:52:00,2025-03-04 04:22:00
|
||||
short,2104.28,2120.15,-75.42,60,hard_sl,2025-03-04 05:05:00,2025-03-04 05:06:00
|
||||
short,2108.12,2090.87,81.83,780,tp,2025-03-04 05:07:00,2025-03-04 05:20:00
|
||||
long,2059.62,2081.39,105.70,720,tp,2025-03-04 13:55:00,2025-03-04 14:07:00
|
||||
short,2139.50,2114.76,115.63,240,tp,2025-03-04 14:40:00,2025-03-04 14:44:00
|
||||
long,2111.64,2095.93,-74.40,240,sl,2025-03-04 14:49:00,2025-03-04 14:53:00
|
||||
short,2068.37,2051.69,80.64,420,tp,2025-03-04 15:11:00,2025-03-04 15:18:00
|
||||
long,2041.20,2057.98,82.21,600,tp,2025-03-04 15:20:00,2025-03-04 15:30:00
|
||||
long,2072.16,2061.00,-53.86,1500,sl,2025-03-04 15:42:00,2025-03-04 16:07:00
|
||||
short,2024.01,1995.28,141.95,360,tp,2025-03-04 16:29:00,2025-03-04 16:35:00
|
||||
long,2017.23,2039.47,110.25,480,tp,2025-03-04 16:36:00,2025-03-04 16:44:00
|
||||
short,2065.81,2078.25,-60.22,300,sl,2025-03-04 16:58:00,2025-03-04 17:03:00
|
||||
short,2076.93,2088.49,-55.66,600,sl,2025-03-04 17:06:00,2025-03-04 17:16:00
|
||||
short,2108.96,2120.38,-54.15,480,sl,2025-03-04 17:19:00,2025-03-04 17:27:00
|
||||
short,2118.06,2131.94,-65.53,1080,sl,2025-03-04 17:28:00,2025-03-04 17:46:00
|
||||
short,2118.66,2130.80,-57.30,1020,sl,2025-03-04 17:51:00,2025-03-04 18:08:00
|
||||
short,2136.07,2118.65,81.55,1200,tp,2025-03-04 18:10:00,2025-03-04 18:30:00
|
||||
long,2126.01,2147.51,101.13,480,tp,2025-03-04 21:03:00,2025-03-04 21:11:00
|
||||
short,2203.47,2220.54,-77.47,180,hard_sl,2025-03-04 21:25:00,2025-03-04 21:28:00
|
||||
short,2215.83,2195.74,90.67,300,tp,2025-03-04 21:29:00,2025-03-04 21:34:00
|
||||
short,2194.78,2175.99,85.61,600,tp,2025-03-04 21:37:00,2025-03-04 21:47:00
|
||||
long,2177.37,2186.30,41.01,1800,timeout,2025-03-05 18:08:00,2025-03-05 18:38:00
|
||||
short,2280.83,2262.02,82.47,420,tp,2025-03-06 01:48:00,2025-03-06 01:55:00
|
||||
long,2213.92,2231.64,80.04,840,tp,2025-03-06 14:36:00,2025-03-06 14:50:00
|
||||
long,2182.98,2196.96,64.04,1800,timeout,2025-03-06 17:45:00,2025-03-06 18:15:00
|
||||
long,2177.44,2152.34,-115.27,60,hard_sl,2025-03-07 00:15:00,2025-03-07 00:16:00
|
||||
long,2169.74,2133.40,-167.49,180,hard_sl,2025-03-07 00:17:00,2025-03-07 00:20:00
|
||||
long,2130.48,2114.02,-77.26,300,hard_sl,2025-03-07 00:21:00,2025-03-07 00:26:00
|
||||
long,2118.49,2130.47,56.55,300,ai_rev,2025-03-07 00:27:00,2025-03-07 00:32:00
|
||||
short,2130.47,2112.11,86.18,240,tp,2025-03-07 00:32:00,2025-03-07 00:36:00
|
||||
long,2114.86,2133.64,88.80,1800,tp,2025-03-07 00:37:00,2025-03-07 01:07:00
|
||||
short,2135.77,2151.43,-73.32,540,sl,2025-03-07 01:09:00,2025-03-07 01:18:00
|
||||
short,2165.54,2177.96,-57.35,1260,sl,2025-03-07 01:19:00,2025-03-07 01:40:00
|
||||
short,2184.04,2183.45,2.70,1800,timeout,2025-03-07 01:41:00,2025-03-07 02:11:00
|
||||
long,2244.44,2227.67,-74.72,300,sl,2025-03-07 14:51:00,2025-03-07 14:56:00
|
||||
short,2228.27,2209.20,85.58,960,tp,2025-03-07 15:01:00,2025-03-07 15:17:00
|
||||
long,2206.59,2195.52,-50.17,1020,sl,2025-03-07 15:53:00,2025-03-07 16:10:00
|
||||
long,2188.65,2172.05,-75.85,540,hard_sl,2025-03-07 16:12:00,2025-03-07 16:21:00
|
||||
short,2125.22,2141.14,-74.91,420,sl,2025-03-07 21:14:00,2025-03-07 21:21:00
|
||||
long,2118.04,2135.22,81.11,1320,tp,2025-03-07 22:10:00,2025-03-07 22:32:00
|
||||
long,2117.32,2135.34,85.11,1200,tp,2025-03-07 23:13:00,2025-03-07 23:33:00
|
||||
long,2026.83,2016.06,-53.14,1380,sl,2025-03-09 17:21:00,2025-03-09 17:44:00
|
||||
long,2015.58,2033.65,89.65,600,tp,2025-03-09 17:57:00,2025-03-09 18:07:00
|
||||
long,2010.35,2028.60,90.78,540,tp,2025-03-09 18:18:00,2025-03-09 18:27:00
|
||||
long,1990.58,2016.49,130.16,240,tp,2025-03-09 23:06:00,2025-03-09 23:10:00
|
||||
short,2024.11,2035.45,-56.02,420,sl,2025-03-10 00:43:00,2025-03-10 00:50:00
|
||||
short,2032.66,2044.36,-57.56,660,sl,2025-03-10 00:51:00,2025-03-10 01:02:00
|
||||
short,2102.64,2130.06,-130.41,60,hard_sl,2025-03-10 09:16:00,2025-03-10 09:17:00
|
||||
short,2131.23,2106.20,117.44,1200,tp,2025-03-10 09:19:00,2025-03-10 09:39:00
|
||||
long,2082.14,2098.97,80.83,1800,tp,2025-03-10 10:23:00,2025-03-10 10:53:00
|
||||
long,2083.49,2071.97,-55.29,480,sl,2025-03-10 13:53:00,2025-03-10 14:01:00
|
||||
long,2066.99,2047.77,-92.99,60,hard_sl,2025-03-10 14:02:00,2025-03-10 14:03:00
|
||||
long,2051.36,2036.31,-73.37,720,sl,2025-03-10 14:04:00,2025-03-10 14:16:00
|
||||
long,2026.65,2010.47,-79.84,60,hard_sl,2025-03-10 14:17:00,2025-03-10 14:18:00
|
||||
long,2015.71,2035.24,96.89,300,tp,2025-03-10 14:19:00,2025-03-10 14:24:00
|
||||
long,2035.56,2019.91,-76.88,240,hard_sl,2025-03-10 14:25:00,2025-03-10 14:29:00
|
||||
long,2022.13,2005.80,-80.76,300,hard_sl,2025-03-10 14:30:00,2025-03-10 14:35:00
|
||||
long,2008.39,1998.06,-51.43,960,sl,2025-03-10 14:36:00,2025-03-10 14:52:00
|
||||
long,2003.76,2023.39,97.97,480,tp,2025-03-10 14:53:00,2025-03-10 15:01:00
|
||||
long,2020.72,2028.30,37.51,1800,timeout,2025-03-10 15:02:00,2025-03-10 15:32:00
|
||||
short,2035.25,2016.93,90.01,1560,tp,2025-03-10 15:33:00,2025-03-10 15:59:00
|
||||
long,2000.95,2018.35,86.96,1560,tp,2025-03-10 16:08:00,2025-03-10 16:34:00
|
||||
short,1975.28,1943.99,158.41,240,tp,2025-03-10 17:03:00,2025-03-10 17:07:00
|
||||
long,1947.55,1936.72,-55.61,240,sl,2025-03-10 17:08:00,2025-03-10 17:12:00
|
||||
long,1940.24,1960.33,103.54,240,tp,2025-03-10 17:13:00,2025-03-10 17:17:00
|
||||
short,1957.55,1941.86,80.15,780,tp,2025-03-10 17:21:00,2025-03-10 17:34:00
|
||||
long,1935.90,1925.05,-56.05,240,sl,2025-03-10 17:35:00,2025-03-10 17:39:00
|
||||
long,1918.68,1920.62,10.11,1800,timeout,2025-03-10 17:40:00,2025-03-10 18:10:00
|
||||
long,1922.76,1912.68,-52.42,240,sl,2025-03-10 18:17:00,2025-03-10 18:21:00
|
||||
long,1890.51,1861.78,-151.97,240,hard_sl,2025-03-10 18:49:00,2025-03-10 18:53:00
|
||||
long,1855.15,1838.48,-89.86,240,hard_sl,2025-03-10 18:54:00,2025-03-10 18:58:00
|
||||
long,1822.49,1844.84,122.63,480,tp,2025-03-10 18:59:00,2025-03-10 19:07:00
|
||||
long,1856.94,1877.21,109.16,660,tp,2025-03-10 19:23:00,2025-03-10 19:34:00
|
||||
long,1876.17,1866.62,-50.90,480,sl,2025-03-10 19:35:00,2025-03-10 19:43:00
|
||||
short,1872.18,1864.05,43.43,1800,timeout,2025-03-10 20:02:00,2025-03-10 20:32:00
|
||||
long,1897.71,1885.93,-62.07,480,sl,2025-03-10 21:40:00,2025-03-10 21:48:00
|
||||
long,1859.73,1879.34,105.45,360,tp,2025-03-10 23:58:00,2025-03-11 00:04:00
|
||||
short,1886.77,1869.45,91.80,420,tp,2025-03-11 00:08:00,2025-03-11 00:15:00
|
||||
long,1861.01,1849.11,-63.94,420,sl,2025-03-11 00:21:00,2025-03-11 00:28:00
|
||||
long,1831.66,1819.39,-66.99,300,sl,2025-03-11 00:29:00,2025-03-11 00:34:00
|
||||
long,1805.62,1822.12,91.38,420,tp,2025-03-11 00:35:00,2025-03-11 00:42:00
|
||||
long,1807.50,1793.63,-76.74,300,hard_sl,2025-03-11 00:45:00,2025-03-11 00:50:00
|
||||
long,1767.79,1798.20,172.02,240,tp,2025-03-11 00:51:00,2025-03-11 00:55:00
|
||||
long,1786.53,1805.15,104.22,240,tp,2025-03-11 00:56:00,2025-03-11 01:00:00
|
||||
short,1809.32,1789.53,109.38,240,tp,2025-03-11 01:01:00,2025-03-11 01:05:00
|
||||
long,1787.39,1810.78,130.86,540,tp,2025-03-11 01:09:00,2025-03-11 01:18:00
|
||||
short,1807.04,1818.07,-61.04,660,sl,2025-03-11 01:19:00,2025-03-11 01:30:00
|
||||
short,1828.22,1837.43,-50.38,660,sl,2025-03-11 01:31:00,2025-03-11 01:42:00
|
||||
short,1839.61,1850.66,-60.07,780,sl,2025-03-11 01:43:00,2025-03-11 01:56:00
|
||||
short,1850.68,1861.06,-56.09,240,sl,2025-03-11 01:57:00,2025-03-11 02:01:00
|
||||
short,1864.07,1874.24,-54.56,1020,sl,2025-03-11 02:02:00,2025-03-11 02:19:00
|
||||
short,1899.49,1883.66,83.34,1560,tp,2025-03-11 05:14:00,2025-03-11 05:40:00
|
||||
long,1891.61,1903.20,61.27,1800,timeout,2025-03-11 12:14:00,2025-03-11 12:44:00
|
||||
long,1868.74,1959.11,483.59,1080,tp,2025-03-11 13:09:00,2025-03-11 13:27:00
|
||||
long,1900.55,1918.18,92.76,900,tp,2025-03-11 13:34:00,2025-03-11 13:49:00
|
||||
long,1908.40,1888.29,-105.38,120,hard_sl,2025-03-11 14:00:00,2025-03-11 14:02:00
|
||||
long,1879.95,1898.09,96.49,300,tp,2025-03-11 14:03:00,2025-03-11 14:08:00
|
||||
long,1894.64,1883.72,-57.64,240,sl,2025-03-11 14:12:00,2025-03-11 14:16:00
|
||||
long,1880.49,1870.25,-54.45,1020,sl,2025-03-11 14:17:00,2025-03-11 14:34:00
|
||||
long,1868.72,1853.12,-83.48,180,hard_sl,2025-03-11 14:35:00,2025-03-11 14:38:00
|
||||
long,1850.95,1867.29,88.28,780,tp,2025-03-11 14:39:00,2025-03-11 14:52:00
|
||||
long,1866.76,1887.91,113.30,480,tp,2025-03-11 14:58:00,2025-03-11 15:06:00
|
||||
short,1904.63,1914.74,-53.08,1680,sl,2025-03-11 15:12:00,2025-03-11 15:40:00
|
||||
short,1917.65,1927.74,-52.62,300,sl,2025-03-11 18:02:00,2025-03-11 18:07:00
|
||||
short,1939.86,1937.74,10.93,360,ai_rev,2025-03-11 18:12:00,2025-03-11 18:18:00
|
||||
long,1937.74,1949.59,61.15,240,ai_rev,2025-03-11 18:18:00,2025-03-11 18:22:00
|
||||
short,1949.59,1957.49,-40.52,1800,timeout,2025-03-11 18:22:00,2025-03-11 18:52:00
|
||||
long,1860.33,1858.41,-10.32,1800,timeout,2025-03-12 04:01:00,2025-03-12 04:31:00
|
||||
short,1894.80,1907.62,-67.66,900,sl,2025-03-12 07:05:00,2025-03-12 07:20:00
|
||||
long,1892.68,1913.95,112.38,480,tp,2025-03-12 08:19:00,2025-03-12 08:27:00
|
||||
short,1913.89,1924.39,-54.86,900,sl,2025-03-12 08:29:00,2025-03-12 08:44:00
|
||||
short,1951.64,1920.79,158.07,240,tp,2025-03-12 09:04:00,2025-03-12 09:08:00
|
||||
long,1907.77,1897.65,-53.05,540,sl,2025-03-12 09:10:00,2025-03-12 09:19:00
|
||||
long,1892.24,1878.65,-71.82,540,sl,2025-03-12 09:20:00,2025-03-12 09:29:00
|
||||
long,1884.84,1881.06,-20.05,1800,timeout,2025-03-12 13:54:00,2025-03-12 14:24:00
|
||||
long,1863.93,1851.75,-65.35,1800,sl,2025-03-12 14:32:00,2025-03-12 15:02:00
|
||||
long,1849.32,1837.36,-64.67,300,sl,2025-03-12 15:05:00,2025-03-12 15:10:00
|
||||
short,1897.83,1902.05,-22.24,1800,timeout,2025-03-13 12:36:00,2025-03-13 13:06:00
|
||||
long,1865.24,1883.20,96.29,660,tp,2025-03-13 14:01:00,2025-03-13 14:12:00
|
||||
short,1888.11,1879.88,43.59,1800,timeout,2025-03-16 12:29:00,2025-03-16 12:59:00
|
||||
long,1870.04,1878.93,47.54,1800,timeout,2025-03-16 19:17:00,2025-03-16 19:47:00
|
||||
short,1922.09,1905.75,85.01,1380,tp,2025-03-17 12:30:00,2025-03-17 12:53:00
|
||||
long,1872.94,1880.72,41.54,1800,timeout,2025-03-18 13:47:00,2025-03-18 14:17:00
|
||||
long,2019.58,2036.53,83.93,600,tp,2025-03-19 18:05:00,2025-03-19 18:15:00
|
||||
long,1981.27,1984.42,15.90,1800,timeout,2025-03-20 10:16:00,2025-03-20 10:46:00
|
||||
long,2010.75,2006.23,-22.48,1800,timeout,2025-03-24 03:19:00,2025-03-24 03:49:00
|
||||
long,2016.72,2014.99,-8.58,1800,timeout,2025-03-26 14:21:00,2025-03-26 14:51:00
|
||||
long,1989.34,2007.05,89.02,1380,tp,2025-03-27 13:37:00,2025-03-27 14:00:00
|
||||
short,1945.35,1932.05,68.37,300,ai_rev,2025-03-28 04:40:00,2025-03-28 04:45:00
|
||||
long,1932.05,1924.97,-36.65,1800,timeout,2025-03-28 04:45:00,2025-03-28 05:15:00
|
||||
long,1923.31,1913.09,-53.14,1620,sl,2025-03-28 06:25:00,2025-03-28 06:52:00
|
||||
long,1835.93,1846.16,55.72,1800,timeout,2025-03-29 11:39:00,2025-03-29 12:09:00
|
||||
long,1793.95,1777.22,-93.26,300,hard_sl,2025-03-30 22:02:00,2025-03-30 22:07:00
|
||||
long,1771.41,1788.22,94.90,900,tp,2025-03-30 22:08:00,2025-03-30 22:23:00
|
||||
long,1811.10,1800.01,-61.23,1200,sl,2025-03-30 22:42:00,2025-03-30 23:02:00
|
||||
short,1806.17,1817.53,-62.90,1200,sl,2025-03-31 01:06:00,2025-03-31 01:26:00
|
||||
short,1829.67,1826.19,19.02,1800,timeout,2025-04-01 00:57:00,2025-04-01 01:27:00
|
||||
short,1909.10,1920.06,-57.41,1680,sl,2025-04-01 15:15:00,2025-04-01 15:43:00
|
||||
long,1884.99,1949.81,343.87,240,tp,2025-04-02 20:12:00,2025-04-02 20:16:00
|
||||
long,1938.34,1923.66,-75.73,300,hard_sl,2025-04-02 20:17:00,2025-04-02 20:22:00
|
||||
short,1922.28,1897.55,128.65,240,tp,2025-04-02 20:25:00,2025-04-02 20:29:00
|
||||
long,1857.91,1868.75,58.35,1800,timeout,2025-04-02 21:11:00,2025-04-02 21:41:00
|
||||
long,1847.73,1832.78,-80.91,360,hard_sl,2025-04-02 22:06:00,2025-04-02 22:12:00
|
||||
long,1827.35,1817.28,-55.11,240,sl,2025-04-02 22:17:00,2025-04-02 22:21:00
|
||||
long,1819.97,1810.43,-52.42,900,sl,2025-04-02 22:23:00,2025-04-02 22:38:00
|
||||
long,1806.15,1796.65,-52.60,240,sl,2025-04-02 22:41:00,2025-04-02 22:45:00
|
||||
long,1797.93,1787.03,-60.63,540,sl,2025-04-02 22:51:00,2025-04-02 23:00:00
|
||||
long,1786.45,1795.82,52.45,1800,timeout,2025-04-02 23:01:00,2025-04-02 23:31:00
|
||||
short,1804.93,1814.41,-52.52,780,sl,2025-04-03 00:06:00,2025-04-03 00:19:00
|
||||
long,1752.33,1766.86,82.92,1440,tp,2025-04-03 12:47:00,2025-04-03 13:11:00
|
||||
long,1797.49,1787.01,-58.30,240,sl,2025-04-03 18:50:00,2025-04-03 18:54:00
|
||||
long,1795.56,1785.77,-54.52,1380,sl,2025-04-04 10:22:00,2025-04-04 10:45:00
|
||||
short,1785.16,1769.94,85.26,420,tp,2025-04-04 12:31:00,2025-04-04 12:38:00
|
||||
short,1699.30,1671.91,161.18,840,tp,2025-04-06 17:28:00,2025-04-06 17:42:00
|
||||
long,1678.32,1694.82,98.31,300,tp,2025-04-06 17:43:00,2025-04-06 17:48:00
|
||||
long,1676.80,1664.05,-76.04,480,hard_sl,2025-04-06 18:00:00,2025-04-06 18:08:00
|
||||
long,1662.87,1650.16,-76.43,180,hard_sl,2025-04-06 18:16:00,2025-04-06 18:19:00
|
||||
long,1637.25,1651.07,84.41,300,tp,2025-04-06 18:20:00,2025-04-06 18:25:00
|
||||
short,1637.00,1623.26,83.93,480,tp,2025-04-06 18:31:00,2025-04-06 18:39:00
|
||||
long,1618.00,1601.98,-99.01,360,hard_sl,2025-04-06 18:44:00,2025-04-06 18:50:00
|
||||
long,1614.69,1632.03,107.39,1500,tp,2025-04-06 18:51:00,2025-04-06 19:16:00
|
||||
short,1588.91,1580.89,50.47,240,ai_rev,2025-04-06 20:34:00,2025-04-06 20:38:00
|
||||
long,1580.89,1595.82,94.44,240,tp,2025-04-06 20:38:00,2025-04-06 20:42:00
|
||||
short,1586.60,1571.43,95.61,420,tp,2025-04-06 20:46:00,2025-04-06 20:53:00
|
||||
short,1600.95,1586.93,87.57,720,tp,2025-04-06 22:10:00,2025-04-06 22:22:00
|
||||
long,1570.05,1561.38,-55.22,720,sl,2025-04-06 22:56:00,2025-04-06 23:08:00
|
||||
long,1557.81,1535.93,-140.45,420,hard_sl,2025-04-06 23:10:00,2025-04-06 23:17:00
|
||||
long,1546.24,1559.68,86.92,840,tp,2025-04-06 23:18:00,2025-04-06 23:32:00
|
||||
short,1580.95,1566.90,88.87,1380,tp,2025-04-07 00:17:00,2025-04-07 00:40:00
|
||||
short,1601.24,1610.04,-54.96,240,sl,2025-04-07 01:19:00,2025-04-07 01:23:00
|
||||
short,1610.47,1595.05,95.75,840,tp,2025-04-07 01:24:00,2025-04-07 01:38:00
|
||||
long,1551.13,1527.98,-149.25,480,hard_sl,2025-04-07 03:36:00,2025-04-07 03:44:00
|
||||
short,1532.10,1540.63,-55.68,480,sl,2025-04-07 03:45:00,2025-04-07 03:53:00
|
||||
long,1524.99,1514.34,-69.84,360,sl,2025-04-07 06:18:00,2025-04-07 06:24:00
|
||||
short,1509.13,1488.49,136.77,540,tp,2025-04-07 06:25:00,2025-04-07 06:34:00
|
||||
short,1483.71,1471.00,85.66,480,tp,2025-04-07 06:35:00,2025-04-07 06:43:00
|
||||
long,1435.00,1458.84,166.13,240,tp,2025-04-07 06:46:00,2025-04-07 06:50:00
|
||||
long,1457.76,1442.85,-102.28,120,hard_sl,2025-04-07 06:51:00,2025-04-07 06:53:00
|
||||
long,1425.16,1438.01,90.17,360,tp,2025-04-07 06:54:00,2025-04-07 07:00:00
|
||||
long,1444.95,1461.15,112.11,300,tp,2025-04-07 07:14:00,2025-04-07 07:19:00
|
||||
long,1460.11,1450.92,-62.94,540,sl,2025-04-07 07:20:00,2025-04-07 07:29:00
|
||||
long,1446.78,1458.82,83.22,480,tp,2025-04-07 07:30:00,2025-04-07 07:38:00
|
||||
long,1461.76,1477.34,106.58,1140,tp,2025-04-07 07:58:00,2025-04-07 08:17:00
|
||||
short,1488.27,1502.86,-98.03,900,hard_sl,2025-04-07 08:20:00,2025-04-07 08:35:00
|
||||
short,1501.11,1508.84,-51.50,360,sl,2025-04-07 08:37:00,2025-04-07 08:43:00
|
||||
short,1515.75,1502.52,87.28,660,tp,2025-04-07 10:16:00,2025-04-07 10:27:00
|
||||
short,1498.74,1511.00,-81.80,180,hard_sl,2025-04-07 12:25:00,2025-04-07 12:28:00
|
||||
short,1516.98,1504.70,80.95,660,tp,2025-04-07 12:29:00,2025-04-07 12:40:00
|
||||
short,1542.70,1555.12,-80.51,540,hard_sl,2025-04-07 13:51:00,2025-04-07 14:00:00
|
||||
long,1549.26,1596.39,304.21,240,tp,2025-04-07 14:09:00,2025-04-07 14:13:00
|
||||
short,1594.04,1629.75,-224.02,120,hard_sl,2025-04-07 14:15:00,2025-04-07 14:17:00
|
||||
short,1589.01,1568.15,131.28,240,tp,2025-04-07 14:20:00,2025-04-07 14:24:00
|
||||
long,1574.18,1557.04,-108.88,180,hard_sl,2025-04-07 14:25:00,2025-04-07 14:28:00
|
||||
long,1557.81,1583.09,162.28,240,tp,2025-04-07 14:29:00,2025-04-07 14:33:00
|
||||
long,1558.32,1549.43,-57.05,300,sl,2025-04-07 14:34:00,2025-04-07 14:39:00
|
||||
long,1554.60,1568.08,86.71,300,tp,2025-04-07 14:40:00,2025-04-07 14:45:00
|
||||
long,1558.43,1571.92,86.56,240,tp,2025-04-07 14:46:00,2025-04-07 14:50:00
|
||||
long,1569.91,1559.76,-64.65,300,sl,2025-04-07 15:00:00,2025-04-07 15:05:00
|
||||
long,1548.09,1560.97,83.20,1440,tp,2025-04-07 15:14:00,2025-04-07 15:38:00
|
||||
long,1531.61,1544.24,82.46,840,tp,2025-04-07 16:46:00,2025-04-07 17:00:00
|
||||
short,1583.57,1592.36,-55.51,1200,sl,2025-04-08 01:38:00,2025-04-08 01:58:00
|
||||
short,1607.43,1593.57,86.22,1560,tp,2025-04-08 02:09:00,2025-04-08 02:35:00
|
||||
long,1528.49,1520.47,-52.47,420,sl,2025-04-08 15:06:00,2025-04-08 15:13:00
|
||||
long,1522.47,1536.01,88.93,1440,tp,2025-04-08 15:14:00,2025-04-08 15:38:00
|
||||
long,1495.21,1482.55,-84.67,1020,hard_sl,2025-04-08 16:39:00,2025-04-08 16:56:00
|
||||
long,1485.91,1477.30,-57.94,600,sl,2025-04-08 16:59:00,2025-04-08 17:09:00
|
||||
long,1466.76,1454.95,-80.52,780,hard_sl,2025-04-08 17:13:00,2025-04-08 17:26:00
|
||||
long,1467.06,1479.21,82.82,300,tp,2025-04-08 17:36:00,2025-04-08 17:41:00
|
||||
long,1447.82,1464.30,113.83,300,tp,2025-04-08 22:20:00,2025-04-08 22:25:00
|
||||
short,1472.64,1468.34,29.20,1800,timeout,2025-04-09 00:09:00,2025-04-09 00:39:00
|
||||
long,1444.34,1428.76,-107.87,180,hard_sl,2025-04-09 01:10:00,2025-04-09 01:13:00
|
||||
long,1424.00,1416.36,-53.65,240,sl,2025-04-09 01:14:00,2025-04-09 01:18:00
|
||||
long,1413.89,1402.68,-79.28,240,hard_sl,2025-04-09 01:19:00,2025-04-09 01:23:00
|
||||
long,1388.00,1403.96,114.99,240,tp,2025-04-09 01:27:00,2025-04-09 01:31:00
|
||||
short,1406.64,1416.83,-72.44,240,sl,2025-04-09 01:32:00,2025-04-09 01:36:00
|
||||
short,1422.19,1429.61,-52.17,660,sl,2025-04-09 01:37:00,2025-04-09 01:48:00
|
||||
long,1434.02,1447.99,97.42,1020,tp,2025-04-09 01:49:00,2025-04-09 02:06:00
|
||||
long,1446.51,1435.57,-75.63,1140,hard_sl,2025-04-09 02:07:00,2025-04-09 02:26:00
|
||||
long,1418.64,1417.05,-11.21,1800,timeout,2025-04-09 02:56:00,2025-04-09 03:26:00
|
||||
short,1477.22,1482.05,-32.70,1800,timeout,2025-04-09 07:03:00,2025-04-09 07:33:00
|
||||
long,1452.08,1449.01,-21.14,1800,timeout,2025-04-09 11:05:00,2025-04-09 11:35:00
|
||||
long,1452.64,1447.50,-35.38,1800,timeout,2025-04-09 11:54:00,2025-04-09 12:24:00
|
||||
short,1471.52,1486.44,-101.39,60,hard_sl,2025-04-09 13:31:00,2025-04-09 13:32:00
|
||||
long,1514.94,1549.48,228.00,240,tp,2025-04-09 17:19:00,2025-04-09 17:23:00
|
||||
short,1570.70,1579.18,-53.99,360,sl,2025-04-09 17:24:00,2025-04-09 17:30:00
|
||||
short,1595.15,1580.66,90.84,780,tp,2025-04-09 17:31:00,2025-04-09 17:44:00
|
||||
short,1598.76,1607.66,-55.67,300,sl,2025-04-09 17:49:00,2025-04-09 17:54:00
|
||||
short,1605.03,1620.69,-97.57,120,hard_sl,2025-04-09 17:55:00,2025-04-09 17:57:00
|
||||
short,1631.39,1646.66,-93.60,900,hard_sl,2025-04-09 17:58:00,2025-04-09 18:13:00
|
||||
short,1655.84,1666.57,-64.80,480,sl,2025-04-09 18:15:00,2025-04-09 18:23:00
|
||||
short,1663.03,1648.01,90.32,600,tp,2025-04-09 18:24:00,2025-04-09 18:34:00
|
||||
long,1634.64,1636.61,12.05,1800,timeout,2025-04-09 18:45:00,2025-04-09 19:15:00
|
||||
long,1616.05,1607.77,-51.24,240,sl,2025-04-10 03:15:00,2025-04-10 03:19:00
|
||||
long,1592.55,1594.22,10.49,1800,timeout,2025-04-10 07:31:00,2025-04-10 08:01:00
|
||||
short,1617.02,1603.03,86.52,240,tp,2025-04-10 12:30:00,2025-04-10 12:34:00
|
||||
long,1569.94,1562.91,-44.78,1800,timeout,2025-04-10 13:31:00,2025-04-10 14:01:00
|
||||
long,1495.60,1482.84,-85.32,1380,hard_sl,2025-04-10 15:58:00,2025-04-10 16:21:00
|
||||
long,1476.77,1490.04,89.86,360,tp,2025-04-10 16:22:00,2025-04-10 16:28:00
|
||||
short,1543.95,1535.76,53.05,1800,timeout,2025-04-11 01:33:00,2025-04-11 02:03:00
|
||||
short,1564.83,1551.22,86.97,840,tp,2025-04-11 08:05:00,2025-04-11 08:19:00
|
||||
short,1633.21,1642.17,-54.86,540,sl,2025-04-12 13:46:00,2025-04-12 13:55:00
|
||||
long,1571.30,1577.88,41.88,1800,timeout,2025-04-13 13:54:00,2025-04-13 14:24:00
|
||||
short,1627.31,1614.21,80.50,420,tp,2025-04-13 17:20:00,2025-04-13 17:27:00
|
||||
short,1598.70,1577.18,134.61,240,tp,2025-04-13 19:38:00,2025-04-13 19:42:00
|
||||
long,1585.85,1577.79,-50.82,1440,sl,2025-04-13 20:40:00,2025-04-13 21:04:00
|
||||
long,1574.79,1589.05,90.55,780,tp,2025-04-13 21:06:00,2025-04-13 21:19:00
|
||||
short,1625.38,1612.07,81.89,1740,tp,2025-04-14 00:22:00,2025-04-14 00:51:00
|
||||
short,1651.52,1638.05,81.56,360,tp,2025-04-14 02:35:00,2025-04-14 02:41:00
|
||||
long,1609.61,1626.80,106.80,1440,tp,2025-04-15 14:19:00,2025-04-15 14:43:00
|
||||
long,1570.35,1554.68,-99.79,180,hard_sl,2025-04-16 17:47:00,2025-04-16 17:50:00
|
||||
short,1550.77,1562.15,-73.38,480,sl,2025-04-16 18:02:00,2025-04-16 18:10:00
|
||||
short,1564.98,1575.27,-65.75,660,sl,2025-04-16 18:15:00,2025-04-16 18:26:00
|
||||
short,1591.24,1586.57,29.35,1800,timeout,2025-04-16 19:54:00,2025-04-16 20:24:00
|
||||
long,1578.32,1579.09,4.88,1800,timeout,2025-04-17 19:21:00,2025-04-17 19:51:00
|
||||
short,1611.84,1609.59,13.96,1800,timeout,2025-04-21 00:27:00,2025-04-21 00:57:00
|
||||
long,1575.91,1580.64,30.01,1800,timeout,2025-04-21 17:05:00,2025-04-21 17:35:00
|
||||
short,1618.28,1627.45,-56.67,1740,sl,2025-04-22 07:36:00,2025-04-22 08:05:00
|
||||
long,1709.80,1722.82,76.15,480,ai_rev,2025-04-22 15:08:00,2025-04-22 15:16:00
|
||||
short,1722.82,1709.03,80.04,240,tp,2025-04-22 15:16:00,2025-04-22 15:20:00
|
||||
short,1727.54,1712.26,88.45,300,tp,2025-04-22 21:22:00,2025-04-22 21:27:00
|
||||
short,1776.73,1751.09,144.31,240,tp,2025-04-22 21:49:00,2025-04-22 21:53:00
|
||||
short,1756.86,1755.02,10.47,1800,timeout,2025-04-22 21:54:00,2025-04-22 22:24:00
|
||||
short,1776.84,1770.76,34.22,1800,timeout,2025-04-23 01:09:00,2025-04-23 01:39:00
|
||||
short,1793.43,1807.45,-78.17,1620,hard_sl,2025-04-23 02:22:00,2025-04-23 02:49:00
|
||||
short,1816.18,1796.34,109.24,1080,tp,2025-04-23 04:59:00,2025-04-23 05:17:00
|
||||
long,1774.70,1765.52,-51.73,660,sl,2025-04-23 15:15:00,2025-04-23 15:26:00
|
||||
long,1728.60,1736.95,48.30,1800,timeout,2025-04-24 08:30:00,2025-04-24 09:00:00
|
||||
short,1823.50,1808.30,83.36,720,tp,2025-04-25 15:24:00,2025-04-25 15:36:00
|
||||
long,1806.95,1808.40,8.02,1800,timeout,2025-04-27 01:35:00,2025-04-27 02:05:00
|
||||
long,1773.72,1760.98,-71.83,1680,sl,2025-04-30 12:45:00,2025-04-30 13:13:00
|
||||
long,1739.97,1754.52,83.62,240,tp,2025-04-30 13:59:00,2025-04-30 14:03:00
|
||||
long,1806.19,1796.50,-53.65,1680,sl,2025-04-30 20:13:00,2025-04-30 20:41:00
|
||||
short,1854.37,1839.51,80.14,1500,tp,2025-05-01 11:00:00,2025-05-01 11:25:00
|
||||
short,1870.24,1858.76,61.38,1800,timeout,2025-05-01 15:14:00,2025-05-01 15:44:00
|
||||
long,1784.77,1787.56,15.63,1800,timeout,2025-05-05 01:31:00,2025-05-05 02:01:00
|
||||
short,1843.92,1837.99,32.16,1800,timeout,2025-05-07 00:22:00,2025-05-07 00:52:00
|
||||
short,1836.00,1820.53,84.26,720,tp,2025-05-08 01:03:00,2025-05-08 01:15:00
|
||||
short,1861.58,1871.03,-50.76,1320,sl,2025-05-08 03:18:00,2025-05-08 03:40:00
|
||||
short,1877.34,1892.65,-81.55,180,hard_sl,2025-05-08 03:42:00,2025-05-08 03:45:00
|
||||
short,1892.17,1905.39,-69.87,660,sl,2025-05-08 03:46:00,2025-05-08 03:57:00
|
||||
short,1938.47,1929.00,48.85,1800,timeout,2025-05-08 07:34:00,2025-05-08 08:04:00
|
||||
short,2020.95,2043.18,-110.00,780,hard_sl,2025-05-08 15:26:00,2025-05-08 15:39:00
|
||||
short,2070.40,2041.95,137.41,600,tp,2025-05-08 15:42:00,2025-05-08 15:52:00
|
||||
short,2043.86,2055.75,-58.17,240,sl,2025-05-08 15:56:00,2025-05-08 16:00:00
|
||||
short,2123.95,2118.65,24.95,240,ai_rev,2025-05-08 19:56:00,2025-05-08 20:00:00
|
||||
long,2118.65,2131.32,59.80,1800,timeout,2025-05-08 20:00:00,2025-05-08 20:30:00
|
||||
short,2136.39,2153.29,-79.11,480,hard_sl,2025-05-08 20:31:00,2025-05-08 20:39:00
|
||||
short,2191.51,2180.24,51.43,360,ai_rev,2025-05-08 20:55:00,2025-05-08 21:01:00
|
||||
long,2180.24,2159.65,-94.44,240,hard_sl,2025-05-08 21:01:00,2025-05-08 21:05:00
|
||||
short,2172.72,2185.26,-57.72,300,sl,2025-05-08 21:09:00,2025-05-08 21:14:00
|
||||
short,2190.89,2208.76,-81.57,120,hard_sl,2025-05-08 21:16:00,2025-05-08 21:18:00
|
||||
short,2219.46,2198.55,94.21,420,tp,2025-05-08 21:22:00,2025-05-08 21:29:00
|
||||
short,2206.14,2184.40,98.54,600,tp,2025-05-08 21:31:00,2025-05-08 21:41:00
|
||||
short,2232.58,2211.77,93.21,300,tp,2025-05-09 01:17:00,2025-05-09 01:22:00
|
||||
short,2257.25,2272.22,-66.32,1680,sl,2025-05-09 06:33:00,2025-05-09 07:01:00
|
||||
long,2294.93,2351.17,245.06,1260,tp,2025-05-09 07:21:00,2025-05-09 07:42:00
|
||||
short,2366.64,2385.06,-77.83,120,hard_sl,2025-05-09 07:46:00,2025-05-09 07:48:00
|
||||
short,2386.20,2366.95,80.67,540,tp,2025-05-09 07:49:00,2025-05-09 07:58:00
|
||||
long,2363.87,2383.38,82.53,540,tp,2025-05-09 07:59:00,2025-05-09 08:08:00
|
||||
short,2428.45,2451.07,-93.15,180,hard_sl,2025-05-09 08:28:00,2025-05-09 08:31:00
|
||||
short,2446.45,2470.99,-100.31,120,hard_sl,2025-05-09 08:32:00,2025-05-09 08:34:00
|
||||
short,2474.77,2425.68,198.36,300,tp,2025-05-09 08:35:00,2025-05-09 08:40:00
|
||||
short,2433.24,2395.42,155.43,240,tp,2025-05-09 08:41:00,2025-05-09 08:45:00
|
||||
short,2417.18,2387.14,124.28,1260,tp,2025-05-09 08:46:00,2025-05-09 09:07:00
|
||||
long,2366.26,2346.73,-82.54,60,hard_sl,2025-05-09 09:10:00,2025-05-09 09:11:00
|
||||
long,2341.11,2324.86,-69.41,660,sl,2025-05-09 09:15:00,2025-05-09 09:26:00
|
||||
long,2309.77,2336.05,113.78,360,tp,2025-05-09 09:27:00,2025-05-09 09:33:00
|
||||
short,2338.68,2351.99,-56.91,1020,sl,2025-05-09 11:27:00,2025-05-09 11:44:00
|
||||
short,2288.49,2302.06,-59.30,240,sl,2025-05-09 14:38:00,2025-05-09 14:42:00
|
||||
short,2409.27,2424.91,-64.92,540,sl,2025-05-10 07:57:00,2025-05-10 08:06:00
|
||||
short,2427.56,2403.99,97.09,360,tp,2025-05-10 08:07:00,2025-05-10 08:13:00
|
||||
short,2442.03,2421.76,83.00,1440,tp,2025-05-10 13:48:00,2025-05-10 14:12:00
|
||||
short,2466.18,2480.47,-57.94,1500,sl,2025-05-10 17:48:00,2025-05-10 18:13:00
|
||||
short,2561.10,2574.34,-51.70,720,sl,2025-05-10 22:41:00,2025-05-10 22:53:00
|
||||
short,2578.91,2593.90,-58.13,540,sl,2025-05-10 23:15:00,2025-05-10 23:24:00
|
||||
short,2603.38,2580.12,89.35,540,tp,2025-05-11 00:05:00,2025-05-11 00:14:00
|
||||
short,2531.29,2544.03,-50.33,240,sl,2025-05-11 00:30:00,2025-05-11 00:34:00
|
||||
short,2558.32,2537.07,83.06,1620,tp,2025-05-11 02:21:00,2025-05-11 02:48:00
|
||||
long,2522.23,2542.46,80.21,1140,tp,2025-05-11 06:05:00,2025-05-11 06:24:00
|
||||
long,2469.89,2490.53,83.57,1260,tp,2025-05-11 07:41:00,2025-05-11 08:02:00
|
||||
short,2549.49,2573.55,-94.37,660,hard_sl,2025-05-12 07:03:00,2025-05-12 07:14:00
|
||||
long,2563.06,2593.96,120.56,600,tp,2025-05-12 07:17:00,2025-05-12 07:27:00
|
||||
short,2614.10,2587.36,102.29,480,tp,2025-05-12 07:31:00,2025-05-12 07:39:00
|
||||
long,2562.06,2539.51,-88.02,300,hard_sl,2025-05-12 07:43:00,2025-05-12 07:48:00
|
||||
short,2548.72,2544.75,15.58,360,ai_rev,2025-05-12 07:49:00,2025-05-12 07:55:00
|
||||
long,2544.75,2540.95,-14.93,1800,timeout,2025-05-12 07:55:00,2025-05-12 08:25:00
|
||||
short,2495.33,2514.98,-78.75,120,hard_sl,2025-05-12 14:30:00,2025-05-12 14:32:00
|
||||
short,2515.24,2531.89,-66.20,360,sl,2025-05-12 14:34:00,2025-05-12 14:40:00
|
||||
long,2503.34,2488.27,-60.20,360,sl,2025-05-12 14:58:00,2025-05-12 15:04:00
|
||||
long,2452.38,2472.08,80.33,540,tp,2025-05-12 18:09:00,2025-05-12 18:18:00
|
||||
short,2411.86,2429.93,-74.92,240,sl,2025-05-12 18:49:00,2025-05-12 18:53:00
|
||||
short,2503.24,2524.46,-84.77,780,hard_sl,2025-05-13 11:24:00,2025-05-13 11:37:00
|
||||
short,2515.82,2531.09,-60.70,600,sl,2025-05-13 12:30:00,2025-05-13 12:40:00
|
||||
short,2645.42,2663.28,-67.51,1500,sl,2025-05-13 18:32:00,2025-05-13 18:57:00
|
||||
long,2684.93,2668.14,-62.53,420,sl,2025-05-13 19:58:00,2025-05-13 20:05:00
|
||||
long,2616.82,2632.54,60.07,1800,timeout,2025-05-14 08:08:00,2025-05-14 08:38:00
|
||||
long,2595.73,2580.49,-58.71,360,sl,2025-05-14 08:59:00,2025-05-14 09:05:00
|
||||
short,2633.30,2620.50,48.61,1800,timeout,2025-05-14 11:39:00,2025-05-14 12:09:00
|
||||
short,2575.14,2553.57,83.76,960,tp,2025-05-15 12:34:00,2025-05-15 12:50:00
|
||||
short,2535.69,2549.98,-56.36,960,sl,2025-05-15 15:30:00,2025-05-15 15:46:00
|
||||
short,2560.23,2551.27,35.00,1800,timeout,2025-05-15 22:20:00,2025-05-15 22:50:00
|
||||
long,2520.96,2545.52,97.42,1320,tp,2025-05-15 23:17:00,2025-05-15 23:39:00
|
||||
long,2526.79,2511.30,-61.30,900,sl,2025-05-17 00:01:00,2025-05-17 00:16:00
|
||||
long,2463.94,2472.28,33.85,1140,ai_rev,2025-05-18 18:08:00,2025-05-18 18:27:00
|
||||
short,2472.28,2456.15,65.24,1800,timeout,2025-05-18 18:27:00,2025-05-18 18:57:00
|
||||
long,2331.88,2364.70,140.74,240,tp,2025-05-18 20:00:00,2025-05-18 20:04:00
|
||||
long,2367.35,2384.01,70.37,1800,timeout,2025-05-18 20:05:00,2025-05-18 20:35:00
|
||||
long,2376.55,2397.28,87.23,240,tp,2025-05-18 22:17:00,2025-05-18 22:21:00
|
||||
short,2453.94,2450.89,12.43,1800,timeout,2025-05-18 22:59:00,2025-05-18 23:29:00
|
||||
short,2474.95,2498.38,-94.67,120,hard_sl,2025-05-18 23:52:00,2025-05-18 23:54:00
|
||||
short,2493.65,2511.56,-71.82,780,sl,2025-05-18 23:55:00,2025-05-19 00:08:00
|
||||
long,2498.76,2480.05,-74.88,240,sl,2025-05-19 00:09:00,2025-05-19 00:13:00
|
||||
long,2352.99,2372.12,81.30,660,tp,2025-05-19 04:54:00,2025-05-19 05:05:00
|
||||
long,2354.49,2378.93,103.80,480,tp,2025-05-19 06:57:00,2025-05-19 07:05:00
|
||||
short,2426.38,2440.24,-57.12,480,sl,2025-05-19 13:37:00,2025-05-19 13:45:00
|
||||
short,2536.71,2520.61,63.47,1800,timeout,2025-05-19 17:08:00,2025-05-19 17:38:00
|
||||
short,2553.00,2530.30,88.92,840,tp,2025-05-20 00:15:00,2025-05-20 00:29:00
|
||||
short,2538.07,2517.61,80.61,1380,tp,2025-05-20 23:18:00,2025-05-20 23:41:00
|
||||
short,2591.05,2594.93,-14.97,1800,timeout,2025-05-21 05:26:00,2025-05-21 05:56:00
|
||||
long,2527.32,2500.24,-107.15,240,hard_sl,2025-05-21 17:20:00,2025-05-21 17:24:00
|
||||
short,2496.89,2472.65,97.08,240,tp,2025-05-21 17:26:00,2025-05-21 17:30:00
|
||||
long,2473.92,2457.46,-66.53,540,sl,2025-05-21 17:45:00,2025-05-21 17:54:00
|
||||
long,2458.22,2482.83,100.11,540,tp,2025-05-21 17:57:00,2025-05-21 18:06:00
|
||||
short,2476.45,2489.34,-52.05,300,sl,2025-05-21 18:11:00,2025-05-21 18:16:00
|
||||
short,2559.75,2578.03,-71.41,240,sl,2025-05-21 23:21:00,2025-05-21 23:25:00
|
||||
short,2582.64,2561.14,83.25,420,tp,2025-05-21 23:26:00,2025-05-21 23:33:00
|
||||
short,2634.43,2622.76,44.30,1800,timeout,2025-05-22 03:42:00,2025-05-22 04:12:00
|
||||
long,2615.03,2606.15,-33.96,1800,timeout,2025-05-22 05:57:00,2025-05-22 06:27:00
|
||||
short,2672.80,2660.73,45.16,1800,timeout,2025-05-22 08:17:00,2025-05-22 08:47:00
|
||||
short,2726.46,2708.67,65.25,1800,timeout,2025-05-23 02:20:00,2025-05-23 02:50:00
|
||||
long,2610.50,2586.96,-90.17,120,hard_sl,2025-05-23 11:46:00,2025-05-23 11:48:00
|
||||
long,2562.10,2530.66,-122.71,240,hard_sl,2025-05-23 11:56:00,2025-05-23 12:00:00
|
||||
long,2554.63,2538.19,-64.35,660,sl,2025-05-23 12:01:00,2025-05-23 12:12:00
|
||||
long,2532.95,2553.26,80.18,1140,tp,2025-05-23 12:13:00,2025-05-23 12:32:00
|
||||
long,2551.68,2538.68,-50.95,1620,sl,2025-05-23 12:36:00,2025-05-23 13:03:00
|
||||
short,2578.18,2585.56,-28.62,1800,timeout,2025-05-23 13:33:00,2025-05-23 14:03:00
|
||||
long,2507.09,2529.31,88.63,1740,tp,2025-05-23 23:37:00,2025-05-24 00:06:00
|
||||
short,2672.26,2675.99,-13.96,1800,timeout,2025-05-28 23:14:00,2025-05-28 23:44:00
|
||||
short,2735.93,2766.30,-111.00,240,hard_sl,2025-05-29 02:26:00,2025-05-29 02:30:00
|
||||
short,2773.09,2765.78,26.36,1800,timeout,2025-05-29 02:31:00,2025-05-29 03:01:00
|
||||
short,2571.35,2585.23,-53.98,240,sl,2025-05-30 00:59:00,2025-05-30 01:03:00
|
||||
long,2584.22,2605.53,82.46,1260,tp,2025-05-30 01:05:00,2025-05-30 01:26:00
|
||||
short,2543.73,2544.68,-3.73,1800,timeout,2025-05-30 16:33:00,2025-05-30 17:03:00
|
||||
long,2615.97,2609.01,-26.61,1800,timeout,2025-06-03 06:58:00,2025-06-03 07:28:00
|
||||
long,2494.47,2481.66,-51.35,360,sl,2025-06-05 20:13:00,2025-06-05 20:19:00
|
||||
long,2449.89,2476.99,110.62,300,tp,2025-06-05 20:21:00,2025-06-05 20:26:00
|
||||
short,2481.07,2449.43,127.53,300,tp,2025-06-05 20:28:00,2025-06-05 20:33:00
|
||||
long,2448.85,2422.54,-107.44,360,hard_sl,2025-06-05 20:35:00,2025-06-05 20:41:00
|
||||
long,2431.10,2408.50,-92.96,660,hard_sl,2025-06-05 20:42:00,2025-06-05 20:53:00
|
||||
long,2408.17,2391.42,-69.55,420,sl,2025-06-05 20:54:00,2025-06-05 21:01:00
|
||||
long,2414.73,2430.58,65.64,1800,timeout,2025-06-05 21:02:00,2025-06-05 21:32:00
|
||||
short,2644.99,2657.01,-45.44,1800,timeout,2025-06-09 21:56:00,2025-06-09 22:26:00
|
||||
short,2689.91,2690.11,-0.74,1800,timeout,2025-06-09 23:44:00,2025-06-10 00:14:00
|
||||
short,2713.15,2735.12,-80.98,120,hard_sl,2025-06-10 11:15:00,2025-06-10 11:17:00
|
||||
short,2743.01,2759.37,-59.64,600,sl,2025-06-10 11:18:00,2025-06-10 11:28:00
|
||||
short,2807.70,2801.24,23.01,1800,timeout,2025-06-11 12:34:00,2025-06-11 13:04:00
|
||||
long,2778.81,2761.99,-60.53,360,sl,2025-06-11 21:57:00,2025-06-11 22:03:00
|
||||
long,2698.29,2683.62,-54.37,1500,sl,2025-06-12 19:49:00,2025-06-12 20:14:00
|
||||
long,2624.02,2610.75,-50.57,240,sl,2025-06-13 00:01:00,2025-06-13 00:05:00
|
||||
short,2562.82,2536.18,103.95,1260,tp,2025-06-13 00:11:00,2025-06-13 00:32:00
|
||||
long,2537.81,2512.25,-100.72,240,hard_sl,2025-06-13 00:48:00,2025-06-13 00:52:00
|
||||
long,2517.25,2504.01,-52.60,660,sl,2025-06-13 00:53:00,2025-06-13 01:04:00
|
||||
long,2503.40,2478.19,-100.70,300,hard_sl,2025-06-13 01:05:00,2025-06-13 01:10:00
|
||||
long,2469.59,2492.16,91.39,300,tp,2025-06-13 01:11:00,2025-06-13 01:16:00
|
||||
long,2498.90,2480.20,-74.83,300,sl,2025-06-13 01:26:00,2025-06-13 01:31:00
|
||||
long,2482.60,2502.95,81.97,840,tp,2025-06-13 01:32:00,2025-06-13 01:46:00
|
||||
long,2496.56,2481.15,-61.72,420,sl,2025-06-13 01:59:00,2025-06-13 02:06:00
|
||||
long,2484.22,2469.25,-60.26,780,sl,2025-06-13 02:11:00,2025-06-13 02:24:00
|
||||
short,2640.19,2636.67,13.33,1800,timeout,2025-06-16 13:58:00,2025-06-16 14:28:00
|
||||
long,2558.33,2565.68,28.73,660,ai_rev,2025-06-16 22:44:00,2025-06-16 22:55:00
|
||||
short,2565.68,2581.67,-62.32,780,sl,2025-06-16 22:55:00,2025-06-16 23:08:00
|
||||
long,2569.68,2562.41,-28.29,1800,timeout,2025-06-16 23:19:00,2025-06-16 23:49:00
|
||||
long,2553.60,2539.40,-55.61,420,sl,2025-06-16 23:50:00,2025-06-16 23:57:00
|
||||
long,2537.81,2559.53,85.59,1440,tp,2025-06-16 23:58:00,2025-06-17 00:22:00
|
||||
long,2472.13,2502.98,124.79,1020,tp,2025-06-18 18:56:00,2025-06-18 19:13:00
|
||||
short,2416.54,2393.58,95.01,360,tp,2025-06-20 17:29:00,2025-06-20 17:35:00
|
||||
short,2381.41,2403.97,-94.73,180,hard_sl,2025-06-20 17:40:00,2025-06-20 17:43:00
|
||||
short,2410.00,2422.52,-51.95,1380,sl,2025-06-20 17:44:00,2025-06-20 18:07:00
|
||||
long,2308.28,2338.18,129.53,240,tp,2025-06-21 21:24:00,2025-06-21 21:28:00
|
||||
long,2250.63,2285.08,153.07,240,tp,2025-06-21 21:31:00,2025-06-21 21:35:00
|
||||
long,2285.03,2289.20,18.25,1800,timeout,2025-06-21 21:36:00,2025-06-21 22:06:00
|
||||
long,2295.10,2295.39,1.26,1800,timeout,2025-06-21 22:07:00,2025-06-21 22:37:00
|
||||
long,2263.49,2246.60,-74.62,240,sl,2025-06-21 23:26:00,2025-06-21 23:30:00
|
||||
long,2254.83,2227.71,-120.28,180,hard_sl,2025-06-21 23:31:00,2025-06-21 23:34:00
|
||||
long,2245.86,2233.83,-53.57,360,sl,2025-06-21 23:35:00,2025-06-21 23:41:00
|
||||
long,2239.09,2226.00,-58.46,240,sl,2025-06-21 23:43:00,2025-06-21 23:47:00
|
||||
long,2291.17,2269.31,-95.41,120,hard_sl,2025-06-21 23:49:00,2025-06-21 23:51:00
|
||||
short,2291.81,2304.67,-56.11,240,sl,2025-06-21 23:56:00,2025-06-22 00:00:00
|
||||
short,2309.96,2291.18,81.30,240,tp,2025-06-22 00:03:00,2025-06-22 00:07:00
|
||||
short,2279.14,2296.75,-77.27,1080,hard_sl,2025-06-22 00:15:00,2025-06-22 00:33:00
|
||||
short,2307.07,2286.99,87.04,1620,tp,2025-06-22 00:35:00,2025-06-22 01:02:00
|
||||
long,2264.96,2266.00,4.59,1800,timeout,2025-06-22 01:58:00,2025-06-22 02:28:00
|
||||
short,2196.34,2200.51,-18.99,1800,timeout,2025-06-22 13:31:00,2025-06-22 14:01:00
|
||||
long,2186.16,2203.69,80.19,360,tp,2025-06-22 14:45:00,2025-06-22 14:51:00
|
||||
long,2126.29,2144.44,85.36,960,tp,2025-06-22 20:18:00,2025-06-22 20:34:00
|
||||
long,2183.71,2177.86,-26.79,1800,timeout,2025-06-22 20:58:00,2025-06-22 21:28:00
|
||||
short,2289.39,2300.87,-50.14,900,sl,2025-06-23 13:45:00,2025-06-23 14:00:00
|
||||
short,2210.80,2189.66,95.62,360,tp,2025-06-23 16:30:00,2025-06-23 16:36:00
|
||||
long,2217.43,2236.31,85.14,840,tp,2025-06-23 16:51:00,2025-06-23 17:05:00
|
||||
short,2308.37,2302.24,26.56,1800,timeout,2025-06-23 18:35:00,2025-06-23 19:05:00
|
||||
short,2365.38,2345.80,82.78,1380,tp,2025-06-23 20:24:00,2025-06-23 20:47:00
|
||||
short,2396.45,2411.86,-64.30,600,sl,2025-06-23 22:12:00,2025-06-23 22:22:00
|
||||
short,2426.27,2405.82,84.29,660,tp,2025-06-23 22:24:00,2025-06-23 22:35:00
|
||||
short,2411.91,2419.88,-33.04,1800,timeout,2025-06-23 22:58:00,2025-06-23 23:28:00
|
||||
long,2391.77,2377.67,-58.95,1200,sl,2025-06-24 07:57:00,2025-06-24 08:17:00
|
||||
short,2468.22,2469.77,-6.28,1800,timeout,2025-06-26 01:35:00,2025-06-26 02:05:00
|
||||
short,2510.44,2489.88,81.90,1620,tp,2025-06-26 03:03:00,2025-06-26 03:30:00
|
||||
long,2492.64,2512.78,80.80,960,tp,2025-06-29 22:45:00,2025-06-29 23:01:00
|
||||
long,2595.19,2595.23,0.15,1800,timeout,2025-07-06 21:44:00,2025-07-06 22:14:00
|
||||
short,2704.50,2719.28,-54.65,1080,sl,2025-07-09 18:10:00,2025-07-09 18:28:00
|
||||
short,2763.88,2740.07,86.15,1560,tp,2025-07-09 19:59:00,2025-07-09 20:25:00
|
||||
short,2868.45,2904.29,-124.95,60,hard_sl,2025-07-10 21:22:00,2025-07-10 21:23:00
|
||||
short,2904.92,2927.35,-77.21,960,hard_sl,2025-07-10 21:24:00,2025-07-10 21:40:00
|
||||
short,2932.30,2979.65,-161.48,60,hard_sl,2025-07-10 21:41:00,2025-07-10 21:42:00
|
||||
short,2962.54,2983.66,-71.29,360,sl,2025-07-10 21:43:00,2025-07-10 21:49:00
|
||||
long,2967.90,2993.29,85.55,720,tp,2025-07-15 14:56:00,2025-07-15 15:08:00
|
||||
long,3034.34,3044.21,32.53,1800,timeout,2025-07-15 19:48:00,2025-07-15 20:18:00
|
||||
long,3317.47,3346.70,88.11,1020,tp,2025-07-17 01:48:00,2025-07-17 02:05:00
|
||||
short,3425.68,3431.71,-17.60,1800,timeout,2025-07-17 06:28:00,2025-07-17 06:58:00
|
||||
short,3441.11,3412.75,82.42,600,tp,2025-07-17 06:59:00,2025-07-17 07:09:00
|
||||
long,3418.91,3427.75,25.86,1800,timeout,2025-07-17 14:06:00,2025-07-17 14:36:00
|
||||
short,3483.99,3452.68,89.87,1260,tp,2025-07-17 21:14:00,2025-07-17 21:35:00
|
||||
short,3526.96,3547.93,-59.46,1500,sl,2025-07-18 00:45:00,2025-07-18 01:10:00
|
||||
short,3572.23,3591.06,-52.71,1380,sl,2025-07-18 01:12:00,2025-07-18 01:35:00
|
||||
short,3595.27,3613.43,-50.51,1020,sl,2025-07-18 01:37:00,2025-07-18 01:54:00
|
||||
short,3614.88,3613.98,2.49,1800,timeout,2025-07-18 01:55:00,2025-07-18 02:25:00
|
||||
short,3595.95,3560.66,98.14,1020,tp,2025-07-18 14:24:00,2025-07-18 14:41:00
|
||||
short,3682.26,3655.16,73.60,1800,timeout,2025-07-20 03:35:00,2025-07-20 04:05:00
|
||||
long,3654.75,3690.67,98.28,840,tp,2025-07-22 04:59:00,2025-07-22 05:13:00
|
||||
long,3626.83,3651.30,67.47,1800,timeout,2025-07-22 08:26:00,2025-07-22 08:56:00
|
||||
long,3655.32,3636.01,-52.83,660,sl,2025-07-22 13:51:00,2025-07-22 14:02:00
|
||||
long,3580.01,3601.55,60.17,1800,timeout,2025-07-23 21:44:00,2025-07-23 22:14:00
|
||||
long,3614.28,3618.65,12.09,1800,timeout,2025-07-25 02:59:00,2025-07-25 03:29:00
|
||||
long,3658.41,3637.72,-56.55,360,sl,2025-07-25 14:34:00,2025-07-25 14:40:00
|
||||
long,3754.11,3728.80,-67.42,600,sl,2025-07-30 18:50:00,2025-07-30 19:00:00
|
||||
long,3761.59,3773.45,31.53,1800,timeout,2025-07-30 19:58:00,2025-07-30 20:28:00
|
||||
long,3579.95,3573.22,-18.80,1800,timeout,2025-08-01 16:56:00,2025-08-01 17:26:00
|
||||
long,3538.80,3568.06,82.68,600,tp,2025-08-01 18:16:00,2025-08-01 18:26:00
|
||||
long,3481.02,3457.65,-67.14,360,sl,2025-08-01 22:27:00,2025-08-01 22:33:00
|
||||
long,3443.99,3473.50,85.69,1020,tp,2025-08-01 22:41:00,2025-08-01 22:58:00
|
||||
long,3413.34,3413.76,1.23,1800,timeout,2025-08-02 17:52:00,2025-08-02 18:22:00
|
||||
long,3393.55,3376.26,-50.95,1260,sl,2025-08-02 18:27:00,2025-08-02 18:48:00
|
||||
long,3368.11,3397.80,88.15,1080,tp,2025-08-02 23:21:00,2025-08-02 23:39:00
|
||||
long,3647.09,3643.86,-8.86,1800,timeout,2025-08-04 14:12:00,2025-08-04 14:42:00
|
||||
short,3801.99,3821.23,-50.61,1080,sl,2025-08-07 10:37:00,2025-08-07 10:55:00
|
||||
short,3818.10,3818.27,-0.45,1800,timeout,2025-08-07 11:00:00,2025-08-07 11:30:00
|
||||
short,4009.42,3997.34,30.13,420,ai_rev,2025-08-08 13:53:00,2025-08-08 14:00:00
|
||||
long,3997.34,3959.66,-94.26,720,hard_sl,2025-08-08 14:00:00,2025-08-08 14:12:00
|
||||
long,4147.81,4183.03,84.91,1560,tp,2025-08-09 05:05:00,2025-08-09 05:31:00
|
||||
short,4298.01,4261.41,85.16,300,tp,2025-08-11 13:47:00,2025-08-11 13:52:00
|
||||
short,4376.22,4413.32,-84.78,180,hard_sl,2025-08-12 12:39:00,2025-08-12 12:42:00
|
||||
short,4409.50,4401.49,18.17,1800,timeout,2025-08-12 12:43:00,2025-08-12 13:13:00
|
||||
long,4604.46,4565.36,-84.92,240,hard_sl,2025-08-14 12:52:00,2025-08-14 12:56:00
|
||||
long,4567.41,4535.99,-68.79,240,sl,2025-08-14 12:57:00,2025-08-14 13:01:00
|
||||
long,4539.07,4489.01,-110.29,120,hard_sl,2025-08-14 13:02:00,2025-08-14 13:04:00
|
||||
long,4502.97,4539.15,80.35,240,tp,2025-08-14 13:05:00,2025-08-14 13:09:00
|
||||
long,4520.47,4565.93,100.56,780,tp,2025-08-14 13:23:00,2025-08-14 13:36:00
|
||||
long,4541.51,4516.38,-55.33,960,sl,2025-08-14 16:14:00,2025-08-14 16:30:00
|
||||
long,4466.47,4473.83,16.48,1800,timeout,2025-08-14 21:35:00,2025-08-14 22:05:00
|
||||
long,4458.37,4433.51,-55.76,420,sl,2025-08-15 15:21:00,2025-08-15 15:28:00
|
||||
long,4416.41,4393.92,-50.92,1380,sl,2025-08-15 15:43:00,2025-08-15 16:06:00
|
||||
short,4535.00,4547.73,-28.07,1800,timeout,2025-08-17 08:57:00,2025-08-17 09:27:00
|
||||
long,4352.13,4374.95,52.43,1800,timeout,2025-08-18 02:09:00,2025-08-18 02:39:00
|
||||
long,4203.33,4186.90,-39.09,1800,timeout,2025-08-19 14:30:00,2025-08-19 15:00:00
|
||||
long,4182.28,4219.19,88.25,1560,tp,2025-08-19 15:01:00,2025-08-19 15:27:00
|
||||
long,4070.69,4089.51,46.23,1800,timeout,2025-08-19 23:52:00,2025-08-20 00:22:00
|
||||
long,4128.26,4105.72,-54.60,1380,sl,2025-08-20 13:53:00,2025-08-20 14:16:00
|
||||
short,4302.87,4267.09,83.15,1680,tp,2025-08-20 15:24:00,2025-08-20 15:52:00
|
||||
short,4416.35,4451.00,-78.46,180,hard_sl,2025-08-22 14:02:00,2025-08-22 14:05:00
|
||||
short,4474.89,4498.39,-52.52,300,sl,2025-08-22 14:06:00,2025-08-22 14:11:00
|
||||
short,4534.32,4572.59,-84.40,60,hard_sl,2025-08-22 14:12:00,2025-08-22 14:13:00
|
||||
short,4591.47,4637.62,-100.51,120,hard_sl,2025-08-22 14:14:00,2025-08-22 14:16:00
|
||||
short,4596.88,4540.81,121.97,300,tp,2025-08-22 14:17:00,2025-08-22 14:22:00
|
||||
short,4550.80,4576.16,-55.73,360,sl,2025-08-22 14:23:00,2025-08-22 14:29:00
|
||||
short,4592.41,4618.23,-56.22,240,sl,2025-08-22 14:30:00,2025-08-22 14:34:00
|
||||
short,4611.97,4635.55,-51.13,900,sl,2025-08-22 14:35:00,2025-08-22 14:50:00
|
||||
short,4648.51,4654.93,-13.81,1800,timeout,2025-08-22 15:18:00,2025-08-22 15:48:00
|
||||
short,4732.59,4764.85,-68.17,300,sl,2025-08-22 16:09:00,2025-08-22 16:14:00
|
||||
short,4749.00,4747.00,4.21,1800,timeout,2025-08-22 16:15:00,2025-08-22 16:45:00
|
||||
long,4745.44,4794.35,103.07,900,tp,2025-08-22 16:58:00,2025-08-22 17:13:00
|
||||
long,4817.96,4788.01,-62.16,1200,sl,2025-08-22 17:22:00,2025-08-22 17:42:00
|
||||
long,4713.61,4686.27,-58.00,240,sl,2025-08-23 02:28:00,2025-08-23 02:32:00
|
||||
long,4673.33,4711.39,81.44,780,tp,2025-08-23 02:34:00,2025-08-23 02:47:00
|
||||
short,4849.59,4804.73,92.50,360,tp,2025-08-24 19:35:00,2025-08-24 19:41:00
|
||||
short,4813.91,4772.93,85.13,600,tp,2025-08-24 19:42:00,2025-08-24 19:52:00
|
||||
short,4799.50,4745.04,113.47,840,tp,2025-08-24 19:53:00,2025-08-24 20:07:00
|
||||
long,4725.56,4764.67,82.76,480,tp,2025-08-24 20:08:00,2025-08-24 20:16:00
|
||||
long,4523.72,4490.00,-74.54,840,sl,2025-08-25 19:17:00,2025-08-25 19:31:00
|
||||
short,4465.77,4418.32,106.25,1200,tp,2025-08-25 19:39:00,2025-08-25 19:59:00
|
||||
short,4427.38,4390.60,83.07,600,tp,2025-08-25 20:00:00,2025-08-25 20:10:00
|
||||
long,4380.84,4417.73,84.21,900,tp,2025-08-25 20:11:00,2025-08-25 20:26:00
|
||||
long,4392.71,4356.83,-81.68,120,hard_sl,2025-08-25 20:31:00,2025-08-25 20:33:00
|
||||
short,4344.65,4382.93,-88.11,120,hard_sl,2025-08-25 20:39:00,2025-08-25 20:41:00
|
||||
long,4355.57,4364.69,20.94,1800,timeout,2025-08-25 20:50:00,2025-08-25 21:20:00
|
||||
short,4519.99,4479.90,88.69,720,tp,2025-08-26 12:41:00,2025-08-26 12:53:00
|
||||
short,4620.77,4603.12,38.20,1800,timeout,2025-08-27 13:43:00,2025-08-27 14:13:00
|
||||
short,4451.32,4415.43,80.63,1620,tp,2025-08-29 06:35:00,2025-08-29 07:02:00
|
||||
long,4318.86,4265.84,-122.76,180,hard_sl,2025-08-29 14:03:00,2025-08-29 14:06:00
|
||||
long,4382.10,4388.01,13.49,1800,timeout,2025-08-31 23:40:00,2025-09-01 00:10:00
|
||||
short,4439.41,4461.85,-50.55,1380,sl,2025-09-05 12:31:00,2025-09-05 12:54:00
|
||||
long,4346.09,4315.04,-71.44,600,sl,2025-09-05 14:18:00,2025-09-05 14:28:00
|
||||
long,4320.16,4285.95,-79.19,300,hard_sl,2025-09-05 14:30:00,2025-09-05 14:35:00
|
||||
long,4277.57,4280.65,7.20,1800,timeout,2025-09-05 14:36:00,2025-09-05 15:06:00
|
||||
long,4713.65,4689.99,-50.19,1260,sl,2025-09-12 22:24:00,2025-09-12 22:45:00
|
||||
long,4360.24,4334.16,-59.81,780,sl,2025-09-22 00:36:00,2025-09-22 00:49:00
|
||||
long,4324.93,4331.11,14.29,1800,timeout,2025-09-22 00:50:00,2025-09-22 01:20:00
|
||||
long,4088.35,4137.95,121.32,240,tp,2025-09-22 06:02:00,2025-09-22 06:06:00
|
||||
short,4151.57,4173.10,-51.86,420,sl,2025-09-22 06:09:00,2025-09-22 06:16:00
|
||||
long,3824.15,3859.52,92.49,1020,tp,2025-09-25 17:57:00,2025-09-25 18:14:00
|
||||
long,3877.02,3899.52,58.03,1800,timeout,2025-09-25 18:20:00,2025-09-25 18:50:00
|
||||
long,3932.29,3931.24,-2.67,1800,timeout,2025-09-26 12:32:00,2025-09-26 13:02:00
|
||||
long,4108.06,4087.49,-50.07,420,sl,2025-09-28 22:06:00,2025-09-28 22:13:00
|
||||
short,4244.24,4279.10,-82.13,180,hard_sl,2025-10-01 08:42:00,2025-10-01 08:45:00
|
||||
short,4275.39,4308.84,-78.24,240,hard_sl,2025-10-01 08:46:00,2025-10-01 08:50:00
|
||||
short,4309.78,4279.99,69.12,1800,timeout,2025-10-01 08:51:00,2025-10-01 09:21:00
|
||||
long,4343.61,4382.38,89.26,360,tp,2025-10-02 15:04:00,2025-10-02 15:10:00
|
||||
long,4484.81,4501.44,37.08,1800,timeout,2025-10-03 17:04:00,2025-10-03 17:34:00
|
||||
long,4462.01,4501.38,88.23,1500,tp,2025-10-03 18:01:00,2025-10-03 18:26:00
|
||||
long,4694.60,4668.18,-56.28,1560,sl,2025-10-07 13:42:00,2025-10-07 14:08:00
|
||||
long,4543.57,4520.09,-51.68,1800,sl,2025-10-07 15:07:00,2025-10-07 15:37:00
|
||||
short,4510.61,4469.49,91.16,1740,tp,2025-10-07 15:53:00,2025-10-07 16:22:00
|
||||
long,4521.15,4494.05,-59.94,600,sl,2025-10-08 14:03:00,2025-10-08 14:13:00
|
||||
long,4262.74,4240.02,-53.30,840,sl,2025-10-10 15:03:00,2025-10-10 15:17:00
|
||||
long,4216.76,4181.93,-82.60,240,hard_sl,2025-10-10 15:20:00,2025-10-10 15:24:00
|
||||
short,4123.12,4103.66,47.20,1800,timeout,2025-10-10 15:46:00,2025-10-10 16:16:00
|
||||
long,4091.19,4093.27,5.08,1800,timeout,2025-10-10 17:14:00,2025-10-10 17:44:00
|
||||
long,4033.95,4013.40,-50.94,600,sl,2025-10-10 19:10:00,2025-10-10 19:20:00
|
||||
short,3971.74,4008.28,-92.00,420,hard_sl,2025-10-10 19:34:00,2025-10-10 19:41:00
|
||||
long,3856.21,3906.95,131.58,240,tp,2025-10-10 21:00:00,2025-10-10 21:04:00
|
||||
long,3903.06,3880.67,-57.37,300,sl,2025-10-10 21:05:00,2025-10-10 21:10:00
|
||||
long,3851.18,3785.26,-171.17,60,hard_sl,2025-10-10 21:11:00,2025-10-10 21:12:00
|
||||
long,3741.28,3558.97,-487.29,180,hard_sl,2025-10-10 21:13:00,2025-10-10 21:16:00
|
||||
long,3621.54,3530.79,-250.58,60,hard_sl,2025-10-10 21:17:00,2025-10-10 21:18:00
|
||||
long,3510.46,3665.37,441.28,240,tp,2025-10-10 21:19:00,2025-10-10 21:23:00
|
||||
long,3718.67,3749.50,82.91,240,tp,2025-10-10 21:37:00,2025-10-10 21:41:00
|
||||
long,3778.96,3840.33,162.40,360,tp,2025-10-10 21:43:00,2025-10-10 21:49:00
|
||||
long,3920.43,3874.38,-117.46,180,hard_sl,2025-10-10 21:51:00,2025-10-10 21:54:00
|
||||
short,3952.89,3916.12,93.02,300,tp,2025-10-10 21:56:00,2025-10-10 22:01:00
|
||||
long,3884.86,3833.78,-131.48,180,hard_sl,2025-10-10 22:02:00,2025-10-10 22:05:00
|
||||
long,3856.56,3892.70,93.71,300,tp,2025-10-10 22:06:00,2025-10-10 22:11:00
|
||||
long,3891.07,3868.11,-59.01,600,sl,2025-10-10 22:12:00,2025-10-10 22:22:00
|
||||
long,3841.99,3875.90,88.26,480,tp,2025-10-10 22:23:00,2025-10-10 22:31:00
|
||||
long,3857.01,3888.32,81.18,900,tp,2025-10-10 22:33:00,2025-10-10 22:48:00
|
||||
long,3811.59,3842.77,81.80,480,tp,2025-10-10 23:10:00,2025-10-10 23:18:00
|
||||
long,3838.70,3871.00,84.14,720,tp,2025-10-10 23:19:00,2025-10-10 23:31:00
|
||||
long,3817.18,3847.99,80.71,480,tp,2025-10-10 23:55:00,2025-10-11 00:03:00
|
||||
long,3821.60,3857.97,95.17,1320,tp,2025-10-11 00:24:00,2025-10-11 00:46:00
|
||||
long,3800.78,3777.36,-61.62,240,sl,2025-10-11 01:15:00,2025-10-11 01:19:00
|
||||
long,3758.95,3791.61,86.89,840,tp,2025-10-11 01:24:00,2025-10-11 01:38:00
|
||||
long,3768.95,3748.00,-55.59,240,sl,2025-10-11 01:40:00,2025-10-11 01:44:00
|
||||
long,3763.45,3803.30,105.89,720,tp,2025-10-11 02:11:00,2025-10-11 02:23:00
|
||||
long,3793.28,3767.55,-67.83,720,sl,2025-10-11 03:09:00,2025-10-11 03:21:00
|
||||
long,3744.46,3723.53,-55.90,1020,sl,2025-10-11 20:05:00,2025-10-11 20:22:00
|
||||
short,3693.71,3666.39,73.96,300,ai_rev,2025-10-11 20:59:00,2025-10-11 21:04:00
|
||||
long,3666.39,3709.18,116.71,1080,tp,2025-10-11 21:04:00,2025-10-11 21:22:00
|
||||
long,3727.56,3708.27,-51.75,360,sl,2025-10-11 21:27:00,2025-10-11 21:33:00
|
||||
short,3987.08,4014.18,-67.97,240,sl,2025-10-12 14:51:00,2025-10-12 14:55:00
|
||||
short,4006.54,3975.78,76.77,240,ai_rev,2025-10-12 14:56:00,2025-10-12 15:00:00
|
||||
long,3975.78,4012.19,91.58,300,tp,2025-10-12 15:00:00,2025-10-12 15:05:00
|
||||
long,4026.24,4073.94,118.47,720,tp,2025-10-12 15:07:00,2025-10-12 15:19:00
|
||||
long,4048.91,4012.82,-89.14,240,hard_sl,2025-10-12 15:28:00,2025-10-12 15:32:00
|
||||
long,3985.93,4023.48,94.21,600,tp,2025-10-12 15:35:00,2025-10-12 15:45:00
|
||||
long,4109.32,4144.13,84.71,420,tp,2025-10-12 17:06:00,2025-10-12 17:13:00
|
||||
long,4117.98,4090.33,-67.14,900,sl,2025-10-13 11:12:00,2025-10-13 11:27:00
|
||||
long,4073.67,4080.99,17.97,1800,timeout,2025-10-13 11:28:00,2025-10-13 11:58:00
|
||||
long,4063.60,4104.54,100.75,540,tp,2025-10-13 12:01:00,2025-10-13 12:10:00
|
||||
long,4124.94,4100.97,-58.11,1080,sl,2025-10-13 12:32:00,2025-10-13 12:50:00
|
||||
long,4167.52,4137.99,-70.86,240,sl,2025-10-13 13:31:00,2025-10-13 13:35:00
|
||||
short,4157.02,4120.89,86.91,1680,tp,2025-10-13 13:53:00,2025-10-13 14:21:00
|
||||
long,3991.94,4002.99,27.68,1800,timeout,2025-10-14 06:44:00,2025-10-14 07:14:00
|
||||
long,3951.48,3965.81,36.26,1800,timeout,2025-10-14 11:01:00,2025-10-14 11:31:00
|
||||
short,3924.49,3949.74,-64.34,540,sl,2025-10-14 13:36:00,2025-10-14 13:45:00
|
||||
short,3957.56,3982.00,-61.76,1260,sl,2025-10-14 13:53:00,2025-10-14 14:14:00
|
||||
short,4068.65,4097.32,-70.47,960,sl,2025-10-14 15:56:00,2025-10-14 16:12:00
|
||||
long,4094.14,4047.69,-113.45,240,hard_sl,2025-10-14 16:57:00,2025-10-14 17:01:00
|
||||
long,4067.42,4110.97,107.07,240,tp,2025-10-14 17:02:00,2025-10-14 17:06:00
|
||||
long,4125.88,4098.53,-66.29,1080,sl,2025-10-14 17:16:00,2025-10-14 17:34:00
|
||||
short,4100.35,4104.81,-10.88,1800,timeout,2025-10-14 19:59:00,2025-10-14 20:29:00
|
||||
long,4021.01,4054.99,84.51,420,tp,2025-10-15 13:48:00,2025-10-15 13:55:00
|
||||
long,3999.35,4011.88,31.33,1800,timeout,2025-10-15 15:00:00,2025-10-15 15:30:00
|
||||
long,4029.58,4064.69,87.13,780,tp,2025-10-16 10:00:00,2025-10-16 10:13:00
|
||||
long,3887.40,3858.91,-73.29,1800,sl,2025-10-16 16:00:00,2025-10-16 16:30:00
|
||||
long,3884.56,3906.38,56.17,1800,timeout,2025-10-16 18:14:00,2025-10-16 18:44:00
|
||||
long,3790.33,3765.53,-65.43,1560,sl,2025-10-17 07:06:00,2025-10-17 07:32:00
|
||||
long,3777.80,3758.09,-52.17,1320,sl,2025-10-17 07:34:00,2025-10-17 07:56:00
|
||||
long,3681.41,3711.02,80.43,1560,tp,2025-10-17 10:23:00,2025-10-17 10:49:00
|
||||
short,3797.19,3754.67,111.98,360,tp,2025-10-17 13:37:00,2025-10-17 13:43:00
|
||||
long,3751.84,3788.20,96.91,480,tp,2025-10-17 13:44:00,2025-10-17 13:52:00
|
||||
short,3796.88,3757.60,103.45,960,tp,2025-10-17 13:53:00,2025-10-17 14:09:00
|
||||
long,3841.58,3873.12,82.10,240,tp,2025-10-19 08:25:00,2025-10-19 08:29:00
|
||||
long,3865.87,3857.64,-21.29,1800,timeout,2025-10-21 04:11:00,2025-10-21 04:41:00
|
||||
short,4019.64,4041.11,-53.41,780,sl,2025-10-21 14:59:00,2025-10-21 15:12:00
|
||||
long,4050.01,4029.57,-50.47,360,sl,2025-10-21 16:56:00,2025-10-21 17:02:00
|
||||
long,3996.64,3972.86,-59.50,300,sl,2025-10-21 17:17:00,2025-10-21 17:22:00
|
||||
long,3980.51,3991.16,26.76,1800,timeout,2025-10-21 18:13:00,2025-10-21 18:43:00
|
||||
long,3922.53,3947.00,62.38,1800,timeout,2025-10-21 21:22:00,2025-10-21 21:52:00
|
||||
long,3896.93,3902.34,13.88,1800,timeout,2025-10-21 22:38:00,2025-10-21 23:08:00
|
||||
long,3837.36,3842.08,12.30,1800,timeout,2025-10-22 00:34:00,2025-10-22 01:04:00
|
||||
long,3779.73,3811.07,82.92,480,tp,2025-10-22 11:03:00,2025-10-22 11:11:00
|
||||
long,3709.23,3747.54,103.28,300,tp,2025-10-22 21:11:00,2025-10-22 21:16:00
|
||||
long,3736.46,3751.16,39.34,1800,timeout,2025-10-22 21:23:00,2025-10-22 21:53:00
|
||||
long,3997.23,3976.04,-53.01,420,sl,2025-10-24 12:30:00,2025-10-24 12:37:00
|
||||
long,4165.46,4123.58,-100.54,180,hard_sl,2025-10-26 22:03:00,2025-10-26 22:06:00
|
||||
long,4053.18,4027.81,-62.59,1200,sl,2025-10-28 19:30:00,2025-10-28 19:50:00
|
||||
long,3983.57,3975.00,-21.51,1800,timeout,2025-10-28 20:19:00,2025-10-28 20:49:00
|
||||
long,3939.44,3974.43,88.82,1620,tp,2025-10-28 21:07:00,2025-10-28 21:34:00
|
||||
long,3929.07,3853.41,-192.56,180,hard_sl,2025-10-29 18:36:00,2025-10-29 18:39:00
|
||||
short,3889.25,3910.96,-55.82,300,sl,2025-10-29 18:45:00,2025-10-29 18:50:00
|
||||
short,3916.11,3937.40,-54.37,240,sl,2025-10-29 18:51:00,2025-10-29 18:55:00
|
||||
short,3924.64,3947.68,-58.71,660,sl,2025-10-29 18:56:00,2025-10-29 19:07:00
|
||||
long,3943.58,3918.70,-63.09,1320,sl,2025-10-29 19:09:00,2025-10-29 19:31:00
|
||||
long,3886.13,3920.82,89.27,780,tp,2025-10-29 20:10:00,2025-10-29 20:23:00
|
||||
long,3824.17,3802.61,-56.38,540,sl,2025-10-30 12:41:00,2025-10-30 12:50:00
|
||||
long,3767.76,3777.31,25.35,1800,timeout,2025-10-30 13:57:00,2025-10-30 14:27:00
|
||||
long,3686.84,3717.10,82.08,840,tp,2025-10-30 20:03:00,2025-10-30 20:17:00
|
||||
long,3597.74,3628.94,86.72,540,tp,2025-11-03 15:30:00,2025-11-03 15:39:00
|
||||
short,3619.21,3581.21,105.00,480,tp,2025-11-03 15:40:00,2025-11-03 15:48:00
|
||||
short,3596.58,3566.03,84.94,660,tp,2025-11-03 15:53:00,2025-11-03 16:04:00
|
||||
long,3571.48,3595.72,67.87,240,ai_rev,2025-11-03 16:08:00,2025-11-03 16:12:00
|
||||
short,3595.72,3620.00,-67.52,420,sl,2025-11-03 16:12:00,2025-11-03 16:19:00
|
||||
short,3611.08,3620.40,-25.81,1800,timeout,2025-11-04 00:37:00,2025-11-04 01:07:00
|
||||
long,3541.54,3514.04,-77.65,60,hard_sl,2025-11-04 05:30:00,2025-11-04 05:31:00
|
||||
long,3526.01,3503.78,-63.05,960,sl,2025-11-04 05:32:00,2025-11-04 05:48:00
|
||||
long,3483.82,3519.47,102.33,1080,tp,2025-11-04 05:56:00,2025-11-04 06:14:00
|
||||
short,3517.61,3542.95,-72.04,300,sl,2025-11-04 14:43:00,2025-11-04 14:48:00
|
||||
short,3566.02,3550.19,44.39,1800,timeout,2025-11-04 15:03:00,2025-11-04 15:33:00
|
||||
long,3417.89,3397.12,-60.77,420,sl,2025-11-04 17:00:00,2025-11-04 17:07:00
|
||||
short,3403.50,3404.42,-2.70,720,ai_rev,2025-11-04 17:22:00,2025-11-04 17:34:00
|
||||
long,3404.42,3378.62,-75.78,840,hard_sl,2025-11-04 17:34:00,2025-11-04 17:48:00
|
||||
short,3374.06,3338.68,104.86,360,tp,2025-11-04 17:58:00,2025-11-04 18:04:00
|
||||
long,3322.78,3297.29,-76.71,720,hard_sl,2025-11-04 18:19:00,2025-11-04 18:31:00
|
||||
long,3288.60,3256.65,-97.15,180,hard_sl,2025-11-04 18:32:00,2025-11-04 18:35:00
|
||||
long,3286.82,3314.81,85.16,420,tp,2025-11-04 18:37:00,2025-11-04 18:44:00
|
||||
long,3307.32,3289.25,-54.64,420,sl,2025-11-04 18:58:00,2025-11-04 19:05:00
|
||||
long,3218.06,3238.68,64.08,360,ai_rev,2025-11-04 20:04:00,2025-11-04 20:10:00
|
||||
short,3238.68,3190.89,147.56,360,tp,2025-11-04 20:10:00,2025-11-04 20:16:00
|
||||
long,3147.17,3175.91,91.32,420,tp,2025-11-04 20:17:00,2025-11-04 20:24:00
|
||||
short,3182.35,3155.30,85.00,420,tp,2025-11-04 20:27:00,2025-11-04 20:34:00
|
||||
long,3206.33,3183.99,-69.67,540,sl,2025-11-04 20:42:00,2025-11-04 20:51:00
|
||||
short,3212.22,3192.06,62.76,480,ai_rev,2025-11-04 20:59:00,2025-11-04 21:07:00
|
||||
long,3192.06,3175.42,-52.13,780,sl,2025-11-04 21:07:00,2025-11-04 21:20:00
|
||||
long,3178.34,3158.96,-60.98,300,sl,2025-11-04 21:22:00,2025-11-04 21:27:00
|
||||
long,3133.41,3094.53,-124.08,180,hard_sl,2025-11-04 21:31:00,2025-11-04 21:34:00
|
||||
long,3059.00,3100.99,137.27,240,tp,2025-11-04 21:35:00,2025-11-04 21:39:00
|
||||
long,3115.13,3089.96,-80.80,120,hard_sl,2025-11-04 21:40:00,2025-11-04 21:42:00
|
||||
long,3136.26,3188.37,166.15,480,tp,2025-11-04 21:47:00,2025-11-04 21:55:00
|
||||
short,3205.64,3233.56,-87.10,420,hard_sl,2025-11-04 21:56:00,2025-11-04 22:03:00
|
||||
short,3230.78,3248.99,-56.36,240,sl,2025-11-04 22:04:00,2025-11-04 22:08:00
|
||||
short,3277.83,3248.60,89.17,480,tp,2025-11-04 22:35:00,2025-11-04 22:43:00
|
||||
short,3245.61,3279.14,-103.31,240,hard_sl,2025-11-04 22:59:00,2025-11-04 23:03:00
|
||||
short,3297.93,3268.30,89.84,240,tp,2025-11-04 23:05:00,2025-11-04 23:09:00
|
||||
long,3264.94,3286.67,66.56,1800,timeout,2025-11-04 23:12:00,2025-11-04 23:42:00
|
||||
long,3227.67,3208.35,-59.86,1260,sl,2025-11-05 00:44:00,2025-11-05 01:05:00
|
||||
long,3184.42,3168.22,-50.87,1020,sl,2025-11-05 01:22:00,2025-11-05 01:39:00
|
||||
long,3187.45,3223.55,113.26,420,tp,2025-11-05 01:45:00,2025-11-05 01:52:00
|
||||
short,3255.31,3272.07,-51.49,300,sl,2025-11-05 02:00:00,2025-11-05 02:05:00
|
||||
short,3273.71,3290.85,-52.36,1560,sl,2025-11-05 02:18:00,2025-11-05 02:44:00
|
||||
long,3198.78,3246.98,150.68,240,tp,2025-11-07 14:39:00,2025-11-07 14:43:00
|
||||
short,3265.04,3288.29,-71.21,660,sl,2025-11-07 14:45:00,2025-11-07 14:56:00
|
||||
short,3296.88,3284.88,36.40,1800,timeout,2025-11-07 15:11:00,2025-11-07 15:41:00
|
||||
long,3520.57,3528.62,22.87,1800,timeout,2025-11-09 14:40:00,2025-11-09 15:10:00
|
||||
short,3480.70,3451.55,83.75,840,tp,2025-11-12 15:24:00,2025-11-12 15:38:00
|
||||
long,3391.00,3404.83,40.78,1800,timeout,2025-11-12 16:10:00,2025-11-12 16:40:00
|
||||
short,3450.56,3411.50,113.20,600,tp,2025-11-13 14:54:00,2025-11-13 15:04:00
|
||||
long,3417.31,3399.18,-53.05,1380,sl,2025-11-13 15:05:00,2025-11-13 15:28:00
|
||||
short,3380.16,3365.44,43.55,1800,timeout,2025-11-13 15:58:00,2025-11-13 16:28:00
|
||||
long,3295.58,3291.68,-11.83,1800,timeout,2025-11-13 17:09:00,2025-11-13 17:39:00
|
||||
long,3217.20,3198.16,-59.18,960,sl,2025-11-13 18:33:00,2025-11-13 18:49:00
|
||||
long,3169.07,3181.75,40.01,1800,timeout,2025-11-13 20:05:00,2025-11-13 20:35:00
|
||||
long,3194.20,3193.24,-3.01,1800,timeout,2025-11-14 00:58:00,2025-11-14 01:28:00
|
||||
long,3135.63,3168.10,103.55,420,tp,2025-11-14 04:38:00,2025-11-14 04:45:00
|
||||
long,3180.29,3163.19,-53.77,480,sl,2025-11-14 05:01:00,2025-11-14 05:09:00
|
||||
short,3212.86,3213.05,-0.59,1800,timeout,2025-11-14 07:00:00,2025-11-14 07:30:00
|
||||
short,3124.61,3141.87,-55.24,780,sl,2025-11-14 13:53:00,2025-11-14 14:06:00
|
||||
long,3188.75,3186.28,-7.75,1800,timeout,2025-11-14 14:38:00,2025-11-14 15:08:00
|
||||
long,3190.71,3166.66,-75.38,180,hard_sl,2025-11-14 15:09:00,2025-11-14 15:12:00
|
||||
short,3133.23,3153.36,-64.25,780,sl,2025-11-14 20:53:00,2025-11-14 21:06:00
|
||||
long,3116.24,3092.25,-76.98,120,hard_sl,2025-11-14 23:04:00,2025-11-14 23:06:00
|
||||
long,3104.53,3114.32,31.53,1800,timeout,2025-11-14 23:07:00,2025-11-14 23:37:00
|
||||
long,3118.31,3100.79,-56.18,960,sl,2025-11-14 23:40:00,2025-11-14 23:56:00
|
||||
long,3104.53,3132.48,90.03,300,tp,2025-11-14 23:57:00,2025-11-15 00:02:00
|
||||
short,3140.64,3126.97,43.53,1800,timeout,2025-11-15 00:08:00,2025-11-15 00:38:00
|
||||
long,3120.33,3101.66,-59.83,240,sl,2025-11-16 15:57:00,2025-11-16 16:01:00
|
||||
long,3093.93,3074.99,-61.22,1380,sl,2025-11-16 16:02:00,2025-11-16 16:25:00
|
||||
short,3071.30,3060.61,34.81,1800,timeout,2025-11-16 16:41:00,2025-11-16 17:11:00
|
||||
short,3090.65,3114.12,-75.94,120,hard_sl,2025-11-16 18:00:00,2025-11-16 18:02:00
|
||||
short,3102.90,3124.42,-69.35,480,sl,2025-11-16 18:03:00,2025-11-16 18:11:00
|
||||
short,3109.24,3093.55,50.46,1800,timeout,2025-11-16 19:10:00,2025-11-16 19:40:00
|
||||
short,3094.51,3059.25,113.94,300,tp,2025-11-16 22:56:00,2025-11-16 23:01:00
|
||||
long,3017.08,3044.49,90.85,300,tp,2025-11-16 23:02:00,2025-11-16 23:07:00
|
||||
long,3039.89,3064.47,80.86,240,tp,2025-11-16 23:08:00,2025-11-16 23:12:00
|
||||
long,3063.30,3099.27,117.42,660,tp,2025-11-16 23:13:00,2025-11-16 23:24:00
|
||||
short,3119.42,3135.78,-52.45,660,sl,2025-11-17 00:28:00,2025-11-17 00:39:00
|
||||
short,3144.67,3119.35,80.52,1500,tp,2025-11-17 01:27:00,2025-11-17 01:52:00
|
||||
short,3119.39,3117.51,6.03,1800,timeout,2025-11-17 13:57:00,2025-11-17 14:27:00
|
||||
short,3189.13,3158.02,97.55,1020,tp,2025-11-17 14:36:00,2025-11-17 14:53:00
|
||||
long,3153.04,3129.95,-73.23,780,sl,2025-11-17 14:55:00,2025-11-17 15:08:00
|
||||
long,3125.96,3108.19,-56.85,600,sl,2025-11-17 15:09:00,2025-11-17 15:19:00
|
||||
long,3121.45,3091.20,-96.91,1440,hard_sl,2025-11-17 15:38:00,2025-11-17 16:02:00
|
||||
short,3079.44,3099.09,-63.81,900,sl,2025-11-17 16:23:00,2025-11-17 16:38:00
|
||||
long,2985.43,2982.67,-9.24,720,ai_rev,2025-11-17 19:43:00,2025-11-17 19:55:00
|
||||
short,2982.67,2958.41,81.34,480,tp,2025-11-17 19:55:00,2025-11-17 20:03:00
|
||||
short,2981.19,2998.30,-57.39,540,sl,2025-11-17 20:16:00,2025-11-17 20:25:00
|
||||
short,3016.52,2985.98,101.24,1140,tp,2025-11-17 21:01:00,2025-11-17 21:20:00
|
||||
short,3033.61,3038.78,-17.04,1800,timeout,2025-11-18 00:50:00,2025-11-18 01:20:00
|
||||
short,3027.64,3001.72,85.61,1800,tp,2025-11-18 02:27:00,2025-11-18 02:57:00
|
||||
long,2984.25,2961.82,-75.16,120,hard_sl,2025-11-18 02:59:00,2025-11-18 03:01:00
|
||||
long,2949.83,2974.23,82.72,540,tp,2025-11-18 03:02:00,2025-11-18 03:11:00
|
||||
long,2958.50,2999.93,140.04,240,tp,2025-11-18 03:34:00,2025-11-18 03:38:00
|
||||
short,2995.68,3013.62,-59.89,720,sl,2025-11-18 05:01:00,2025-11-18 05:13:00
|
||||
short,3082.14,3057.34,80.46,1620,tp,2025-11-18 14:32:00,2025-11-18 14:59:00
|
||||
long,3038.22,3068.76,100.52,240,tp,2025-11-18 15:03:00,2025-11-18 15:07:00
|
||||
short,3085.99,3108.71,-73.62,420,sl,2025-11-18 15:13:00,2025-11-18 15:20:00
|
||||
long,3049.98,3028.43,-70.66,300,sl,2025-11-19 15:20:00,2025-11-19 15:25:00
|
||||
long,3033.37,3013.08,-66.89,300,sl,2025-11-19 15:26:00,2025-11-19 15:31:00
|
||||
short,3001.74,2974.63,90.31,420,tp,2025-11-19 15:44:00,2025-11-19 15:51:00
|
||||
short,2941.36,2956.22,-50.52,360,sl,2025-11-19 16:55:00,2025-11-19 17:01:00
|
||||
long,2929.82,2913.61,-55.33,900,sl,2025-11-19 17:04:00,2025-11-19 17:19:00
|
||||
short,3031.87,3000.08,104.85,840,tp,2025-11-20 13:30:00,2025-11-20 13:44:00
|
||||
long,2938.72,2914.22,-83.37,240,hard_sl,2025-11-20 16:03:00,2025-11-20 16:07:00
|
||||
long,2917.85,2897.03,-71.35,300,sl,2025-11-20 16:09:00,2025-11-20 16:14:00
|
||||
long,2901.47,2883.11,-63.28,1020,sl,2025-11-20 16:15:00,2025-11-20 16:32:00
|
||||
short,2861.55,2835.01,92.75,360,tp,2025-11-20 16:59:00,2025-11-20 17:05:00
|
||||
long,2826.86,2810.30,-58.58,420,sl,2025-11-20 17:10:00,2025-11-20 17:17:00
|
||||
short,2818.99,2840.43,-76.06,1080,hard_sl,2025-11-20 17:20:00,2025-11-20 17:38:00
|
||||
short,2832.17,2847.92,-55.61,840,sl,2025-11-20 17:59:00,2025-11-20 18:13:00
|
||||
long,2835.99,2846.76,37.98,1800,timeout,2025-11-20 23:39:00,2025-11-21 00:09:00
|
||||
long,2796.48,2820.06,84.32,720,tp,2025-11-21 02:56:00,2025-11-21 03:08:00
|
||||
long,2694.70,2719.92,93.59,360,tp,2025-11-21 07:33:00,2025-11-21 07:39:00
|
||||
long,2712.59,2739.04,97.51,1380,tp,2025-11-21 07:48:00,2025-11-21 08:11:00
|
||||
long,2642.21,2666.08,90.34,480,tp,2025-11-21 12:23:00,2025-11-21 12:31:00
|
||||
short,2740.10,2753.81,-50.03,1620,sl,2025-11-21 13:15:00,2025-11-21 13:42:00
|
||||
short,2767.84,2741.85,93.90,780,tp,2025-11-21 14:34:00,2025-11-21 14:47:00
|
||||
short,2777.32,2750.80,95.49,480,tp,2025-11-21 14:54:00,2025-11-21 15:02:00
|
||||
long,2713.39,2695.24,-66.89,240,sl,2025-11-21 15:35:00,2025-11-21 15:39:00
|
||||
long,2684.85,2706.38,80.19,1020,tp,2025-11-21 16:04:00,2025-11-21 16:21:00
|
||||
short,2759.12,2777.45,-66.43,360,sl,2025-11-21 17:00:00,2025-11-21 17:06:00
|
||||
short,2741.00,2762.51,-78.48,420,hard_sl,2025-11-21 18:25:00,2025-11-21 18:32:00
|
||||
long,2720.80,2746.35,93.91,1260,tp,2025-11-21 23:00:00,2025-11-21 23:21:00
|
||||
short,2792.69,2769.23,84.01,1560,tp,2025-11-22 22:53:00,2025-11-22 23:19:00
|
||||
short,2829.66,2849.00,-68.35,300,sl,2025-11-24 02:01:00,2025-11-24 02:06:00
|
||||
short,2881.52,2862.01,67.71,1800,timeout,2025-11-24 05:26:00,2025-11-24 05:56:00
|
||||
short,2835.81,2801.96,119.37,660,tp,2025-11-24 14:33:00,2025-11-24 14:44:00
|
||||
long,2804.71,2787.81,-60.26,300,sl,2025-11-24 14:45:00,2025-11-24 14:50:00
|
||||
long,2797.59,2823.13,91.29,420,tp,2025-11-24 14:51:00,2025-11-24 14:58:00
|
||||
short,2831.26,2820.86,36.73,1800,timeout,2025-11-24 14:59:00,2025-11-24 15:29:00
|
||||
short,2879.41,2883.47,-14.10,1800,timeout,2025-11-25 14:59:00,2025-11-25 15:29:00
|
||||
short,2975.65,2963.24,41.71,1800,timeout,2025-11-25 22:58:00,2025-11-25 23:28:00
|
||||
short,2996.79,3007.52,-35.80,1800,timeout,2025-11-29 06:34:00,2025-11-29 07:04:00
|
||||
long,2930.11,2915.08,-51.30,360,sl,2025-12-01 00:08:00,2025-12-01 00:14:00
|
||||
long,2915.94,2892.34,-80.93,360,hard_sl,2025-12-01 00:15:00,2025-12-01 00:21:00
|
||||
long,2898.29,2875.65,-78.12,600,hard_sl,2025-12-01 00:22:00,2025-12-01 00:32:00
|
||||
long,2874.67,2858.53,-56.15,720,sl,2025-12-01 00:33:00,2025-12-01 00:45:00
|
||||
long,2780.52,2766.38,-50.85,360,sl,2025-12-01 15:24:00,2025-12-01 15:30:00
|
||||
short,2764.99,2742.65,80.80,480,tp,2025-12-01 15:31:00,2025-12-01 15:39:00
|
||||
short,2725.95,2741.42,-56.75,360,sl,2025-12-01 15:44:00,2025-12-01 15:50:00
|
||||
short,2746.94,2724.62,81.25,1200,tp,2025-12-01 15:51:00,2025-12-01 16:11:00
|
||||
short,2970.08,2995.68,-86.19,1380,hard_sl,2025-12-02 15:19:00,2025-12-02 15:42:00
|
||||
short,3007.38,2999.75,25.37,1800,timeout,2025-12-02 15:55:00,2025-12-02 16:25:00
|
||||
long,2997.85,3002.80,16.51,1800,timeout,2025-12-02 16:26:00,2025-12-02 16:56:00
|
||||
long,3051.00,3065.80,48.51,1800,timeout,2025-12-03 14:06:00,2025-12-03 14:36:00
|
||||
long,3142.04,3114.49,-87.68,180,hard_sl,2025-12-03 14:43:00,2025-12-03 14:46:00
|
||||
long,3085.96,3065.82,-65.26,1200,sl,2025-12-03 14:54:00,2025-12-03 15:14:00
|
||||
short,3143.01,3139.66,10.66,1800,timeout,2025-12-05 15:04:00,2025-12-05 15:34:00
|
||||
short,3053.89,3071.75,-58.48,300,sl,2025-12-05 16:17:00,2025-12-05 16:22:00
|
||||
short,3064.49,3032.47,104.49,720,tp,2025-12-05 16:24:00,2025-12-05 16:36:00
|
||||
short,3020.05,3036.05,-52.98,360,sl,2025-12-05 16:52:00,2025-12-05 16:58:00
|
||||
short,3033.91,3009.59,80.16,1020,tp,2025-12-05 17:01:00,2025-12-05 17:18:00
|
||||
long,2970.27,2946.99,-78.38,60,hard_sl,2025-12-07 14:24:00,2025-12-07 14:25:00
|
||||
long,2952.80,2937.88,-50.53,300,sl,2025-12-07 14:26:00,2025-12-07 14:31:00
|
||||
short,2955.73,2946.13,32.48,1800,timeout,2025-12-07 14:36:00,2025-12-07 15:06:00
|
||||
long,3021.37,3013.62,-25.65,360,ai_rev,2025-12-07 15:55:00,2025-12-07 16:01:00
|
||||
short,3013.62,3013.73,-0.37,1800,timeout,2025-12-07 16:01:00,2025-12-07 16:31:00
|
||||
long,3062.66,3037.37,-82.58,120,hard_sl,2025-12-07 22:04:00,2025-12-07 22:06:00
|
||||
long,3023.60,3055.10,104.18,240,tp,2025-12-07 22:07:00,2025-12-07 22:11:00
|
||||
long,3039.14,3050.44,37.18,1800,timeout,2025-12-07 22:13:00,2025-12-07 22:43:00
|
||||
long,3023.95,3043.66,65.18,1800,timeout,2025-12-07 22:55:00,2025-12-07 23:25:00
|
||||
short,3110.63,3130.41,-63.59,540,sl,2025-12-08 01:30:00,2025-12-08 01:39:00
|
||||
short,3147.45,3146.92,1.68,1800,timeout,2025-12-09 15:03:00,2025-12-09 15:33:00
|
||||
short,3248.31,3271.92,-72.68,420,sl,2025-12-09 15:54:00,2025-12-09 16:01:00
|
||||
long,3274.33,3310.87,111.60,1800,tp,2025-12-09 16:02:00,2025-12-09 16:32:00
|
||||
long,3375.00,3365.54,-28.03,1800,timeout,2025-12-09 17:02:00,2025-12-09 17:32:00
|
||||
long,3364.25,3377.14,38.31,1800,timeout,2025-12-09 17:33:00,2025-12-09 18:03:00
|
||||
long,3374.07,3402.78,85.09,780,tp,2025-12-10 19:02:00,2025-12-10 19:15:00
|
||||
long,3356.81,3339.61,-51.24,1020,sl,2025-12-10 21:16:00,2025-12-10 21:33:00
|
||||
long,3258.14,3260.22,6.38,1800,timeout,2025-12-11 00:48:00,2025-12-11 01:18:00
|
||||
short,3155.29,3112.49,135.65,300,tp,2025-12-12 15:30:00,2025-12-12 15:35:00
|
||||
short,3099.13,3114.80,-50.56,240,sl,2025-12-12 15:45:00,2025-12-12 15:49:00
|
||||
short,3099.68,3073.90,83.17,360,tp,2025-12-12 15:50:00,2025-12-12 15:56:00
|
||||
short,3069.35,3064.61,15.44,1800,timeout,2025-12-12 16:02:00,2025-12-12 16:32:00
|
||||
short,3055.23,3073.33,-59.24,840,sl,2025-12-12 16:43:00,2025-12-12 16:57:00
|
||||
long,3096.32,3075.14,-68.40,240,sl,2025-12-15 14:48:00,2025-12-15 14:52:00
|
||||
long,3070.47,3046.48,-78.13,180,hard_sl,2025-12-15 14:53:00,2025-12-15 14:56:00
|
||||
short,3019.19,3045.67,-87.71,60,hard_sl,2025-12-15 15:04:00,2025-12-15 15:05:00
|
||||
short,3045.26,3019.70,83.93,600,tp,2025-12-15 15:06:00,2025-12-15 15:16:00
|
||||
short,2992.30,3008.48,-54.07,360,sl,2025-12-15 15:25:00,2025-12-15 15:31:00
|
||||
short,3004.08,3022.07,-59.89,780,sl,2025-12-15 15:32:00,2025-12-15 15:45:00
|
||||
short,3020.02,2995.61,80.83,360,tp,2025-12-15 15:47:00,2025-12-15 15:53:00
|
||||
short,2971.27,2987.10,-53.28,360,sl,2025-12-15 16:49:00,2025-12-15 16:55:00
|
||||
short,2932.44,2930.26,7.43,1800,timeout,2025-12-15 17:58:00,2025-12-15 18:28:00
|
||||
short,2913.41,2929.09,-53.82,840,sl,2025-12-15 18:40:00,2025-12-15 18:54:00
|
||||
short,2952.31,2925.00,92.50,240,tp,2025-12-16 13:30:00,2025-12-16 13:34:00
|
||||
long,2934.20,2917.89,-55.59,1380,sl,2025-12-16 13:35:00,2025-12-16 13:58:00
|
||||
long,2896.41,2921.38,86.21,300,tp,2025-12-17 14:46:00,2025-12-17 14:51:00
|
||||
long,2954.52,2978.92,82.59,360,tp,2025-12-17 14:52:00,2025-12-17 14:58:00
|
||||
long,2993.93,3022.66,95.96,420,tp,2025-12-17 14:59:00,2025-12-17 15:06:00
|
||||
long,3021.25,2985.55,-118.16,1680,hard_sl,2025-12-17 15:07:00,2025-12-17 15:35:00
|
||||
long,2979.99,2958.45,-72.28,240,sl,2025-12-17 15:38:00,2025-12-17 15:42:00
|
||||
long,2954.97,2935.05,-67.41,240,sl,2025-12-17 15:43:00,2025-12-17 15:47:00
|
||||
long,2943.41,2920.05,-79.36,120,hard_sl,2025-12-17 15:48:00,2025-12-17 15:50:00
|
||||
long,2916.64,2901.68,-51.29,420,sl,2025-12-17 15:51:00,2025-12-17 15:58:00
|
||||
short,2910.08,2878.04,110.10,780,tp,2025-12-17 16:01:00,2025-12-17 16:14:00
|
||||
short,2863.35,2837.10,91.68,660,tp,2025-12-17 16:19:00,2025-12-17 16:30:00
|
||||
short,2839.51,2855.36,-55.82,1320,sl,2025-12-17 16:31:00,2025-12-17 16:53:00
|
||||
short,2860.75,2864.11,-11.75,1800,timeout,2025-12-17 17:02:00,2025-12-17 17:32:00
|
||||
short,2799.02,2813.05,-50.12,660,sl,2025-12-17 19:07:00,2025-12-17 19:18:00
|
||||
short,2927.80,2949.17,-72.99,300,sl,2025-12-18 13:42:00,2025-12-18 13:47:00
|
||||
short,2962.66,2979.00,-55.15,300,sl,2025-12-18 13:59:00,2025-12-18 14:04:00
|
||||
long,2935.00,2963.23,96.18,240,tp,2025-12-18 14:14:00,2025-12-18 14:18:00
|
||||
long,2965.60,2970.00,14.84,480,ai_rev,2025-12-18 14:22:00,2025-12-18 14:30:00
|
||||
short,2970.00,2945.83,81.38,300,tp,2025-12-18 14:30:00,2025-12-18 14:35:00
|
||||
long,2946.44,2928.30,-61.57,480,sl,2025-12-18 14:36:00,2025-12-18 14:44:00
|
||||
long,2946.86,2933.78,-44.39,1800,timeout,2025-12-18 14:46:00,2025-12-18 15:16:00
|
||||
long,2816.44,2799.24,-61.07,480,sl,2025-12-18 17:17:00,2025-12-18 17:25:00
|
||||
long,2798.15,2821.59,83.77,360,tp,2025-12-18 17:26:00,2025-12-18 17:32:00
|
||||
short,2782.52,2805.79,-83.63,600,hard_sl,2025-12-18 19:52:00,2025-12-18 20:02:00
|
||||
short,2807.55,2781.77,91.82,1680,tp,2025-12-18 20:03:00,2025-12-18 20:31:00
|
||||
short,2928.94,2891.71,127.11,240,tp,2025-12-19 03:32:00,2025-12-19 03:36:00
|
||||
long,2913.01,2921.35,28.63,1800,timeout,2025-12-19 04:00:00,2025-12-19 04:30:00
|
||||
long,3004.49,3009.82,17.74,1800,timeout,2025-12-22 02:25:00,2025-12-22 02:55:00
|
||||
short,2986.93,2974.69,40.98,1800,timeout,2025-12-26 02:30:00,2025-12-26 03:00:00
|
||||
short,2913.33,2924.53,-38.44,1800,timeout,2025-12-26 15:04:00,2025-12-26 15:34:00
|
||||
|
8755
best_equity.csv
Normal file
8755
best_equity.csv
Normal file
File diff suppressed because it is too large
Load Diff
228
best_trades.csv
Normal file
228
best_trades.csv
Normal file
@@ -0,0 +1,228 @@
|
||||
open_time,close_time,dir,open_px,close_px,size,pnl,pnl_pct,fee,rebate,hold_sec,reason
|
||||
2025-01-09 01:47:00,2025-01-09 01:51:00,short,3236.07,3243.77,2500.00,-5.9486,-0.2379%,2.9964,2.6968,240,delayed_cross
|
||||
2025-01-19 22:37:00,2025-01-19 23:07:00,long,3345.20,3401.72,2484.38,41.9757,1.6896%,3.0064,2.7058,1800,timeout(1800s)
|
||||
2025-01-19 23:07:00,2025-01-19 23:19:00,long,3401.72,3387.81,2588.57,-10.5849,-0.4089%,3.0999,2.7899,720,SL(-0.41%)
|
||||
2025-01-20 06:36:00,2025-01-20 06:56:00,short,3250.88,3252.83,2561.33,-1.5364,-0.0600%,3.0727,2.7654,1200,cross_rev
|
||||
2025-01-20 07:02:00,2025-01-20 07:09:00,short,3220.24,3241.10,2556.72,-16.5619,-0.6478%,3.0581,2.7523,420,hard_SL(-0.65%)
|
||||
2025-01-20 07:57:00,2025-01-20 08:01:00,short,3205.90,3197.72,2514.55,6.4160,0.2552%,3.0213,2.7192,240,delayed_cross
|
||||
2025-01-20 08:01:00,2025-01-20 08:05:00,short,3197.72,3218.22,2529.84,-16.2183,-0.6411%,3.0261,2.7235,240,hard_SL(-0.64%)
|
||||
2025-01-20 08:10:00,2025-01-20 08:14:00,short,3199.41,3212.78,2488.53,-10.3993,-0.4179%,2.9800,2.6820,240,SL(-0.42%)
|
||||
2025-01-20 08:14:00,2025-01-20 08:18:00,short,3212.78,3196.19,2461.79,12.7121,0.5164%,2.9618,2.6656,240,delayed_cross
|
||||
2025-01-20 08:18:00,2025-01-20 08:25:00,short,3196.19,3210.09,2492.83,-10.8411,-0.4349%,2.9849,2.6864,420,SL(-0.43%)
|
||||
2025-01-20 08:25:00,2025-01-20 08:55:00,short,3210.09,3168.49,2464.98,31.9440,1.2959%,2.9771,2.6794,1800,timeout(1800s)
|
||||
2025-01-20 08:55:00,2025-01-20 09:02:00,short,3168.49,3188.72,2544.10,-16.2434,-0.6385%,3.0432,2.7389,420,hard_SL(-0.64%)
|
||||
2025-01-20 09:21:00,2025-01-20 09:25:00,short,3180.13,3186.13,2502.73,-4.7219,-0.1887%,3.0004,2.7004,240,delayed_cross
|
||||
2025-01-20 09:25:00,2025-01-20 09:29:00,short,3186.13,3194.15,2490.17,-6.2682,-0.2517%,2.9844,2.6860,240,delayed_cross
|
||||
2025-01-20 23:25:00,2025-01-20 23:35:00,long,3363.35,3354.76,2473.76,-6.3180,-0.2554%,2.9647,2.6682,600,cross_rev
|
||||
2025-01-21 00:22:00,2025-01-21 00:52:00,long,3343.01,3375.00,2457.22,23.5137,0.9569%,2.9628,2.6665,1800,timeout(1800s)
|
||||
2025-01-21 01:27:00,2025-01-21 01:53:00,short,3333.15,3310.20,2515.26,17.3185,0.6885%,3.0287,2.7258,1560,cross_rev
|
||||
2025-01-21 02:17:00,2025-01-21 02:21:00,short,3286.27,3303.29,2557.80,-13.2472,-0.5179%,3.0614,2.7553,240,SL(-0.52%)
|
||||
2025-02-03 07:01:00,2025-02-03 07:31:00,short,2837.73,2824.40,2523.92,11.8559,0.4697%,3.0358,2.7322,1800,timeout(1800s)
|
||||
2025-02-03 07:31:00,2025-02-03 07:36:00,short,2824.40,2831.96,2552.80,-6.8330,-0.2677%,3.0593,2.7533,300,cross_rev
|
||||
2025-02-03 08:14:00,2025-02-03 08:44:00,short,2851.94,2778.69,2534.95,65.1084,2.5684%,3.0810,2.7729,1800,timeout(1800s)
|
||||
2025-02-03 08:44:00,2025-02-03 08:45:00,short,2778.69,2798.72,2696.95,-19.4408,-0.7208%,3.2247,2.9022,60,hard_SL(-0.72%)
|
||||
2025-02-03 09:09:00,2025-02-03 09:13:00,short,2820.39,2824.09,2647.55,-3.4732,-0.1312%,3.1750,2.8575,240,delayed_cross
|
||||
2025-02-03 09:13:00,2025-02-03 09:43:00,short,2824.09,2750.20,2638.07,69.0229,2.6164%,3.2071,2.8864,1800,timeout(1800s)
|
||||
2025-02-03 09:43:00,2025-02-03 10:13:00,short,2750.20,2435.30,2809.82,321.7270,11.4501%,3.5648,3.2083,1800,timeout(1800s)
|
||||
2025-02-03 10:13:00,2025-02-03 10:17:00,short,2435.30,2486.09,3613.25,-75.3570,-2.0856%,4.2907,3.8616,240,hard_SL(-2.09%)
|
||||
2025-02-03 10:32:00,2025-02-03 10:36:00,short,2454.28,2490.54,3423.79,-50.5837,-1.4774%,4.0782,3.6704,240,hard_SL(-1.48%)
|
||||
2025-02-03 10:53:00,2025-02-03 10:58:00,short,2469.32,2479.43,3296.31,-13.4959,-0.4094%,3.9475,3.5527,300,SL(-0.41%)
|
||||
2025-02-03 10:58:00,2025-02-03 11:00:00,short,2479.43,2496.72,3261.58,-22.7442,-0.6973%,3.9002,3.5102,120,hard_SL(-0.70%)
|
||||
2025-02-03 11:34:00,2025-02-03 11:45:00,short,2510.35,2523.31,3203.74,-16.5397,-0.5163%,3.8346,3.4511,660,SL(-0.52%)
|
||||
2025-02-03 12:10:00,2025-02-03 12:40:00,short,2517.23,2480.73,3161.44,45.8410,1.4500%,3.8212,3.4391,1800,timeout(1800s)
|
||||
2025-02-03 12:40:00,2025-02-03 13:10:00,short,2480.73,2467.64,3275.08,17.2815,0.5277%,3.9405,3.5464,1800,timeout(1800s)
|
||||
2025-02-03 13:10:00,2025-02-03 13:16:00,short,2467.64,2477.99,3317.30,-13.9137,-0.4194%,3.9724,3.5752,360,SL(-0.42%)
|
||||
2025-02-03 13:39:00,2025-02-03 13:48:00,short,2472.19,2482.08,3281.52,-13.1277,-0.4001%,3.9300,3.5370,540,SL(-0.40%)
|
||||
2025-02-03 14:05:00,2025-02-03 14:09:00,short,2484.26,2495.61,3247.72,-14.8381,-0.4569%,3.8884,3.4995,240,SL(-0.46%)
|
||||
2025-02-03 17:28:00,2025-02-03 17:46:00,long,2584.86,2578.96,3209.66,-7.3261,-0.2283%,3.8472,3.4625,1080,cross_rev
|
||||
2025-02-03 22:19:00,2025-02-03 22:30:00,short,2550.76,2557.25,3190.38,-8.1174,-0.2544%,3.8236,3.4412,660,cross_rev
|
||||
2025-02-04 00:19:00,2025-02-04 00:30:00,long,2704.94,2688.64,3169.13,-19.0972,-0.6026%,3.7915,3.4123,660,hard_SL(-0.60%)
|
||||
2025-02-04 13:59:00,2025-02-04 14:02:00,short,2689.52,2706.41,3120.44,-19.5961,-0.6280%,3.7328,3.3595,180,hard_SL(-0.63%)
|
||||
2025-02-07 21:38:00,2025-02-07 22:08:00,long,2750.61,2786.11,3070.51,39.6288,1.2906%,3.7084,3.3376,1800,timeout(1800s)
|
||||
2025-02-12 21:31:00,2025-02-12 21:47:00,short,2595.80,2608.47,3168.66,-15.4661,-0.4881%,3.7931,3.4138,960,SL(-0.49%)
|
||||
2025-02-12 21:47:00,2025-02-12 22:17:00,short,2608.47,2587.47,3129.05,25.1910,0.8051%,3.7700,3.3930,1800,timeout(1800s)
|
||||
2025-02-25 07:15:00,2025-02-25 07:19:00,short,2529.28,2540.47,3191.08,-14.1179,-0.4424%,3.8208,3.4387,240,SL(-0.44%)
|
||||
2025-02-25 07:49:00,2025-02-25 08:01:00,short,2512.82,2523.61,3154.83,-13.5468,-0.4294%,3.7777,3.3999,720,SL(-0.43%)
|
||||
2025-02-25 08:33:00,2025-02-25 08:50:00,short,2496.24,2510.10,3120.02,-17.3234,-0.5552%,3.7336,3.3603,1020,SL(-0.56%)
|
||||
2025-02-25 09:18:00,2025-02-25 09:22:00,short,2484.42,2498.00,3075.78,-16.8124,-0.5466%,3.6808,3.3128,240,SL(-0.55%)
|
||||
2025-02-25 15:59:00,2025-02-25 16:04:00,short,2367.49,2386.70,3032.83,-24.6086,-0.8114%,3.6246,3.2622,300,hard_SL(-0.81%)
|
||||
2025-02-25 18:04:00,2025-02-25 18:08:00,long,2403.72,2393.77,2970.40,-12.2957,-0.4139%,3.5571,3.2014,240,SL(-0.41%)
|
||||
2025-02-25 18:11:00,2025-02-25 18:30:00,short,2376.73,2389.96,2938.77,-16.3586,-0.5566%,3.5167,3.1650,1140,SL(-0.56%)
|
||||
2025-02-25 23:29:00,2025-02-25 23:35:00,short,2373.44,2379.46,2896.99,-7.3479,-0.2536%,3.4720,3.1248,360,cross_rev
|
||||
2025-02-27 04:13:00,2025-02-27 04:26:00,short,2290.54,2303.58,2877.76,-16.3830,-0.5693%,3.4435,3.0991,780,SL(-0.57%)
|
||||
2025-02-27 04:26:00,2025-02-27 04:30:00,short,2303.58,2309.19,2835.94,-6.9065,-0.2435%,3.3990,3.0591,240,delayed_cross
|
||||
2025-02-27 04:30:00,2025-02-27 05:00:00,long,2309.19,2326.81,2817.82,21.5011,0.7630%,3.3943,3.0549,1800,timeout(1800s)
|
||||
2025-02-28 10:16:00,2025-02-28 10:46:00,short,2203.78,2134.72,2870.73,89.9601,3.1337%,3.4988,3.1490,1800,timeout(1800s)
|
||||
2025-02-28 10:46:00,2025-02-28 10:52:00,short,2134.72,2144.20,3094.75,-13.7434,-0.4441%,3.7055,3.3349,360,SL(-0.44%)
|
||||
2025-02-28 10:52:00,2025-02-28 10:55:00,short,2144.20,2160.90,3059.47,-23.8285,-0.7788%,3.6571,3.2914,180,hard_SL(-0.78%)
|
||||
2025-02-28 13:24:00,2025-02-28 13:43:00,short,2125.15,2121.70,2998.98,4.8686,0.1623%,3.6017,3.2415,1140,cross_rev
|
||||
2025-02-28 13:46:00,2025-02-28 13:50:00,short,2115.35,2114.29,3010.25,1.5084,0.0501%,3.6132,3.2519,240,delayed_cross
|
||||
2025-02-28 13:59:00,2025-02-28 14:09:00,long,2130.87,2119.27,3013.12,-16.4028,-0.5444%,3.6059,3.2453,600,SL(-0.54%)
|
||||
2025-02-28 14:11:00,2025-02-28 14:31:00,short,2112.73,2120.01,2971.21,-10.2381,-0.3446%,3.5593,3.2034,1200,cross_rev
|
||||
2025-02-28 16:45:00,2025-02-28 16:49:00,short,2094.69,2104.60,2944.73,-13.9315,-0.4731%,3.5253,3.1728,240,SL(-0.47%)
|
||||
2025-02-28 21:30:00,2025-02-28 22:00:00,long,2146.41,2158.14,2909.02,15.8976,0.5465%,3.5004,3.1503,1800,timeout(1800s)
|
||||
2025-02-28 22:42:00,2025-02-28 22:57:00,long,2170.87,2161.06,2947.89,-13.3213,-0.4519%,3.5295,3.1765,900,SL(-0.45%)
|
||||
2025-03-03 00:13:00,2025-03-03 00:43:00,long,2240.01,2490.06,2913.70,325.2533,11.1629%,3.6916,3.3224,1800,timeout(1800s)
|
||||
2025-03-03 00:43:00,2025-03-03 00:46:00,long,2490.06,2440.04,3725.91,-74.8456,-2.0088%,4.4262,3.9836,180,hard_SL(-2.01%)
|
||||
2025-03-03 01:06:00,2025-03-03 01:36:00,long,2437.84,2476.92,3537.69,56.7112,1.6031%,4.2793,3.8513,1800,timeout(1800s)
|
||||
2025-03-03 01:36:00,2025-03-03 01:48:00,long,2476.92,2459.55,3678.40,-25.7957,-0.7013%,4.3986,3.9587,720,hard_SL(-0.70%)
|
||||
2025-03-03 01:59:00,2025-03-03 02:16:00,long,2479.82,2461.26,3612.81,-27.0398,-0.7484%,4.3191,3.8872,1020,hard_SL(-0.75%)
|
||||
2025-03-03 02:21:00,2025-03-03 02:33:00,long,2492.94,2480.59,3544.13,-17.5576,-0.4954%,4.2424,3.8182,720,SL(-0.50%)
|
||||
2025-03-03 02:33:00,2025-03-03 02:36:00,long,2480.59,2459.17,3499.18,-30.2155,-0.8635%,4.1809,3.7628,180,hard_SL(-0.86%)
|
||||
2025-03-03 23:33:00,2025-03-03 23:42:00,short,2288.17,2297.07,3422.59,-13.3124,-0.3890%,4.0991,3.6892,540,cross_rev
|
||||
2025-03-04 22:35:00,2025-03-04 22:55:00,long,2088.01,2100.66,3388.29,20.5276,0.6058%,4.0783,3.6704,1200,cross_rev
|
||||
2025-03-07 08:15:00,2025-03-07 08:45:00,short,2177.44,2114.20,3438.59,99.8678,2.9043%,4.1862,3.7676,1800,timeout(1800s)
|
||||
2025-03-07 08:45:00,2025-03-07 09:05:00,short,2114.20,2123.71,3687.21,-16.5856,-0.4498%,4.4147,3.9732,1200,SL(-0.45%)
|
||||
2025-03-07 09:05:00,2025-03-07 09:09:00,short,2123.71,2135.77,3644.64,-20.6970,-0.5679%,4.3612,3.9250,240,SL(-0.57%)
|
||||
2025-03-07 23:49:00,2025-03-07 23:53:00,long,2219.36,2206.59,3591.81,-20.6669,-0.5754%,4.2978,3.8680,240,SL(-0.58%)
|
||||
2025-03-07 23:53:00,2025-03-08 00:05:00,long,2206.59,2202.09,3539.07,-7.2174,-0.2039%,4.2425,3.8183,720,cross_rev
|
||||
2025-03-08 02:05:00,2025-03-08 02:23:00,long,2176.51,2164.97,3519.96,-18.6631,-0.5302%,4.2128,3.7915,1080,SL(-0.53%)
|
||||
2025-03-08 04:57:00,2025-03-08 05:24:00,short,2158.81,2153.19,3472.25,9.0393,0.2603%,4.1721,3.7549,1620,cross_rev
|
||||
2025-03-10 02:38:00,2025-03-10 02:56:00,short,2013.16,2022.45,3493.81,-16.1226,-0.4615%,4.1829,3.7646,1080,SL(-0.46%)
|
||||
2025-03-11 02:07:00,2025-03-11 02:11:00,short,1916.91,1931.48,3452.45,-26.2413,-0.7601%,4.1272,3.7145,240,hard_SL(-0.76%)
|
||||
2025-03-11 02:19:00,2025-03-11 02:49:00,short,1918.01,1890.51,3385.82,48.5451,1.4338%,4.0921,3.6829,1800,timeout(1800s)
|
||||
2025-03-11 02:49:00,2025-03-11 03:16:00,short,1890.51,1863.15,3506.16,50.7421,1.4472%,4.2378,3.8141,1620,cross_rev
|
||||
2025-03-11 03:47:00,2025-03-11 03:51:00,short,1862.16,1869.04,3631.95,-13.4187,-0.3695%,4.3503,3.9153,240,cross_rev
|
||||
2025-03-11 08:07:00,2025-03-11 08:12:00,long,1886.18,1876.76,3597.32,-17.9658,-0.4994%,4.3060,3.8754,300,SL(-0.50%)
|
||||
2025-03-11 21:27:00,2025-03-11 21:28:00,long,1959.11,1926.70,3551.33,-58.7504,-1.6543%,4.2263,3.8037,60,hard_SL(-1.65%)
|
||||
2025-03-12 16:16:00,2025-03-12 16:17:00,short,1882.36,1897.86,3403.40,-28.0247,-0.8234%,4.0673,3.6605,60,hard_SL(-0.82%)
|
||||
2025-03-12 16:29:00,2025-03-12 16:59:00,long,1913.89,1927.41,3332.32,23.5400,0.7064%,4.0129,3.6116,1800,timeout(1800s)
|
||||
2025-03-12 17:10:00,2025-03-12 17:40:00,short,1907.77,1896.72,3390.16,19.6362,0.5792%,4.0800,3.6720,1800,timeout(1800s)
|
||||
2025-03-13 20:37:00,2025-03-13 21:00:00,short,1893.99,1903.36,3438.23,-17.0097,-0.4947%,4.1157,3.7041,1380,SL(-0.49%)
|
||||
2025-03-20 02:06:00,2025-03-20 02:09:00,short,2005.30,2018.91,3394.68,-23.0398,-0.6787%,4.0598,3.6538,180,hard_SL(-0.68%)
|
||||
2025-03-28 19:25:00,2025-03-28 19:27:00,short,1883.20,1895.03,3336.07,-20.9567,-0.6282%,3.9907,3.5916,120,hard_SL(-0.63%)
|
||||
2025-04-03 04:14:00,2025-04-03 04:15:00,long,1933.74,1919.83,3282.68,-23.6133,-0.7193%,3.9250,3.5325,60,hard_SL(-0.72%)
|
||||
2025-04-03 04:27:00,2025-04-03 04:57:00,short,1894.51,1880.55,3222.66,23.7467,0.7369%,3.8814,3.4933,1800,timeout(1800s)
|
||||
2025-04-04 20:38:00,2025-04-04 20:44:00,short,1769.94,1780.32,3281.06,-19.2421,-0.5865%,3.9257,3.5332,360,SL(-0.59%)
|
||||
2025-04-05 00:14:00,2025-04-05 00:18:00,long,1799.52,1791.46,3231.97,-14.4759,-0.4479%,3.8697,3.4827,240,SL(-0.45%)
|
||||
2025-04-05 00:18:00,2025-04-05 00:36:00,short,1791.46,1795.09,3194.82,-6.4736,-0.2026%,3.8299,3.4469,1080,cross_rev
|
||||
2025-04-06 04:50:00,2025-04-06 04:54:00,short,1783.62,1788.30,3177.67,-8.3378,-0.2624%,3.8082,3.4274,240,cross_rev
|
||||
2025-04-06 04:54:00,2025-04-06 05:04:00,long,1788.30,1784.39,3155.88,-6.9001,-0.2186%,3.7829,3.4046,600,cross_rev
|
||||
2025-04-07 03:04:00,2025-04-07 03:08:00,short,1621.11,1618.01,3137.68,6.0001,0.1912%,3.7688,3.3919,240,delayed_cross
|
||||
2025-04-07 03:08:00,2025-04-07 03:16:00,short,1618.01,1632.03,3151.74,-27.3097,-0.8665%,3.7657,3.3891,480,hard_SL(-0.87%)
|
||||
2025-04-07 03:24:00,2025-04-07 03:35:00,short,1616.01,1622.66,3082.52,-12.6848,-0.4115%,3.6914,3.3223,660,SL(-0.41%)
|
||||
2025-04-07 05:08:00,2025-04-07 05:12:00,short,1581.70,1588.76,3049.89,-13.6133,-0.4464%,3.6517,3.2865,240,SL(-0.45%)
|
||||
2025-04-07 06:21:00,2025-04-07 06:27:00,short,1589.29,1597.01,3014.94,-14.6451,-0.4858%,3.6091,3.2482,360,SL(-0.49%)
|
||||
2025-04-07 08:28:00,2025-04-07 08:34:00,long,1578.89,1571.56,2977.43,-13.8227,-0.4643%,3.5646,3.2082,360,SL(-0.46%)
|
||||
2025-04-07 15:30:00,2025-04-07 15:37:00,short,1446.78,1456.64,2941.98,-20.0500,-0.6815%,3.5183,3.1665,420,hard_SL(-0.68%)
|
||||
2025-04-07 15:53:00,2025-04-07 15:57:00,short,1454.59,1464.59,2890.98,-19.8748,-0.6875%,3.4572,3.1115,240,hard_SL(-0.69%)
|
||||
2025-04-07 17:27:00,2025-04-07 17:54:00,long,1491.67,1492.56,2840.42,1.6947,0.0597%,3.4095,3.0686,1620,cross_rev
|
||||
2025-04-07 18:35:00,2025-04-07 18:44:00,short,1495.29,1501.60,2843.81,-12.0006,-0.4220%,3.4054,3.0648,540,SL(-0.42%)
|
||||
2025-04-07 20:49:00,2025-04-07 21:12:00,long,1510.88,1513.01,2812.96,3.9656,0.1410%,3.3779,3.0401,1380,cross_rev
|
||||
2025-04-07 21:48:00,2025-04-07 22:18:00,long,1512.10,1612.18,2822.02,186.7788,6.6186%,3.4985,3.1486,1800,timeout(1800s)
|
||||
2025-04-07 22:18:00,2025-04-07 22:20:00,long,1612.18,1589.01,3288.10,-47.2560,-1.4372%,3.9174,3.5256,120,hard_SL(-1.44%)
|
||||
2025-04-07 22:50:00,2025-04-07 23:05:00,long,1571.92,1559.76,3168.98,-24.5145,-0.7736%,3.7881,3.4093,900,hard_SL(-0.77%)
|
||||
2025-04-07 23:36:00,2025-04-07 23:54:00,long,1558.55,1554.41,3106.74,-8.2525,-0.2656%,3.7231,3.3508,1080,cross_rev
|
||||
2025-04-08 00:30:00,2025-04-08 00:39:00,long,1552.32,1545.29,3085.18,-13.9719,-0.4529%,3.6938,3.3245,540,SL(-0.45%)
|
||||
2025-04-08 01:01:00,2025-04-08 01:03:00,long,1550.02,1539.41,3049.33,-20.8729,-0.6845%,3.6467,3.2820,120,hard_SL(-0.68%)
|
||||
2025-04-09 02:40:00,2025-04-09 03:10:00,short,1480.29,1468.67,2996.24,23.5199,0.7850%,3.6096,3.2486,1800,timeout(1800s)
|
||||
2025-04-09 19:01:00,2025-04-09 19:31:00,short,1468.26,1455.54,3054.13,26.4589,0.8663%,3.6808,3.3128,1800,timeout(1800s)
|
||||
2025-04-09 22:23:00,2025-04-09 22:40:00,long,1488.82,1482.99,3119.36,-12.2150,-0.3916%,3.7359,3.3623,1020,cross_rev
|
||||
2025-04-10 01:20:00,2025-04-10 01:50:00,long,1527.12,1596.62,3087.89,140.5314,4.5511%,3.7898,3.4108,1800,timeout(1800s)
|
||||
2025-04-10 01:50:00,2025-04-10 02:20:00,long,1596.62,1656.57,3438.27,129.1004,3.7548%,4.2034,3.7830,1800,timeout(1800s)
|
||||
2025-04-10 02:20:00,2025-04-10 02:34:00,long,1656.57,1648.01,3759.97,-19.4289,-0.5167%,4.5003,4.0503,840,SL(-0.52%)
|
||||
2025-04-10 02:34:00,2025-04-10 02:42:00,long,1648.01,1643.59,3710.27,-9.9510,-0.2682%,4.4464,4.0017,480,cross_rev
|
||||
2025-04-10 20:41:00,2025-04-10 21:11:00,short,1595.54,1589.17,3684.28,14.7091,0.3992%,4.4300,3.9870,1800,timeout(1800s)
|
||||
2025-04-11 00:16:00,2025-04-11 00:32:00,short,1495.35,1498.14,3719.95,-6.9406,-0.1866%,4.4598,4.0138,960,cross_rev
|
||||
2025-04-11 01:07:00,2025-04-11 01:27:00,short,1496.46,1499.96,3701.48,-8.6572,-0.2339%,4.4366,3.9929,1200,cross_rev
|
||||
2025-04-11 16:06:00,2025-04-11 16:18:00,long,1559.46,1552.32,3678.73,-16.8431,-0.4579%,4.4044,3.9639,720,SL(-0.46%)
|
||||
2025-04-11 16:18:00,2025-04-11 16:22:00,long,1552.32,1552.60,3635.52,0.6558,0.0180%,4.3630,3.9267,240,delayed_cross
|
||||
2025-04-15 21:55:00,2025-04-15 22:25:00,short,1631.26,1610.22,3636.07,46.8980,1.2898%,4.3914,3.9523,1800,timeout(1800s)
|
||||
2025-04-15 22:25:00,2025-04-15 22:41:00,short,1610.22,1619.31,3752.22,-21.1820,-0.5645%,4.4900,4.0410,960,SL(-0.56%)
|
||||
2025-05-09 00:06:00,2025-05-09 00:26:00,long,2050.68,2047.34,3698.14,-6.0233,-0.1629%,4.4342,3.9907,1200,cross_rev
|
||||
2025-05-09 19:28:00,2025-05-09 19:33:00,short,2330.98,2348.40,3681.97,-27.5163,-0.7473%,4.4019,3.9617,300,hard_SL(-0.75%)
|
||||
2025-05-11 01:48:00,2025-05-11 02:12:00,short,2466.18,2478.32,3612.08,-17.7808,-0.4923%,4.3238,3.8914,1440,SL(-0.49%)
|
||||
2025-06-06 05:13:00,2025-06-06 05:18:00,short,2415.46,2420.42,3566.55,-7.3237,-0.2053%,4.2755,3.8479,300,cross_rev
|
||||
2025-06-06 05:20:00,2025-06-06 05:24:00,short,2413.77,2424.43,3547.17,-15.6655,-0.4416%,4.2472,3.8225,240,SL(-0.44%)
|
||||
2025-06-19 02:05:00,2025-06-19 02:25:00,short,2485.49,2496.81,3506.94,-15.9721,-0.4554%,4.1988,3.7789,1200,SL(-0.46%)
|
||||
2025-06-22 07:49:00,2025-06-22 07:50:00,long,2291.17,2274.62,3465.96,-25.0360,-0.7223%,4.1441,3.7297,60,hard_SL(-0.72%)
|
||||
2025-06-22 08:12:00,2025-06-22 08:19:00,short,2275.01,2287.02,3402.34,-17.9613,-0.5279%,4.0720,3.6648,420,SL(-0.53%)
|
||||
2025-06-22 08:20:00,2025-06-22 08:28:00,long,2284.86,2282.33,3356.42,-3.7165,-0.1107%,4.0255,3.6229,480,cross_rev
|
||||
2025-06-24 00:54:00,2025-06-24 00:58:00,short,2214.42,2217.90,3346.12,-5.2585,-0.1572%,4.0122,3.6110,240,cross_rev
|
||||
2025-08-14 21:15:00,2025-08-14 21:36:00,short,4553.08,4565.93,3331.97,-9.4037,-0.2822%,3.9927,3.5935,1260,cross_rev
|
||||
2025-08-15 20:39:00,2025-08-15 20:45:00,short,4620.11,4641.67,3307.46,-15.4345,-0.4667%,3.9597,3.5637,360,SL(-0.47%)
|
||||
2025-08-20 21:45:00,2025-08-20 22:15:00,short,4161.83,4119.44,3267.89,33.2848,1.0185%,3.9414,3.5473,1800,timeout(1800s)
|
||||
2025-08-26 20:57:00,2025-08-26 21:01:00,long,4499.56,4472.24,3350.11,-20.3409,-0.6072%,4.0079,3.6071,240,hard_SL(-0.61%)
|
||||
2025-09-11 20:30:00,2025-09-11 20:31:00,short,4390.02,4421.01,3298.26,-23.2831,-0.7059%,3.9439,3.5495,60,hard_SL(-0.71%)
|
||||
2025-10-11 06:23:00,2025-10-11 06:28:00,short,3841.99,3861.79,3239.07,-16.6928,-0.5154%,3.8769,3.4892,300,SL(-0.52%)
|
||||
2025-10-11 06:28:00,2025-10-11 06:32:00,short,3861.79,3878.52,3196.36,-13.8473,-0.4332%,3.8273,3.4446,240,SL(-0.43%)
|
||||
2025-10-11 06:35:00,2025-10-11 06:39:00,long,3883.63,3864.24,3160.79,-15.7810,-0.4993%,3.7835,3.4051,240,SL(-0.50%)
|
||||
2025-10-11 06:39:00,2025-10-11 06:46:00,short,3864.24,3875.86,3120.39,-9.3832,-0.3007%,3.7388,3.3650,420,cross_rev
|
||||
2025-10-11 06:46:00,2025-10-11 06:51:00,long,3875.86,3858.46,3096.00,-13.8989,-0.4489%,3.7069,3.3362,300,SL(-0.45%)
|
||||
2025-10-11 06:52:00,2025-10-11 07:22:00,short,3859.71,3839.30,3060.32,16.1829,0.5288%,3.6821,3.3139,1800,timeout(1800s)
|
||||
2025-10-11 07:22:00,2025-10-11 07:26:00,short,3839.30,3849.77,3099.86,-8.4535,-0.2727%,3.7148,3.3433,240,delayed_cross
|
||||
2025-10-11 07:49:00,2025-10-11 08:06:00,short,3842.57,3858.39,3077.80,-12.6714,-0.4117%,3.6858,3.3172,1020,SL(-0.41%)
|
||||
2025-10-11 08:07:00,2025-10-11 08:16:00,long,3862.97,3831.58,3045.20,-24.7449,-0.8126%,3.6394,3.2755,540,hard_SL(-0.81%)
|
||||
2025-10-11 08:17:00,2025-10-11 08:26:00,short,3816.12,3832.28,2982.43,-12.6296,-0.4235%,3.5713,3.2142,540,SL(-0.42%)
|
||||
2025-10-11 08:26:00,2025-10-11 08:40:00,short,3832.28,3839.98,2949.96,-5.9272,-0.2009%,3.5364,3.1828,840,cross_rev
|
||||
2025-10-12 23:48:00,2025-10-13 00:18:00,long,4026.01,4066.43,2934.26,29.4591,1.0040%,3.5388,3.1849,1800,timeout(1800s)
|
||||
2025-10-13 00:18:00,2025-10-13 00:37:00,long,4066.43,4053.59,3007.02,-9.4949,-0.3158%,3.6027,3.2425,1140,cross_rev
|
||||
2025-10-13 00:44:00,2025-10-13 00:50:00,long,4119.04,4101.94,2982.38,-12.3812,-0.4151%,3.5714,3.2143,360,SL(-0.42%)
|
||||
2025-10-13 00:50:00,2025-10-13 01:20:00,long,4101.94,4131.75,2950.54,21.4424,0.7267%,3.5535,3.1982,1800,timeout(1800s)
|
||||
2025-10-14 21:37:00,2025-10-14 21:39:00,short,3899.44,3932.77,3003.25,-25.6700,-0.8547%,3.5885,3.2297,120,hard_SL(-0.85%)
|
||||
2025-10-14 21:45:00,2025-10-14 22:07:00,long,3949.74,3942.13,2938.18,-5.6610,-0.1927%,3.5224,3.1702,1320,cross_rev
|
||||
2025-10-15 01:10:00,2025-10-15 01:33:00,long,4120.36,4112.24,2923.15,-5.7607,-0.1971%,3.5043,3.1539,1380,cross_rev
|
||||
2025-10-15 21:43:00,2025-10-15 22:03:00,short,4074.45,4066.89,2907.87,5.3955,0.1855%,3.4927,3.1434,1200,cross_rev
|
||||
2025-10-16 17:44:00,2025-10-16 18:14:00,long,4017.20,4057.01,2920.49,28.9417,0.9910%,3.5219,3.1698,1800,timeout(1800s)
|
||||
2025-10-17 21:43:00,2025-10-17 21:48:00,short,3754.67,3771.24,2991.96,-13.2040,-0.4413%,3.5824,3.2242,300,SL(-0.44%)
|
||||
2025-10-17 21:48:00,2025-10-17 21:52:00,short,3771.24,3788.20,2958.06,-13.3030,-0.4497%,3.5417,3.1875,240,SL(-0.45%)
|
||||
2025-10-17 21:52:00,2025-10-17 22:04:00,long,3788.20,3769.81,2923.91,-14.1943,-0.4855%,3.5002,3.1502,720,SL(-0.49%)
|
||||
2025-10-19 16:25:00,2025-10-19 16:26:00,short,3841.58,3864.92,2887.55,-17.5437,-0.6076%,3.4545,3.1091,60,hard_SL(-0.61%)
|
||||
2025-10-23 00:39:00,2025-10-23 00:59:00,long,3844.42,3834.05,2842.83,-7.6683,-0.2697%,3.4068,3.0661,1200,cross_rev
|
||||
2025-10-30 02:35:00,2025-10-30 02:36:00,short,3905.22,3929.07,2822.81,-17.2395,-0.6107%,3.3770,3.0393,60,hard_SL(-0.61%)
|
||||
2025-11-04 22:42:00,2025-11-04 23:12:00,long,3519.78,3553.41,2778.86,26.5509,0.9555%,3.3506,3.0155,1800,timeout(1800s)
|
||||
2025-11-05 01:42:00,2025-11-05 02:12:00,short,3390.48,3357.61,2844.40,27.5759,0.9695%,3.4298,3.0868,1800,timeout(1800s)
|
||||
2025-11-05 02:12:00,2025-11-05 02:42:00,short,3357.61,3299.42,2912.49,50.4756,1.7331%,3.5253,3.1727,1800,timeout(1800s)
|
||||
2025-11-05 02:42:00,2025-11-05 02:55:00,short,3299.42,3301.70,3037.79,-2.0992,-0.0691%,3.6441,3.2797,780,cross_rev
|
||||
2025-11-05 02:56:00,2025-11-05 03:00:00,short,3291.01,3303.63,3031.63,-11.6254,-0.3835%,3.6310,3.2679,240,delayed_cross
|
||||
2025-11-05 03:07:00,2025-11-05 03:14:00,short,3290.34,3303.99,3001.66,-12.4524,-0.4149%,3.5945,3.2351,420,SL(-0.41%)
|
||||
2025-11-05 04:53:00,2025-11-05 04:58:00,short,3181.02,3202.57,2969.63,-20.1179,-0.6775%,3.5515,3.1963,300,hard_SL(-0.68%)
|
||||
2025-11-05 05:08:00,2025-11-05 05:38:00,short,3190.86,3077.89,2918.45,103.3256,3.5404%,3.5641,3.2077,1800,timeout(1800s)
|
||||
2025-11-05 05:38:00,2025-11-05 05:39:00,short,3077.89,3100.99,3175.87,-23.8354,-0.7505%,3.7967,3.4171,60,hard_SL(-0.75%)
|
||||
2025-11-07 22:43:00,2025-11-07 23:13:00,long,3246.98,3307.59,3115.34,58.1527,1.8667%,3.7733,3.3960,1800,timeout(1800s)
|
||||
2025-11-07 23:13:00,2025-11-07 23:23:00,long,3307.59,3292.22,3259.77,-15.1478,-0.4647%,3.9026,3.5124,600,SL(-0.46%)
|
||||
2025-11-07 23:23:00,2025-11-07 23:37:00,long,3292.22,3292.21,3220.93,-0.0098,-0.0003%,3.8651,3.4786,840,cross_rev
|
||||
2025-11-13 11:24:00,2025-11-13 11:54:00,long,3430.47,3461.35,3219.94,28.9849,0.9002%,3.8813,3.4932,1800,timeout(1800s)
|
||||
2025-11-13 22:51:00,2025-11-13 23:00:00,long,3451.15,3435.67,3291.43,-14.7636,-0.4485%,3.9409,3.5468,540,SL(-0.45%)
|
||||
2025-11-13 23:04:00,2025-11-13 23:08:00,short,3411.50,3432.35,3253.54,-19.8846,-0.6112%,3.8923,3.5031,240,hard_SL(-0.61%)
|
||||
2025-11-14 03:34:00,2025-11-14 04:04:00,short,3208.02,3179.06,3202.85,28.9133,0.9027%,3.8608,3.4747,1800,timeout(1800s)
|
||||
2025-11-14 04:34:00,2025-11-14 04:36:00,short,3170.62,3191.92,3274.17,-21.9956,-0.6718%,3.9158,3.5242,120,hard_SL(-0.67%)
|
||||
2025-11-14 22:32:00,2025-11-14 22:34:00,short,3121.48,3147.98,3218.20,-27.3211,-0.8490%,3.8454,3.4609,120,hard_SL(-0.85%)
|
||||
2025-11-14 22:35:00,2025-11-14 23:02:00,long,3167.25,3176.85,3148.94,9.5445,0.3031%,3.7845,3.4060,1620,cross_rev
|
||||
2025-11-14 23:22:00,2025-11-14 23:26:00,long,3187.81,3183.59,3171.85,-4.1989,-0.1324%,3.8037,3.4233,240,delayed_cross
|
||||
2025-11-14 23:27:00,2025-11-14 23:57:00,long,3202.22,3193.13,3160.40,-8.9713,-0.2839%,3.7871,3.4084,1800,timeout(1800s)
|
||||
2025-11-17 01:59:00,2025-11-17 02:28:00,long,3078.97,3092.34,3137.03,13.6221,0.4342%,3.7726,3.3953,1740,cross_rev
|
||||
2025-11-17 22:56:00,2025-11-17 23:26:00,short,3151.31,3122.60,3170.14,28.8816,0.9110%,3.8215,3.4393,1800,timeout(1800s)
|
||||
2025-11-17 23:26:00,2025-11-17 23:28:00,short,3122.60,3153.09,3241.39,-31.6499,-0.9764%,3.8707,3.4836,120,hard_SL(-0.98%)
|
||||
2025-11-17 23:29:00,2025-11-17 23:33:00,long,3151.40,3129.07,3161.30,-22.4001,-0.7086%,3.7801,3.4021,240,hard_SL(-0.71%)
|
||||
2025-11-17 23:35:00,2025-11-18 00:05:00,short,3126.51,3086.79,3104.35,39.4385,1.2704%,3.7489,3.3740,1800,timeout(1800s)
|
||||
2025-11-18 11:38:00,2025-11-18 12:07:00,long,2999.93,3002.35,3202.01,2.5830,0.0807%,3.8440,3.4596,1740,cross_rev
|
||||
2025-11-18 12:56:00,2025-11-18 13:02:00,long,2999.15,2985.89,3207.51,-14.1812,-0.4421%,3.8405,3.4565,360,SL(-0.44%)
|
||||
2025-11-18 23:09:00,2025-11-18 23:39:00,long,3074.76,3112.69,3171.09,39.1184,1.2336%,3.8288,3.4459,1800,timeout(1800s)
|
||||
2025-11-18 23:39:00,2025-11-18 23:48:00,long,3112.69,3098.96,3267.93,-14.4148,-0.4411%,3.9129,3.5216,540,SL(-0.44%)
|
||||
2025-11-19 23:20:00,2025-11-19 23:50:00,short,3049.98,2978.39,3230.92,75.8370,2.3472%,3.9226,3.5303,1800,timeout(1800s)
|
||||
2025-11-20 01:45:00,2025-11-20 01:52:00,short,2912.34,2923.97,3419.53,-13.6554,-0.3993%,4.0952,3.6857,420,cross_rev
|
||||
2025-11-20 01:54:00,2025-11-20 02:13:00,short,2911.80,2923.91,3384.37,-14.0754,-0.4159%,4.0528,3.6475,1140,SL(-0.42%)
|
||||
2025-11-20 22:40:00,2025-11-20 22:47:00,short,2985.75,3000.90,3348.17,-16.9889,-0.5074%,4.0076,3.6068,420,SL(-0.51%)
|
||||
2025-11-20 22:47:00,2025-11-20 22:53:00,short,3000.90,2998.43,3304.69,2.7200,0.0823%,3.9673,3.5705,360,cross_rev
|
||||
2025-11-21 01:43:00,2025-11-21 01:54:00,short,2820.27,2827.47,3310.50,-8.4515,-0.2553%,3.9675,3.5708,660,cross_rev
|
||||
2025-11-21 02:02:00,2025-11-21 02:06:00,short,2822.81,2842.42,3288.38,-22.8443,-0.6947%,3.9323,3.5391,240,hard_SL(-0.69%)
|
||||
2025-11-21 02:23:00,2025-11-21 02:53:00,short,2830.94,2805.79,3230.29,28.6978,0.8884%,3.8936,3.5042,1800,timeout(1800s)
|
||||
2025-11-21 18:29:00,2025-11-21 18:33:00,short,2685.30,2702.90,3301.06,-21.6358,-0.6554%,3.9483,3.5535,240,hard_SL(-0.66%)
|
||||
2025-11-24 22:45:00,2025-11-24 22:57:00,short,2804.71,2818.80,3245.98,-16.3068,-0.5024%,3.8854,3.4969,720,SL(-0.50%)
|
||||
2025-11-24 22:58:00,2025-11-24 23:20:00,long,2823.13,2823.65,3204.24,0.5902,0.0184%,3.8454,3.4609,1320,cross_rev
|
||||
2025-12-03 23:04:00,2025-12-03 23:08:00,long,3105.04,3087.17,3204.76,-18.4439,-0.5755%,3.8346,3.4512,240,SL(-0.58%)
|
||||
2025-12-08 22:37:00,2025-12-08 22:43:00,short,3136.68,3155.59,3157.69,-19.0366,-0.6029%,3.7778,3.4000,360,hard_SL(-0.60%)
|
||||
2025-12-08 22:44:00,2025-12-08 22:55:00,long,3168.44,3154.66,3109.15,-13.5221,-0.4349%,3.7229,3.3506,660,SL(-0.43%)
|
||||
2025-12-11 03:12:00,2025-12-11 03:26:00,long,3391.59,3377.85,3074.42,-12.4551,-0.4051%,3.6818,3.3136,840,SL(-0.41%)
|
||||
2025-12-11 03:41:00,2025-12-11 03:55:00,short,3375.89,3382.64,3042.36,-6.0831,-0.1999%,3.6472,3.2825,840,cross_rev
|
||||
2025-12-16 21:34:00,2025-12-16 21:39:00,short,2925.00,2937.28,3026.24,-12.7050,-0.4198%,3.6239,3.2615,300,SL(-0.42%)
|
||||
2025-12-16 21:39:00,2025-12-16 22:09:00,short,2937.28,2919.85,2993.57,17.7640,0.5934%,3.6029,3.2426,1800,timeout(1800s)
|
||||
2025-12-16 22:09:00,2025-12-16 22:17:00,short,2919.85,2924.64,3037.08,-4.9823,-0.1640%,3.6415,3.2774,480,cross_rev
|
||||
2025-12-16 22:45:00,2025-12-16 22:49:00,short,2921.59,2936.31,3023.71,-15.2345,-0.5038%,3.6193,3.2574,240,SL(-0.50%)
|
||||
2025-12-17 22:53:00,2025-12-17 23:23:00,long,2951.05,3014.95,2984.72,64.6291,2.1653%,3.6204,3.2584,1800,timeout(1800s)
|
||||
2025-12-17 23:23:00,2025-12-17 23:35:00,long,3014.95,2985.55,3145.39,-30.6720,-0.9751%,3.7561,3.3805,720,hard_SL(-0.98%)
|
||||
2025-12-18 22:20:00,2025-12-18 22:26:00,long,2967.76,2956.28,3067.77,-11.8669,-0.3868%,3.6742,3.3068,360,cross_rev
|
||||
2025-12-18 22:56:00,2025-12-18 23:03:00,long,2950.00,2943.04,3037.18,-7.1657,-0.2359%,3.6403,3.2763,420,cross_rev
|
||||
2025-12-19 22:44:00,2025-12-19 22:46:00,long,2993.80,2975.79,3018.36,-18.1577,-0.6016%,3.6111,3.2500,120,hard_SL(-0.60%)
|
||||
2025-12-19 23:21:00,2025-12-19 23:32:00,short,2958.02,2970.69,2972.06,-12.7301,-0.4283%,3.5588,3.2030,660,SL(-0.43%)
|
||||
|
22
check_db.py
Normal file
22
check_db.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import sqlite3
|
||||
import datetime
|
||||
|
||||
conn = sqlite3.connect(r'models\database.db')
|
||||
c = conn.cursor()
|
||||
|
||||
tables = c.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
||||
print("Tables:", [t[0] for t in tables])
|
||||
|
||||
for table in [t[0] for t in tables]:
|
||||
try:
|
||||
r = c.execute(f"SELECT MIN(id), MAX(id), COUNT(*) FROM [{table}]").fetchone()
|
||||
if r[2] > 0 and r[0] and r[0] > 1000000000:
|
||||
ts_min = r[0] // 1000 if r[0] > 1e12 else r[0]
|
||||
ts_max = r[1] // 1000 if r[1] > 1e12 else r[1]
|
||||
print(f" {table}: {r[2]} rows | {datetime.datetime.fromtimestamp(ts_min)} ~ {datetime.datetime.fromtimestamp(ts_max)}")
|
||||
else:
|
||||
print(f" {table}: {r[2]} rows")
|
||||
except Exception as e:
|
||||
print(f" {table}: error - {e}")
|
||||
|
||||
conn.close()
|
||||
20510
combo_trades.csv
Normal file
20510
combo_trades.csv
Normal file
File diff suppressed because it is too large
Load Diff
3351
final_trades.csv
Normal file
3351
final_trades.csv
Normal file
File diff suppressed because it is too large
Load Diff
13
multi_strategy_results.csv
Normal file
13
multi_strategy_results.csv
Normal file
@@ -0,0 +1,13 @@
|
||||
方案,交易数,年净利,月均,返佣,最大回撤
|
||||
A1: EMA(8/21) ATR>0.3% [基线],227,1196,100,2452,1191
|
||||
A2: 基线 x5 (500U保证金),227,5981,498,12258,5955
|
||||
A3: 基线 x10 (1000U保证金),227,11962,997,24516,11910
|
||||
B1: ATR>0.15% (更频繁),2805,-550,-46,30294,4292
|
||||
B2: ATR>0.1% SL=0.8%,7247,-4384,-365,78268,7849
|
||||
B3: ATR>0.2% SL=0.8% MH=3600,954,262,22,10303,2564
|
||||
C1: 3策略组合 (不重叠),1042,-798,-66,11254,3519
|
||||
C2: 5策略组合 (不重叠),1451,-2880,-240,15671,6137
|
||||
D1: 3策略+300U仓位,1042,-2393,-199,33761,10556
|
||||
D2: 3策略+500U仓位,1042,-3988,-332,56268,17593
|
||||
E1: 5策略并行(允许同时持仓) 100U each,3265,-3370,-281,35262,8580
|
||||
E2: 5策略并行 200U each,3265,-6741,-562,70524,17159
|
||||
|
118
param_results.csv
Normal file
118
param_results.csv
Normal file
@@ -0,0 +1,118 @@
|
||||
fast,slow,big,atr_min,stop_loss,max_hold,net_pct,net_usd,trades,win_rate,dir_pnl,rebate,net_fee
|
||||
8,21,120,0.003,0.004,1800,2.8804,28.8042,227,28.63,35.8251,63.1882,7.0209
|
||||
30,80,200,0.002,0.008,3600,2.5294,25.2944,538,39.59,41.7775,148.3473,16.4830
|
||||
8,21,120,0.002,0.008,1800,1.9371,19.3709,1111,33.39,53.9134,310.8830,34.5426
|
||||
8,21,120,0.003,0.008,1800,1.8037,18.0373,215,32.56,24.6529,59.5406,6.6156
|
||||
8,21,120,0.002,0.004,1800,1.6738,16.7385,1139,31.61,51.9434,316.8445,35.2049
|
||||
8,21,120,0.003,0.006,1800,1.1376,11.3764,227,29.52,18.3430,62.6992,6.9666
|
||||
8,21,120,0.002,0.006,1800,0.7172,7.1717,1124,32.92,41.8236,311.8677,34.6520
|
||||
13,55,200,0.002,0.005,1800,0.0606,0.6065,748,37.17,23.7114,207.9442,23.1049
|
||||
13,34,200,0.002,0.005,1800,-0.5446,-5.4462,823,34.63,19.4196,223.7925,24.8658
|
||||
8,21,120,0.0015,0.008,1800,-0.7004,-7.0036,2764,32.96,79.9670,782.7354,86.9706
|
||||
13,34,200,0.002,0.008,3600,-1.0078,-10.0782,637,29.83,9.2531,173.9816,19.3313
|
||||
13,55,200,0.0015,0.008,3600,-1.0144,-10.1437,1444,31.44,34.7921,404.4219,44.9358
|
||||
20,80,120,0.0015,0.008,3600,-1.2599,-12.5994,1718,37.08,39.4259,468.2275,52.0253
|
||||
13,34,200,0.0015,0.005,1800,-1.2949,-12.9486,2098,36.32,51.5414,580.4096,64.4900
|
||||
13,55,200,0.0015,0.005,1800,-1.5247,-15.2473,1940,38.61,45.0450,542.6307,60.2923
|
||||
30,80,200,0.002,0.005,1800,-1.7559,-17.5588,678,38.79,3.1650,186.5138,20.7238
|
||||
8,21,120,0.0015,0.004,1800,-1.8117,-18.1172,2805,31.87,69.2390,786.2058,87.3562
|
||||
30,80,120,0.002,0.008,3600,-1.8769,-18.7693,587,38.84,-1.2243,157.9043,17.5449
|
||||
13,34,200,0.0015,0.008,3600,-2.1280,-21.2799,1592,29.33,27.9077,442.6891,49.1877
|
||||
20,55,200,0.002,0.005,1800,-2.2504,-22.5040,723,37.90,-0.9827,193.6921,21.5213
|
||||
20,80,200,0.0015,0.008,3600,-2.3212,-23.2123,1390,36.62,18.6887,377.1086,41.9010
|
||||
13,55,120,0.002,0.005,1800,-2.3291,-23.2913,899,37.93,4.0796,246.3386,27.3710
|
||||
8,21,120,0.0015,0.006,1800,-2.5501,-25.5015,2781,32.76,61.1120,779.5212,86.6135
|
||||
20,80,200,0.002,0.005,1800,-2.7383,-27.3832,694,38.33,-6.2615,190.0956,21.1217
|
||||
13,34,120,0.002,0.005,1800,-2.8484,-28.4839,924,34.63,-1.0207,247.1690,27.4632
|
||||
20,55,200,0.0015,0.008,3600,-3.0647,-30.6472,1395,34.19,11.7438,381.5190,42.3910
|
||||
30,80,200,0.0015,0.008,3600,-3.2665,-32.6653,1338,39.91,7.4666,361.1871,40.1319
|
||||
13,55,200,0.0005,0.008,3600,-3.3377,-33.3771,9040,30.95,249.3994,2544.9893,282.7766
|
||||
20,80,120,0.002,0.008,3600,-3.3637,-33.6373,655,36.49,-14.1033,175.8060,19.5340
|
||||
13,55,200,0.002,0.008,3600,-3.4898,-34.8977,585,30.77,-17.3758,157.6970,17.5219
|
||||
8,21,120,0.0013,0.004,1800,-4.0358,-40.3579,4115,32.32,86.7056,1143.5715,127.0635
|
||||
8,21,120,0.0013,0.008,1800,-4.0630,-40.6300,4058,33.07,85.0233,1130.8798,125.6533
|
||||
20,80,200,0.002,0.008,3600,-4.0714,-40.7135,541,34.94,-24.6583,144.4968,16.0552
|
||||
20,80,120,0.002,0.005,1800,-4.7790,-47.7896,826,38.14,-22.9637,223.4329,24.8259
|
||||
30,80,120,0.002,0.005,1800,-4.8156,-48.1563,731,38.44,-26.2163,197.4609,21.9401
|
||||
8,21,120,0.0013,0.006,1800,-5.0486,-50.4858,4082,33.00,75.6864,1135.5499,126.1722
|
||||
13,55,120,0.0015,0.008,3600,-5.0751,-50.7511,1818,31.57,3.5346,488.5720,54.2858
|
||||
13,34,120,0.0015,0.005,1800,-5.2842,-52.8417,2380,36.60,18.5168,642.2269,71.3585
|
||||
8,21,120,0.001,0.005,5400,-5.3197,-53.1966,5813,30.76,127.2687,1624.1877,180.4653
|
||||
13,34,120,0.002,0.008,3600,-5.3360,-53.3596,739,29.91,-31.4512,197.1759,21.9084
|
||||
8,21,120,0.001,0.005,7200,-5.3686,-53.6863,5760,30.76,125.0600,1608.7167,178.7463
|
||||
30,80,120,0.0015,0.008,3600,-5.4084,-54.0845,1528,39.53,-8.4484,410.7246,45.6361
|
||||
20,80,200,0.0015,0.005,1800,-5.5411,-55.4113,1884,40.18,1.0998,508.6001,56.5111
|
||||
13,55,120,0.002,0.008,3600,-5.6179,-56.1794,732,30.74,-34.7510,192.8561,21.4285
|
||||
20,55,200,0.002,0.008,3600,-5.8880,-58.8800,550,33.64,-42.6664,145.9224,16.2136
|
||||
13,55,120,0.0015,0.005,1800,-6.6472,-66.4722,2328,39.35,3.3645,628.5303,69.8367
|
||||
13,34,200,0.0005,0.008,3600,-6.9625,-69.6248,9557,30.09,230.7806,2703.6480,300.4053
|
||||
8,21,120,0.001,0.005,3600,-7.0396,-70.3962,6049,30.70,115.6276,1674.2136,186.0237
|
||||
13,55,200,0.0005,0.005,1800,-7.5588,-75.5883,13167,39.93,330.3966,3653.8642,405.9849
|
||||
20,55,120,0.002,0.005,1800,-8.3000,-83.0000,866,38.57,-58.2772,222.5052,24.7228
|
||||
13,34,120,0.0015,0.008,3600,-8.3653,-83.6529,1846,29.69,-28.3848,497.4133,55.2681
|
||||
20,80,120,0.0015,0.005,1800,-8.4858,-84.8576,2220,40.50,-19.4811,588.3877,65.3764
|
||||
13,34,200,0.001,0.008,5400,-8.7122,-87.1219,3772,29.40,24.0490,1000.5385,111.1709
|
||||
13,55,200,0.001,0.005,1800,-8.8009,-88.0089,5430,39.02,72.9670,1448.7829,160.9759
|
||||
30,80,200,0.0015,0.005,1800,-8.8155,-88.1553,1742,41.04,-36.2424,467.2162,51.9129
|
||||
13,34,200,0.001,0.008,7200,-9.3224,-93.2236,3658,28.76,14.2231,967.0196,107.4466
|
||||
13,55,200,0.001,0.008,3600,-9.3394,-93.3936,3882,31.63,19.5971,1016.9162,112.9907
|
||||
20,80,200,0.0005,0.008,3600,-9.7768,-97.7683,8676,35.86,163.2375,2349.0527,261.0059
|
||||
20,55,200,0.0015,0.005,1800,-9.8959,-98.9595,1919,39.50,-44.1768,493.0437,54.7826
|
||||
20,55,120,0.0015,0.008,3600,-9.9651,-99.6511,1744,33.66,-48.9695,456.1342,50.6816
|
||||
13,34,200,0.0005,0.005,1800,-10.1572,-101.5722,13277,37.00,309.5739,3700.3149,411.1461
|
||||
8,21,120,0.001,0.008,1800,-11.0920,-110.9199,7247,32.40,107.9420,1969.7574,218.8619
|
||||
8,21,120,0.001,0.004,1800,-11.2407,-112.4072,7326,31.87,107.5953,1980.0229,220.0025
|
||||
30,80,200,0.001,0.005,1800,-11.3494,-113.4945,4960,43.17,30.1867,1293.1304,143.6812
|
||||
8,21,120,0.0008,0.004,1800,-11.8072,-118.0723,10688,32.25,205.0977,2908.5303,323.1700
|
||||
20,55,120,0.002,0.008,3600,-12.0458,-120.4576,684,32.31,-101.1721,173.5694,19.2855
|
||||
13,34,200,0.001,0.008,3600,-12.0761,-120.7608,4183,29.91,0.9299,1095.2161,121.6907
|
||||
8,21,120,0.001,0.006,1800,-12.1207,-121.2067,7280,32.35,97.9991,1972.8521,219.2058
|
||||
8,21,120,0.0008,0.008,1800,-12.1657,-121.6575,10601,32.61,198.8687,2884.7356,320.5262
|
||||
30,80,200,0.0005,0.008,3600,-12.5232,-125.2321,8311,39.07,123.4098,2237.7771,248.6419
|
||||
8,21,120,0.0008,0.006,1800,-12.7361,-127.3613,10633,32.58,193.9211,2891.5410,321.2823
|
||||
8,21,120,0.001,0.005,1800,-12.9573,-129.5727,7284,32.02,87.6035,1954.5855,217.1762
|
||||
30,80,120,0.0015,0.005,1800,-12.9954,-129.9539,1942,41.04,-73.8222,505.1853,56.1317
|
||||
13,34,200,0.001,0.005,1800,-13.4101,-134.1006,5633,36.52,30.5080,1481.4776,164.6086
|
||||
30,80,200,0.001,0.008,3600,-13.6138,-136.1376,3522,39.41,-36.6941,894.9918,99.4435
|
||||
13,34,200,0.001,0.008,1800,-13.9334,-139.3336,5527,37.02,21.9090,1451.1835,161.2426
|
||||
20,55,120,0.0015,0.005,1800,-14.0235,-140.2351,2285,40.18,-76.7679,571.2056,63.4673
|
||||
13,34,120,0.001,0.005,5400,-14.0512,-140.5117,4415,29.97,-14.1500,1137.2558,126.3618
|
||||
20,80,200,0.001,0.005,1800,-14.7028,-147.0281,5273,41.82,3.8789,1358.1634,150.9070
|
||||
13,34,120,0.001,0.005,7200,-14.7324,-147.3243,4269,29.66,-25.7517,1094.1534,121.5726
|
||||
20,80,200,0.001,0.008,3600,-15.0881,-150.8810,3706,35.89,-48.6981,919.6456,102.1828
|
||||
20,55,200,0.0005,0.008,3600,-16.4525,-164.5246,8432,32.84,79.0765,2192.4097,243.6011
|
||||
13,55,120,0.001,0.005,1800,-16.6854,-166.8541,6619,39.60,16.0628,1646.2524,182.9169
|
||||
20,80,120,0.001,0.008,3600,-16.8332,-168.3321,4617,35.85,-42.7615,1130.1352,125.5706
|
||||
13,34,120,0.001,0.005,3600,-16.8915,-168.9149,4892,30.31,-30.3968,1246.6631,138.5181
|
||||
20,55,200,0.001,0.005,1800,-16.9523,-169.5229,5262,41.18,-25.9781,1291.9033,143.5448
|
||||
13,55,120,0.001,0.008,3600,-16.9881,-169.8815,4852,31.51,-38.6785,1180.8266,131.2030
|
||||
13,55,120,0.0005,0.008,3600,-17.4817,-174.8171,11319,30.75,140.0546,2833.8459,314.8718
|
||||
20,55,200,0.001,0.008,3600,-17.6084,-176.0837,3641,33.40,-76.3658,897.4604,99.7178
|
||||
8,21,120,0.0005,0.004,1800,-18.5058,-185.0575,16780,32.03,301.1786,4376.1252,486.2361
|
||||
13,34,120,0.0005,0.008,3600,-18.7777,-187.7767,11110,30.32,140.3925,2953.5231,328.1692
|
||||
13,34,120,0.001,0.005,1800,-18.8707,-188.7074,6444,36.86,-7.5874,1630.0806,181.1201
|
||||
8,21,120,0.0005,0.008,1800,-19.1071,-191.0708,16672,32.25,291.6353,4344.3548,482.7061
|
||||
30,80,120,0.001,0.008,3600,-19.3656,-193.6559,4127,39.25,-80.2881,1020.3104,113.3678
|
||||
20,80,200,0.0005,0.005,1800,-19.4582,-194.5820,13093,42.71,187.2340,3436.3437,381.8160
|
||||
13,34,120,0.001,0.008,3600,-19.4902,-194.9016,4851,30.34,-59.3513,1219.9530,135.5503
|
||||
8,21,120,0.0005,0.006,1800,-19.6367,-196.3674,16704,32.23,287.0091,4350.3884,483.3765
|
||||
30,80,120,0.001,0.005,1800,-19.7258,-197.2577,5623,43.20,-42.0391,1396.9675,155.2186
|
||||
13,34,200,0.001,0.008,900,-19.8344,-198.3435,8184,44.24,30.4954,2059.5500,228.8389
|
||||
8,21,120,0.001,0.005,900,-19.9549,-199.5489,10093,40.95,87.1353,2580.1574,286.6842
|
||||
20,80,120,0.0005,0.008,3600,-20.3308,-203.3076,10889,35.37,98.4242,2715.5860,301.7318
|
||||
20,80,120,0.001,0.005,1800,-20.5589,-205.5892,6332,41.65,-31.3286,1568.3454,174.2606
|
||||
13,34,120,0.0005,0.005,1800,-21.0367,-210.3667,15337,37.12,233.6945,3996.5509,444.0612
|
||||
20,55,120,0.001,0.008,3600,-21.3117,-213.1174,4597,33.54,-90.3005,1105.3524,122.8169
|
||||
20,55,120,0.001,0.005,1800,-22.1645,-221.6446,6388,41.34,-51.2146,1533.8703,170.4300
|
||||
20,55,200,0.0005,0.005,1800,-22.1716,-221.7155,12655,41.60,128.3622,3150.6996,350.0777
|
||||
30,80,200,0.0005,0.005,1800,-23.1090,-231.0899,12609,43.82,129.2105,3242.7037,360.3004
|
||||
13,55,120,0.0005,0.005,1800,-23.2784,-232.7844,16204,40.04,209.2676,3978.4678,442.0520
|
||||
8,21,120,0.0003,0.004,1800,-24.5317,-245.3168,18991,31.87,290.1137,4818.8749,535.4305
|
||||
13,34,120,0.001,0.005,900,-24.6756,-246.7559,9269,43.88,5.2843,2268.3617,252.0402
|
||||
8,21,120,0.0003,0.008,1800,-25.0885,-250.8855,18883,32.06,281.0114,4787.0721,531.8969
|
||||
8,21,120,0.0003,0.006,1800,-25.5790,-255.7904,18915,32.04,276.7012,4792.4250,532.4917
|
||||
30,80,120,0.0005,0.008,3600,-27.4109,-274.1094,9848,38.70,-8.5842,2389.7267,265.5252
|
||||
20,55,120,0.0005,0.008,3600,-28.5676,-285.6755,10748,32.77,-1.0716,2561.4354,284.6039
|
||||
20,80,120,0.0005,0.005,1800,-30.9125,-309.1250,16030,42.51,112.0831,3790.8723,421.2080
|
||||
20,55,120,0.0005,0.005,1800,-35.4787,-354.7868,15692,41.84,41.0372,3562.4159,395.8240
|
||||
30,80,120,0.0005,0.005,1800,-35.8112,-358.1119,14514,43.91,13.0272,3340.2519,371.1391
|
||||
|
307
交易/bitmart-100u回测.py
Normal file
307
交易/bitmart-100u回测.py
Normal file
@@ -0,0 +1,307 @@
|
||||
"""
|
||||
BitMart ETH 返佣策略回测 — 固定仓位版
|
||||
|
||||
条件:
|
||||
- 每笔仓位固定 100 USDT 保证金
|
||||
- 100 倍杠杆 → 每笔名义价值 10,000 USDT
|
||||
- ETH 合约
|
||||
- 90% 手续费返佣
|
||||
- 最低持仓 > 3 分钟
|
||||
|
||||
策略:EMA(8/21/120) + ATR>0.3% 过滤(回测最优参数)
|
||||
"""
|
||||
import sys, time, datetime, sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025, 1, 1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026, 1, 1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s, e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
def main():
|
||||
print("=" * 70, flush=True)
|
||||
print(" ETH 返佣策略回测 | 固定 100U 仓位 | 100x 杠杆", flush=True)
|
||||
print("=" * 70, flush=True)
|
||||
|
||||
# ===== 固定参数 =====
|
||||
MARGIN = 100.0 # 每笔保证金 100 USDT
|
||||
LEVERAGE = 100 # 100 倍杠杆
|
||||
NOTIONAL = MARGIN * LEVERAGE # 名义价值 10,000 USDT
|
||||
TAKER_FEE = 0.0006 # taker 手续费 0.06%
|
||||
REBATE_RATE = 0.90 # 90% 返佣
|
||||
MIN_HOLD = 200 # 最低持仓秒数 (>3分钟)
|
||||
MAX_HOLD = 1800 # 最大持仓秒数 (30分钟)
|
||||
SL_PCT = 0.004 # 止损 0.4%
|
||||
HARD_SL = 0.006 # 硬止损 0.6%
|
||||
|
||||
# EMA 参数(回测最优)
|
||||
FP, SP, BP = 8, 21, 120
|
||||
ATR_MIN = 0.003 # ATR > 0.3%
|
||||
ATR_P = 14
|
||||
|
||||
print(f"\n 保证金: {MARGIN} USDT/笔", flush=True)
|
||||
print(f" 杠杆: {LEVERAGE}x → 名义价值: {NOTIONAL:,.0f} USDT/笔", flush=True)
|
||||
print(f" 手续费: {TAKER_FEE*100:.2f}% | 返佣: {REBATE_RATE*100:.0f}%", flush=True)
|
||||
print(f" 策略: EMA({FP}/{SP}/{BP}) ATR>{ATR_MIN*100:.1f}%", flush=True)
|
||||
print(f" 止损: {SL_PCT*100:.1f}% | 硬止损: {HARD_SL*100:.1f}%", flush=True)
|
||||
print(f" 持仓: {MIN_HOLD}s ~ {MAX_HOLD}s\n", flush=True)
|
||||
|
||||
data = load()
|
||||
print(f" 数据: {len(data)} 根 1分钟K线 (2025全年)\n", flush=True)
|
||||
|
||||
# ===== 回测引擎 =====
|
||||
ef = EMA(FP); es = EMA(SP); eb = EMA(BP)
|
||||
H = []; L = []; C = []
|
||||
pf_ = None; ps_ = None
|
||||
|
||||
pos = 0 # -1/0/1
|
||||
op = 0.0 # 开仓价
|
||||
ot = None # 开仓时间
|
||||
pend = None # 延迟信号
|
||||
|
||||
# 统计
|
||||
trades = [] # [(方向, 开仓价, 平仓价, 盈亏, 手续费, 返佣, 持仓秒, 原因, 开仓时间, 平仓时间)]
|
||||
|
||||
for dt, o_, h_, l_, c_ in data:
|
||||
p = c_
|
||||
H.append(h_); L.append(l_); C.append(p)
|
||||
fast = ef.update(p); slow = es.update(p); big = eb.update(p)
|
||||
|
||||
# ATR
|
||||
atr_pct = 0.0
|
||||
if len(H) > ATR_P + 1:
|
||||
s = 0.0
|
||||
for i in range(-ATR_P, 0):
|
||||
tr = H[i] - L[i]
|
||||
d1 = abs(H[i] - C[i-1]); d2 = abs(L[i] - C[i-1])
|
||||
if d1 > tr: tr = d1
|
||||
if d2 > tr: tr = d2
|
||||
s += tr
|
||||
atr_pct = s / (ATR_P * p) if p > 0 else 0
|
||||
|
||||
# EMA 交叉检测
|
||||
cu = pf_ is not None and pf_ <= ps_ and fast > slow # 金叉
|
||||
cd = pf_ is not None and pf_ >= ps_ and fast < slow # 死叉
|
||||
pf_ = fast; ps_ = slow
|
||||
|
||||
# --- 有持仓 ---
|
||||
if pos != 0 and ot is not None:
|
||||
pp = (p - op) / op if pos == 1 else (op - p) / op # 浮动盈亏%
|
||||
hsec = (dt - ot).total_seconds()
|
||||
|
||||
# 硬止损(不受时间限制)
|
||||
if -pp >= HARD_SL:
|
||||
pnl = NOTIONAL * pp
|
||||
fee = NOTIONAL * TAKER_FEE * 2 # 开+平
|
||||
reb = fee * REBATE_RATE
|
||||
d = 'long' if pos == 1 else 'short'
|
||||
trades.append((d, op, p, pnl, fee, reb, hsec, f"硬止损({pp*100:+.2f}%)", ot, dt))
|
||||
pos = 0; op = 0; ot = None; pend = None
|
||||
continue
|
||||
|
||||
can_c = hsec >= MIN_HOLD
|
||||
|
||||
if can_c:
|
||||
do_close = False; reason = ""
|
||||
if -pp >= SL_PCT:
|
||||
do_close = True; reason = f"止损({pp*100:+.2f}%)"
|
||||
elif hsec >= MAX_HOLD:
|
||||
do_close = True; reason = f"超时({hsec:.0f}s)"
|
||||
elif pos == 1 and cd:
|
||||
do_close = True; reason = "死叉反转"
|
||||
elif pos == -1 and cu:
|
||||
do_close = True; reason = "金叉反转"
|
||||
elif pend == 'cl' and pos == 1:
|
||||
do_close = True; reason = "延迟死叉"
|
||||
elif pend == 'cs' and pos == -1:
|
||||
do_close = True; reason = "延迟金叉"
|
||||
|
||||
if do_close:
|
||||
pnl = NOTIONAL * pp
|
||||
fee = NOTIONAL * TAKER_FEE * 2
|
||||
reb = fee * REBATE_RATE
|
||||
d = 'long' if pos == 1 else 'short'
|
||||
trades.append((d, op, p, pnl, fee, reb, hsec, reason, ot, dt))
|
||||
pos = 0; op = 0; ot = None; pend = None
|
||||
|
||||
# 反手
|
||||
if atr_pct >= ATR_MIN:
|
||||
if (cd or fast < slow) and p < big:
|
||||
pos = -1; op = p; ot = dt
|
||||
elif (cu or fast > slow) and p > big:
|
||||
pos = 1; op = p; ot = dt
|
||||
continue
|
||||
else:
|
||||
if pos == 1 and cd: pend = 'cl'
|
||||
elif pos == -1 and cu: pend = 'cs'
|
||||
|
||||
# --- 无持仓 ---
|
||||
if pos == 0 and atr_pct >= ATR_MIN:
|
||||
if cu and p > big:
|
||||
pos = 1; op = p; ot = dt
|
||||
elif cd and p < big:
|
||||
pos = -1; op = p; ot = dt
|
||||
|
||||
# 强制平仓
|
||||
if pos != 0:
|
||||
p = data[-1][4]; dt = data[-1][0]
|
||||
pp = (p - op) / op if pos == 1 else (op - p) / op
|
||||
pnl = NOTIONAL * pp
|
||||
fee = NOTIONAL * TAKER_FEE * 2
|
||||
reb = fee * REBATE_RATE
|
||||
d = 'long' if pos == 1 else 'short'
|
||||
trades.append((d, op, p, pnl, fee, reb, (dt - ot).total_seconds(), "回测结束", ot, dt))
|
||||
|
||||
# ===== 输出结果 =====
|
||||
if not trades:
|
||||
print(" 无交易记录!", flush=True)
|
||||
return
|
||||
|
||||
n = len(trades)
|
||||
wins = [t for t in trades if t[3] > 0]
|
||||
losses = [t for t in trades if t[3] <= 0]
|
||||
|
||||
total_pnl = sum(t[3] for t in trades) # 方向总盈亏
|
||||
total_fee = sum(t[4] for t in trades) # 总手续费
|
||||
total_rebate = sum(t[5] for t in trades) # 总返佣
|
||||
net_fee = total_fee - total_rebate # 净手续费成本(10%)
|
||||
net_profit = total_pnl - net_fee # 最终净利润
|
||||
total_volume = NOTIONAL * n * 2 # 总交易额(开+平)
|
||||
|
||||
avg_hold = sum(t[6] for t in trades) / n
|
||||
wr = len(wins) / n * 100
|
||||
|
||||
avg_win = sum(t[3] for t in wins) / len(wins) if wins else 0
|
||||
avg_loss = sum(t[3] for t in losses) / len(losses) if losses else 0
|
||||
best = max(t[3] for t in trades)
|
||||
worst = min(t[3] for t in trades)
|
||||
|
||||
pf_num = sum(t[3] for t in wins) if wins else 0
|
||||
pf_den = abs(sum(t[3] for t in losses)) if losses else 0
|
||||
pf = pf_num / pf_den if pf_den > 0 else float('inf')
|
||||
|
||||
long_t = [t for t in trades if t[0] == 'long']
|
||||
short_t = [t for t in trades if t[0] == 'short']
|
||||
long_wr = len([t for t in long_t if t[3] > 0]) / len(long_t) * 100 if long_t else 0
|
||||
short_wr = len([t for t in short_t if t[3] > 0]) / len(short_t) * 100 if short_t else 0
|
||||
|
||||
# 连续亏损
|
||||
max_streak = 0; cur = 0
|
||||
for t in trades:
|
||||
if t[3] <= 0: cur += 1; max_streak = max(max_streak, cur)
|
||||
else: cur = 0
|
||||
|
||||
# 最大回撤(基于累计净利润)
|
||||
cum = 0; peak = 0; max_dd = 0
|
||||
for t in trades:
|
||||
net_t = t[3] - (t[4] - t[5]) # pnl - net_fee
|
||||
cum += net_t
|
||||
if cum > peak: peak = cum
|
||||
dd = peak - cum
|
||||
if dd > max_dd: max_dd = dd
|
||||
|
||||
# 平仓原因
|
||||
reasons = {}
|
||||
for t in trades:
|
||||
r = t[7].split('(')[0]
|
||||
reasons[r] = reasons.get(r, 0) + 1
|
||||
|
||||
print("=" * 70, flush=True)
|
||||
print(" 回测结果", flush=True)
|
||||
print("=" * 70, flush=True)
|
||||
|
||||
print(f"\n --- 核心收益 ---", flush=True)
|
||||
print(f" 方向交易盈亏: {total_pnl:>+12.2f} USDT", flush=True)
|
||||
print(f" 总手续费: {total_fee:>12.2f} USDT", flush=True)
|
||||
print(f" 返佣收入(90%): {total_rebate:>+12.2f} USDT", flush=True)
|
||||
print(f" 净手续费(10%): {net_fee:>12.2f} USDT", flush=True)
|
||||
print(f" ================================", flush=True)
|
||||
print(f" 最终净利润: {net_profit:>+12.2f} USDT", flush=True)
|
||||
print(f" 最大回撤: {max_dd:>12.2f} USDT", flush=True)
|
||||
|
||||
print(f"\n --- 交易统计 ---", flush=True)
|
||||
print(f" 总交易次数: {n:>8} 笔", flush=True)
|
||||
print(f" 盈利笔数: {len(wins):>8} 笔 ({wr:.1f}%)", flush=True)
|
||||
print(f" 亏损笔数: {len(losses):>8} 笔 ({100-wr:.1f}%)", flush=True)
|
||||
print(f" 做多: {len(long_t):>8} 笔 (胜率 {long_wr:.1f}%)", flush=True)
|
||||
print(f" 做空: {len(short_t):>8} 笔 (胜率 {short_wr:.1f}%)", flush=True)
|
||||
print(f" 盈亏比: {pf:>8.2f}", flush=True)
|
||||
print(f" 最大连亏: {max_streak:>8} 笔", flush=True)
|
||||
|
||||
print(f"\n --- 单笔详情 ---", flush=True)
|
||||
print(f" 每笔保证金: {MARGIN:>8.0f} USDT", flush=True)
|
||||
print(f" 每笔名义价值: {NOTIONAL:>8,.0f} USDT", flush=True)
|
||||
print(f" 平均盈利: {avg_win:>+12.2f} USDT", flush=True)
|
||||
print(f" 平均亏损: {avg_loss:>+12.2f} USDT", flush=True)
|
||||
print(f" 最大单笔盈利: {best:>+12.2f} USDT", flush=True)
|
||||
print(f" 最大单笔亏损: {worst:>+12.2f} USDT", flush=True)
|
||||
print(f" 平均持仓: {avg_hold:>8.0f} 秒 ({avg_hold/60:.1f}分钟)", flush=True)
|
||||
|
||||
print(f"\n --- 费用明细 ---", flush=True)
|
||||
print(f" 总交易额: {total_volume:>12,.0f} USDT", flush=True)
|
||||
per_trade_fee = NOTIONAL * TAKER_FEE * 2
|
||||
per_trade_reb = per_trade_fee * REBATE_RATE
|
||||
per_trade_net_fee = per_trade_fee - per_trade_reb
|
||||
print(f" 每笔手续费: {per_trade_fee:>12.2f} USDT", flush=True)
|
||||
print(f" 每笔返佣: {per_trade_reb:>+12.2f} USDT", flush=True)
|
||||
print(f" 每笔净费用: {per_trade_net_fee:>12.2f} USDT", flush=True)
|
||||
|
||||
print(f"\n --- 平仓原因 ---", flush=True)
|
||||
for r, c in sorted(reasons.items(), key=lambda x: -x[1]):
|
||||
print(f" {r:<16} {c:>5} 笔 ({c/n*100:.1f}%)", flush=True)
|
||||
|
||||
# 月度统计
|
||||
print(f"\n --- 月度明细 ---", flush=True)
|
||||
print(f" {'月份':<8} {'笔数':>5} {'方向盈亏':>10} {'返佣':>10} {'净利润':>10} {'胜率':>6}", flush=True)
|
||||
print(f" {'-'*55}", flush=True)
|
||||
|
||||
monthly = {}
|
||||
for t in trades:
|
||||
k = t[9].strftime('%Y-%m') # 用平仓时间
|
||||
if k not in monthly:
|
||||
monthly[k] = {'n': 0, 'pnl': 0, 'reb': 0, 'fee': 0, 'w': 0}
|
||||
monthly[k]['n'] += 1
|
||||
monthly[k]['pnl'] += t[3]
|
||||
monthly[k]['reb'] += t[5]
|
||||
monthly[k]['fee'] += t[4]
|
||||
if t[3] > 0: monthly[k]['w'] += 1
|
||||
|
||||
for m in sorted(monthly.keys()):
|
||||
d = monthly[m]
|
||||
net_m = d['pnl'] - (d['fee'] - d['reb']) # 方向盈亏 - 净手续费
|
||||
wr_m = d['w'] / d['n'] * 100 if d['n'] > 0 else 0
|
||||
print(f" {m:<8} {d['n']:>5} {d['pnl']:>+10.2f} {d['reb']:>10.2f} {net_m:>+10.2f} {wr_m:>5.1f}%", flush=True)
|
||||
|
||||
# 年度汇总
|
||||
print(f" {'-'*55}", flush=True)
|
||||
print(f" {'合计':<8} {n:>5} {total_pnl:>+10.2f} {total_rebate:>10.2f} {net_profit:>+10.2f} {wr:>5.1f}%", flush=True)
|
||||
|
||||
print(f"\n{'='*70}", flush=True)
|
||||
|
||||
# 保存交易记录
|
||||
csv_path = Path(__file__).parent.parent / '100u_trades.csv'
|
||||
with open(csv_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("开仓时间,平仓时间,方向,开仓价,平仓价,名义价值,方向盈亏,手续费,返佣,净盈亏,持仓秒,原因\n")
|
||||
for t in trades:
|
||||
net_t = t[3] - (t[4] - t[5])
|
||||
f.write(f"{t[8]},{t[9]},{t[0]},{t[1]:.2f},{t[2]:.2f},{NOTIONAL:.0f},"
|
||||
f"{t[3]:.2f},{t[4]:.2f},{t[5]:.2f},{net_t:.2f},{t[6]:.0f},{t[7]}\n")
|
||||
print(f"\n 交易记录已保存: {csv_path}", flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
376
交易/bitmart-AI优化回测.py
Normal file
376
交易/bitmart-AI优化回测.py
Normal file
@@ -0,0 +1,376 @@
|
||||
"""
|
||||
AI策略优化 v2 — 目标: 100U保证金达到1000U/月
|
||||
|
||||
优化方向:
|
||||
1. 多时间框架特征: 加入5分钟/15分钟聚合K线指标
|
||||
2. Ensemble: LightGBM + RandomForest 投票
|
||||
3. 更长训练窗口: 4个月 vs 3个月
|
||||
4. 高置信度过滤: 只在双模型一致时交易
|
||||
5. 动态止盈: 用ATR倍数而非固定比例
|
||||
6. 更多K线形态特征: 连续涨跌、缺口、波动率变化率
|
||||
7. 扫描最优参数组合
|
||||
"""
|
||||
import datetime, sqlite3, time as _time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
def load_data():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp())*1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp())*1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
df = pd.read_sql_query(
|
||||
f"SELECT id as ts,open,high,low,close FROM bitmart_eth_1m WHERE id>={s} AND id<{e} ORDER BY id", conn)
|
||||
conn.close()
|
||||
df['datetime'] = pd.to_datetime(df['ts'], unit='ms')
|
||||
df.set_index('datetime', inplace=True)
|
||||
return df
|
||||
|
||||
def add_features(df):
|
||||
c=df['close']; h=df['high']; l=df['low']; o=df['open']
|
||||
|
||||
# === 1分钟基础指标 ===
|
||||
for p in [5,8,13,21,50,120]:
|
||||
df[f'ema_{p}'] = c.ewm(span=p, adjust=False).mean()
|
||||
df['ema_fast_slow'] = (df['ema_8']-df['ema_21'])/c
|
||||
df['ema_slow_big'] = (df['ema_21']-df['ema_120'])/c
|
||||
df['price_vs_ema120'] = (c-df['ema_120'])/c
|
||||
df['price_vs_ema50'] = (c-df['ema_50'])/c
|
||||
df['ema8_slope'] = df['ema_8'].pct_change(5)
|
||||
df['ema21_slope'] = df['ema_21'].pct_change(5)
|
||||
df['ema120_slope'] = df['ema_120'].pct_change(20)
|
||||
|
||||
# 三线排列
|
||||
df['triple_bull'] = ((df['ema_8']>df['ema_21'])&(df['ema_21']>df['ema_120'])).astype(float)
|
||||
df['triple_bear'] = ((df['ema_8']<df['ema_21'])&(df['ema_21']<df['ema_120'])).astype(float)
|
||||
|
||||
# RSI
|
||||
delta = c.diff(); gain = delta.clip(lower=0); loss = (-delta).clip(lower=0)
|
||||
for p in [7,14,21]:
|
||||
ag=gain.rolling(p).mean(); al=loss.rolling(p).mean()
|
||||
df[f'rsi_{p}'] = 100 - 100/(1+ag/al.replace(0,np.nan))
|
||||
df['rsi_14_slope'] = df['rsi_14'].diff(5) # RSI变化率
|
||||
|
||||
# BB
|
||||
mid=c.rolling(20).mean(); std=c.rolling(20).std()
|
||||
df['bb_pct'] = (c-(mid-2*std))/((mid+2*std)-(mid-2*std)).replace(0,np.nan)
|
||||
df['bb_width'] = 4*std/mid
|
||||
df['bb_width_change'] = df['bb_width'].pct_change(10) # 波动率变化
|
||||
|
||||
# MACD
|
||||
ema12=c.ewm(span=12,adjust=False).mean(); ema26=c.ewm(span=26,adjust=False).mean()
|
||||
df['macd'] = (ema12-ema26)/c
|
||||
df['macd_signal'] = df['macd'].ewm(span=9,adjust=False).mean()
|
||||
df['macd_hist'] = df['macd']-df['macd_signal']
|
||||
df['macd_hist_slope'] = df['macd_hist'].diff(3) # MACD柱变化
|
||||
|
||||
# ATR
|
||||
tr = pd.concat([h-l,(h-c.shift(1)).abs(),(l-c.shift(1)).abs()],axis=1).max(axis=1)
|
||||
df['atr_pct'] = tr.rolling(14).mean()/c
|
||||
df['atr_7'] = tr.rolling(7).mean()/c
|
||||
df['atr_ratio'] = df['atr_7']/df['atr_pct'].replace(0,np.nan) # 短期/长期ATR
|
||||
|
||||
# Stochastic
|
||||
for p in [14,28]:
|
||||
low_p=l.rolling(p).min(); high_p=h.rolling(p).max()
|
||||
df[f'stoch_k_{p}'] = (c-low_p)/(high_p-low_p).replace(0,np.nan)*100
|
||||
df['stoch_d_14'] = df['stoch_k_14'].rolling(3).mean()
|
||||
|
||||
# 动量
|
||||
for p in [1,3,5,10,20,60,120]:
|
||||
df[f'ret_{p}'] = c.pct_change(p)
|
||||
|
||||
# 波动率
|
||||
df['vol_5'] = c.pct_change().rolling(5).std()
|
||||
df['vol_20'] = c.pct_change().rolling(20).std()
|
||||
df['vol_60'] = c.pct_change().rolling(60).std()
|
||||
df['vol_ratio'] = df['vol_5']/df['vol_20'].replace(0,np.nan)
|
||||
df['vol_trend'] = df['vol_20'].pct_change(20) # 波动率趋势
|
||||
|
||||
# K线形态
|
||||
body = (c-o).abs()
|
||||
df['body_pct'] = body/c
|
||||
df['upper_shadow'] = (h-pd.concat([o,c],axis=1).max(axis=1))/c
|
||||
df['lower_shadow'] = (pd.concat([o,c],axis=1).min(axis=1)-l)/c
|
||||
df['body_vs_range'] = body/(h-l).replace(0,np.nan)
|
||||
df['is_bullish'] = (c>o).astype(float)
|
||||
df['range_pct'] = (h-l)/c # K线振幅
|
||||
|
||||
# 连续方向
|
||||
bullish = (c>o).astype(int)
|
||||
df['streak'] = bullish.groupby((bullish!=bullish.shift()).cumsum()).cumcount()+1
|
||||
df['streak'] = df['streak'] * bullish - df['streak'] * (1-bullish) # 正=连阳, 负=连阴
|
||||
|
||||
# 吞没/锤子
|
||||
prev_body = body.shift(1)
|
||||
df['engulf_ratio'] = body/prev_body.replace(0,np.nan)
|
||||
df['hammer'] = (df['lower_shadow']>df['body_pct']*2).astype(float)
|
||||
df['shooting_star'] = (df['upper_shadow']>df['body_pct']*2).astype(float)
|
||||
|
||||
# 价格位置
|
||||
for p in [20,60]:
|
||||
df[f'high_{p}'] = h.rolling(p).max()
|
||||
df[f'low_{p}'] = l.rolling(p).min()
|
||||
df[f'pos_{p}'] = (c-df[f'low_{p}'])/(df[f'high_{p}']-df[f'low_{p}']).replace(0,np.nan)
|
||||
|
||||
# === 多时间框架: 5分钟 ===
|
||||
c5 = c.resample('5min').last()
|
||||
h5 = h.resample('5min').max()
|
||||
l5 = l.resample('5min').min()
|
||||
o5 = o.resample('5min').first()
|
||||
|
||||
ema5_8 = c5.ewm(span=8,adjust=False).mean()
|
||||
ema5_21 = c5.ewm(span=21,adjust=False).mean()
|
||||
rsi5_14_delta = c5.diff()
|
||||
rsi5_g = rsi5_14_delta.clip(lower=0).rolling(14).mean()
|
||||
rsi5_l = (-rsi5_14_delta).clip(lower=0).rolling(14).mean()
|
||||
rsi5 = 100 - 100/(1+rsi5_g/rsi5_l.replace(0,np.nan))
|
||||
|
||||
# 5分钟指标 reindex 到1分钟
|
||||
df['ema5m_fast_slow'] = ((ema5_8-ema5_21)/c5).reindex(df.index, method='ffill')
|
||||
df['rsi5m_14'] = rsi5.reindex(df.index, method='ffill')
|
||||
tr5 = pd.concat([h5-l5,(h5-c5.shift(1)).abs(),(l5-c5.shift(1)).abs()],axis=1).max(axis=1)
|
||||
df['atr5m'] = (tr5.rolling(14).mean()/c5).reindex(df.index, method='ffill')
|
||||
df['ret5m_1'] = c5.pct_change(1).reindex(df.index, method='ffill')
|
||||
df['ret5m_5'] = c5.pct_change(5).reindex(df.index, method='ffill')
|
||||
df['ret5m_20'] = c5.pct_change(20).reindex(df.index, method='ffill')
|
||||
|
||||
# === 多时间框架: 15分钟 ===
|
||||
c15 = c.resample('15min').last()
|
||||
ema15_21 = c15.ewm(span=21,adjust=False).mean()
|
||||
df['ema15m_trend'] = ((c15-ema15_21)/c15).reindex(df.index, method='ffill')
|
||||
df['ret15m_5'] = c15.pct_change(5).reindex(df.index, method='ffill')
|
||||
|
||||
# 时间
|
||||
df['hour'] = df.index.hour
|
||||
df['minute'] = df.index.minute
|
||||
df['hour_sin'] = np.sin(2*np.pi*df['hour']/24)
|
||||
df['hour_cos'] = np.cos(2*np.pi*df['hour']/24)
|
||||
df['weekday'] = df.index.weekday
|
||||
|
||||
return df
|
||||
|
||||
def get_feature_cols(df):
|
||||
exclude = {'ts','open','high','low','close','label','month',
|
||||
'ema_5','ema_8','ema_13','ema_21','ema_50','ema_120',
|
||||
'high_20','low_20','high_60','low_60'}
|
||||
return [c for c in df.columns if c not in exclude
|
||||
and df[c].dtype in ('float64','float32','int64','int32')]
|
||||
|
||||
def train_ensemble(X_tr, y_tr, X_te, fcols):
|
||||
"""训练 LightGBM + GradientBoosting ensemble"""
|
||||
y_cls = y_tr + 1 # -1→0, 0→1, 1→2
|
||||
|
||||
# Model 1: LightGBM
|
||||
params = {
|
||||
'objective':'multiclass','num_class':3,'metric':'multi_logloss',
|
||||
'learning_rate':0.03,'num_leaves':63,'max_depth':8,
|
||||
'min_child_samples':100,'subsample':0.7,'colsample_bytree':0.7,
|
||||
'reg_alpha':0.5,'reg_lambda':0.5,'verbose':-1,'n_jobs':-1,'seed':42
|
||||
}
|
||||
dt_ = lgb.Dataset(X_tr, label=y_cls)
|
||||
m1 = lgb.train(params, dt_, num_boost_round=300)
|
||||
p1 = m1.predict(X_te) # (n, 3)
|
||||
|
||||
# Model 2: GradientBoosting (sklearn)
|
||||
m2 = GradientBoostingClassifier(
|
||||
n_estimators=150, max_depth=5, learning_rate=0.05,
|
||||
subsample=0.8, min_samples_leaf=50, random_state=42
|
||||
)
|
||||
m2.fit(X_tr, y_cls)
|
||||
p2 = m2.predict_proba(X_te) # (n, 3)
|
||||
|
||||
# Ensemble: 加权平均 (LightGBM权重更高)
|
||||
proba = p1 * 0.6 + p2 * 0.4
|
||||
return proba, m1
|
||||
|
||||
def backtest(df, pl, ps, notional, prob_th, sl_pct, tp_pct, max_hold, use_atr_tp=False):
|
||||
FEE = notional*0.0006*2; REB=FEE*0.9; NFEE=FEE-REB
|
||||
pos=0; op=0.0; ot=None; trades=[]; atr_at_open=0
|
||||
|
||||
for i in range(len(df)):
|
||||
dt=df.index[i]; p=df['close'].iloc[i]; p_l=pl.iloc[i]; p_s=ps.iloc[i]
|
||||
atr_val = df['atr_pct'].iloc[i] if 'atr_pct' in df.columns else 0.002
|
||||
|
||||
if pos!=0 and ot is not None:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
|
||||
# 动态止盈(ATR倍数)
|
||||
if use_atr_tp and atr_at_open > 0:
|
||||
dyn_tp = atr_at_open * 2.5 # 2.5倍ATR止盈
|
||||
dyn_tp = max(dyn_tp, tp_pct) # 不低于固定TP
|
||||
else:
|
||||
dyn_tp = tp_pct
|
||||
|
||||
hard_sl = max(sl_pct*1.5, 0.006)
|
||||
if -pp>=hard_sl:
|
||||
trades.append((pos,op,p,notional*pp,hsec,'硬止损',ot,dt)); pos=0; continue
|
||||
if hsec>=200:
|
||||
if -pp>=sl_pct:
|
||||
trades.append((pos,op,p,notional*pp,hsec,'止损',ot,dt)); pos=0; continue
|
||||
if pp>=dyn_tp:
|
||||
trades.append((pos,op,p,notional*pp,hsec,'止盈',ot,dt)); pos=0; continue
|
||||
if hsec>=max_hold:
|
||||
trades.append((pos,op,p,notional*pp,hsec,'超时',ot,dt)); pos=0; continue
|
||||
# 模型反转
|
||||
if pos==1 and p_s>prob_th+0.08:
|
||||
trades.append((pos,op,p,notional*pp,hsec,'AI反转',ot,dt)); pos=0
|
||||
elif pos==-1 and p_l>prob_th+0.08:
|
||||
trades.append((pos,op,p,notional*pp,hsec,'AI反转',ot,dt)); pos=0
|
||||
|
||||
if pos==0:
|
||||
if p_l>prob_th and p_l>p_s+0.03: # 要求概率差距>3%
|
||||
pos=1; op=p; ot=dt; atr_at_open=atr_val
|
||||
elif p_s>prob_th and p_s>p_l+0.03:
|
||||
pos=-1; op=p; ot=dt; atr_at_open=atr_val
|
||||
|
||||
if pos!=0:
|
||||
p=df['close'].iloc[-1]; dt=df.index[-1]
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
trades.append((pos,op,p,notional*pp,(dt-ot).total_seconds(),'end',ot,dt))
|
||||
return trades
|
||||
|
||||
def analyze(trades, notional, label):
|
||||
if not trades: print(f" [{label}] No trades"); return 0, {}
|
||||
n=len(trades)
|
||||
FEE=notional*0.0006*2; REB=FEE*0.9; NFEE=FEE-REB
|
||||
tpnl=sum(t[3] for t in trades); net=tpnl-NFEE*n; treb=REB*n
|
||||
wins=len([t for t in trades if t[3]>0]); wr=wins/n*100 if n else 0
|
||||
|
||||
monthly=defaultdict(lambda:{'n':0,'net':0,'w':0})
|
||||
for t in trades:
|
||||
k=t[7].strftime('%Y-%m')
|
||||
monthly[k]['n']+=1; monthly[k]['net']+=t[3]-NFEE
|
||||
if t[3]>0: monthly[k]['w']+=1
|
||||
|
||||
cum=0;peak=0;dd=0
|
||||
for t in trades:
|
||||
cum+=t[3]-NFEE
|
||||
if cum>peak:peak=cum
|
||||
if peak-cum>dd:dd=peak-cum
|
||||
|
||||
pm=len([m for m in monthly.values() if m['net']>0])
|
||||
min_m=min(monthly.values(),key=lambda x:x['net'])['net'] if monthly else 0
|
||||
max_m=max(monthly.values(),key=lambda x:x['net'])['net'] if monthly else 0
|
||||
|
||||
return net, {'n':n,'wr':wr,'pm':pm,'dd':dd,'treb':treb,'min_m':min_m,'max_m':max_m,'monthly':monthly}
|
||||
|
||||
def main():
|
||||
t0 = _time.time()
|
||||
print("="*70, flush=True)
|
||||
print(" AI策略优化 v2 — Ensemble + 多时间框架 + 60+特征", flush=True)
|
||||
print(" 100U保证金 × 100倍 = 10,000U名义", flush=True)
|
||||
print("="*70, flush=True)
|
||||
|
||||
df = load_data()
|
||||
print(f" {len(df):,} bars", flush=True)
|
||||
|
||||
df = add_features(df)
|
||||
fcols = get_feature_cols(df)
|
||||
print(f" {len(fcols)} features", flush=True)
|
||||
|
||||
NOTIONAL = 10000.0
|
||||
|
||||
# 测试多种配置
|
||||
configs = [
|
||||
# (fb, thresh, prob_th, sl, tp, max_hold, use_atr_tp, train_m, label)
|
||||
(10, 0.003, 0.45, 0.005, 0.008, 1800, False, 3, "v1: 基线(上轮最佳)"),
|
||||
(10, 0.003, 0.48, 0.005, 0.008, 1800, False, 3, "v2: 高置信0.48"),
|
||||
(10, 0.003, 0.50, 0.005, 0.010, 2400, False, 3, "v3: 超高置信0.50 大TP"),
|
||||
(10, 0.003, 0.45, 0.005, 0.010, 2400, True, 3, "v4: ATR动态止盈"),
|
||||
(10, 0.003, 0.48, 0.006, 0.010, 2400, True, 3, "v5: 高置信+ATR+宽SL"),
|
||||
(15, 0.004, 0.45, 0.006, 0.010, 2400, True, 3, "v6: 15bar前瞻 大波动"),
|
||||
(10, 0.003, 0.45, 0.005, 0.008, 1800, False, 4, "v7: 4月训练窗口"),
|
||||
(10, 0.003, 0.48, 0.005, 0.010, 2400, True, 4, "v8: 4月+高置信+ATR"),
|
||||
]
|
||||
|
||||
results = []
|
||||
|
||||
for fb, thresh, prob_th, sl, tp, mh, use_atr, train_m, label in configs:
|
||||
print(f"\n--- {label} ---", flush=True)
|
||||
print(f" 前瞻={fb} 阈值={thresh*100:.1f}% prob>{prob_th} SL={sl*100:.1f}% TP={tp*100:.1f}% MH={mh}s ATR_TP={use_atr} train={train_m}m", flush=True)
|
||||
|
||||
# 标签
|
||||
df_t = df.copy()
|
||||
future_ret = df_t['close'].shift(-fb)/df_t['close'] - 1
|
||||
df_t['label'] = 0
|
||||
df_t.loc[future_ret > thresh, 'label'] = 1
|
||||
df_t.loc[future_ret < -thresh, 'label'] = -1
|
||||
|
||||
df_t['month'] = df_t.index.to_period('M')
|
||||
months = sorted(df_t['month'].unique())
|
||||
|
||||
pl = pd.Series(index=df_t.index, dtype=float); pl[:] = 0.0
|
||||
ps = pd.Series(index=df_t.index, dtype=float); ps[:] = 0.0
|
||||
|
||||
for mi in range(train_m, len(months)):
|
||||
tm = months[mi]; ts_ = months[mi-train_m]
|
||||
tr_mask = (df_t['month']>=ts_) & (df_t['month']<tm)
|
||||
te_mask = df_t['month']==tm
|
||||
tr_df = df_t[tr_mask].dropna(subset=fcols+['label'])
|
||||
te_df = df_t[te_mask].dropna(subset=fcols)
|
||||
if len(tr_df)<1000 or len(te_df)<100: continue
|
||||
|
||||
proba, _ = train_ensemble(tr_df[fcols].values, tr_df['label'].values, te_df[fcols].values, fcols)
|
||||
pl.loc[te_df.index] = proba[:,2]
|
||||
ps.loc[te_df.index] = proba[:,0]
|
||||
|
||||
# 回测
|
||||
trades = backtest(df_t, pl, ps, NOTIONAL, prob_th, sl, tp, mh, use_atr)
|
||||
net, info = analyze(trades, NOTIONAL, label)
|
||||
|
||||
if info:
|
||||
print(f" 净利={net:+.0f} 交易={info['n']} 胜率={info['wr']:.1f}% 盈利月={info['pm']}/12 回撤={info['dd']:.0f}", flush=True)
|
||||
# 月度简览
|
||||
for m in sorted(info['monthly'].keys()):
|
||||
d = info['monthly'][m]
|
||||
s = "+" if d['net']>0 else "-"
|
||||
print(f" {m}: {d['net']:>+6.0f} ({d['n']}笔) {s}", flush=True)
|
||||
results.append((label, net, info))
|
||||
|
||||
# === 总览 ===
|
||||
elapsed = _time.time()-t0
|
||||
results.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
print(f"\n\n{'='*80}", flush=True)
|
||||
print(f" 总览 | 100U保证金 × 100倍 | 耗时 {elapsed:.0f}s", flush=True)
|
||||
print(f"{'='*80}", flush=True)
|
||||
print(f" {'方案':<30} {'年净利':>8} {'月均':>6} {'交易':>5} {'胜率':>5} {'盈月':>4} {'回撤':>6}", flush=True)
|
||||
print(f" {'-'*72}", flush=True)
|
||||
|
||||
for label, net, info in results:
|
||||
if not info: continue
|
||||
mavg = net/12
|
||||
print(f" {label:<30} {net:>+8.0f} {mavg:>+6.0f} {info['n']:>5} {info['wr']:>4.1f}% {info['pm']:>2}/12 {info['dd']:>6.0f}", flush=True)
|
||||
|
||||
best = results[0]
|
||||
print(f"\n 最佳: {best[0]}", flush=True)
|
||||
print(f" 年净利: {best[1]:+.0f} USDT = 月均 {best[1]/12:+.0f} USDT", flush=True)
|
||||
|
||||
if best[2]:
|
||||
print(f"\n 最佳方案月度:", flush=True)
|
||||
for m in sorted(best[2]['monthly'].keys()):
|
||||
d = best[2]['monthly'][m]
|
||||
wr_m = d['w']/d['n']*100 if d['n']>0 else 0
|
||||
print(f" {m}: {d['n']:>4}笔 {d['net']:>+8.0f}U [{('盈利' if d['net']>0 else '亏损')}]", flush=True)
|
||||
|
||||
print(f"\n 对比基线(LightGBM v1): +4801/年 = +400/月", flush=True)
|
||||
if best[1] > 4801:
|
||||
print(f" 优化提升: {(best[1]/4801-1)*100:+.0f}%", flush=True)
|
||||
print(f"{'='*80}", flush=True)
|
||||
|
||||
# 保存最佳交易
|
||||
if best[2]:
|
||||
# 重跑最佳配置保存CSV
|
||||
csv = Path(__file__).parent.parent / 'ai_v2_best.csv'
|
||||
print(f" Results saved summary to console.", flush=True)
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
316
交易/bitmart-AI快速优化.py
Normal file
316
交易/bitmart-AI快速优化.py
Normal file
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
AI策略快速优化 — 只用LightGBM,多时间框架特征,扫描参数
|
||||
|
||||
优化点 vs v1:
|
||||
1. 63个特征(加5m/15m多时间框架)
|
||||
2. 更强LightGBM参数(更多树+更深)
|
||||
3. 扫描: 概率阈值/止损/止盈/前瞻期/持仓时间
|
||||
4. 要求多空概率差距>3%才开仓(减少弱信号)
|
||||
5. 动态ATR止盈选项
|
||||
|
||||
固定: 100U保证金, 100x杠杆, 10,000U名义, 90%返佣
|
||||
"""
|
||||
import datetime, sqlite3, time as _time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
def load_data():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp())*1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp())*1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
df = pd.read_sql_query(
|
||||
f"SELECT id as ts,open,high,low,close FROM bitmart_eth_1m WHERE id>={s} AND id<{e} ORDER BY id", conn)
|
||||
conn.close()
|
||||
df['datetime'] = pd.to_datetime(df['ts'], unit='ms')
|
||||
df.set_index('datetime', inplace=True)
|
||||
return df
|
||||
|
||||
def add_features(df):
|
||||
c=df['close']; h=df['high']; l=df['low']; o=df['open']
|
||||
|
||||
for p in [5,8,13,21,50,120]:
|
||||
df[f'ema_{p}'] = c.ewm(span=p, adjust=False).mean()
|
||||
df['ema_fast_slow'] = (df['ema_8']-df['ema_21'])/c
|
||||
df['ema_slow_big'] = (df['ema_21']-df['ema_120'])/c
|
||||
df['price_vs_ema120'] = (c-df['ema_120'])/c
|
||||
df['price_vs_ema50'] = (c-df['ema_50'])/c
|
||||
df['ema8_slope'] = df['ema_8'].pct_change(5)
|
||||
df['ema21_slope'] = df['ema_21'].pct_change(5)
|
||||
df['ema120_slope'] = df['ema_120'].pct_change(20)
|
||||
df['triple_bull'] = ((df['ema_8']>df['ema_21'])&(df['ema_21']>df['ema_120'])).astype(float)
|
||||
df['triple_bear'] = ((df['ema_8']<df['ema_21'])&(df['ema_21']<df['ema_120'])).astype(float)
|
||||
|
||||
delta = c.diff(); gain = delta.clip(lower=0); loss = (-delta).clip(lower=0)
|
||||
for p in [7,14,21]:
|
||||
ag=gain.rolling(p).mean(); al=loss.rolling(p).mean()
|
||||
df[f'rsi_{p}'] = 100 - 100/(1+ag/al.replace(0,np.nan))
|
||||
df['rsi_14_slope'] = df['rsi_14'].diff(5)
|
||||
|
||||
mid=c.rolling(20).mean(); std=c.rolling(20).std()
|
||||
df['bb_pct'] = (c-(mid-2*std))/((mid+2*std)-(mid-2*std)).replace(0,np.nan)
|
||||
df['bb_width'] = 4*std/mid
|
||||
df['bb_width_chg'] = df['bb_width'].pct_change(10)
|
||||
|
||||
ema12=c.ewm(span=12,adjust=False).mean(); ema26=c.ewm(span=26,adjust=False).mean()
|
||||
df['macd'] = (ema12-ema26)/c
|
||||
df['macd_signal'] = df['macd'].ewm(span=9,adjust=False).mean()
|
||||
df['macd_hist'] = df['macd']-df['macd_signal']
|
||||
df['macd_hist_slope'] = df['macd_hist'].diff(3)
|
||||
|
||||
tr = pd.concat([h-l,(h-c.shift(1)).abs(),(l-c.shift(1)).abs()],axis=1).max(axis=1)
|
||||
df['atr_pct'] = tr.rolling(14).mean()/c
|
||||
df['atr_7'] = tr.rolling(7).mean()/c
|
||||
df['atr_ratio'] = df['atr_7']/df['atr_pct'].replace(0,np.nan)
|
||||
|
||||
for p in [14,28]:
|
||||
low_p=l.rolling(p).min(); high_p=h.rolling(p).max()
|
||||
df[f'stoch_k_{p}'] = (c-low_p)/(high_p-low_p).replace(0,np.nan)*100
|
||||
df['stoch_d_14'] = df['stoch_k_14'].rolling(3).mean()
|
||||
|
||||
for p in [1,3,5,10,20,60,120]:
|
||||
df[f'ret_{p}'] = c.pct_change(p)
|
||||
|
||||
df['vol_5'] = c.pct_change().rolling(5).std()
|
||||
df['vol_20'] = c.pct_change().rolling(20).std()
|
||||
df['vol_60'] = c.pct_change().rolling(60).std()
|
||||
df['vol_ratio'] = df['vol_5']/df['vol_20'].replace(0,np.nan)
|
||||
df['vol_trend'] = df['vol_20'].pct_change(20)
|
||||
|
||||
body = (c-o).abs()
|
||||
df['body_pct'] = body/c
|
||||
df['upper_shadow'] = (h-pd.concat([o,c],axis=1).max(axis=1))/c
|
||||
df['lower_shadow'] = (pd.concat([o,c],axis=1).min(axis=1)-l)/c
|
||||
df['body_vs_range'] = body/(h-l).replace(0,np.nan)
|
||||
df['range_pct'] = (h-l)/c
|
||||
bullish = (c>o).astype(int)
|
||||
df['streak'] = bullish.groupby((bullish!=bullish.shift()).cumsum()).cumcount()+1
|
||||
df['streak'] = df['streak'] * bullish - df['streak'] * (1-bullish)
|
||||
df['engulf_ratio'] = body/body.shift(1).replace(0,np.nan)
|
||||
|
||||
for p in [20,60]:
|
||||
df[f'high_{p}'] = h.rolling(p).max()
|
||||
df[f'low_{p}'] = l.rolling(p).min()
|
||||
df[f'pos_{p}'] = (c-df[f'low_{p}'])/(df[f'high_{p}']-df[f'low_{p}']).replace(0,np.nan)
|
||||
|
||||
# 5分钟
|
||||
c5=c.resample('5min').last(); h5=h.resample('5min').max()
|
||||
l5=l.resample('5min').min(); o5=o.resample('5min').first()
|
||||
e5_8=c5.ewm(span=8,adjust=False).mean(); e5_21=c5.ewm(span=21,adjust=False).mean()
|
||||
df['ema5m_fs'] = ((e5_8-e5_21)/c5).reindex(df.index, method='ffill')
|
||||
d5=c5.diff(); g5=d5.clip(lower=0).rolling(14).mean(); l5r=(-d5).clip(lower=0).rolling(14).mean()
|
||||
df['rsi5m'] = (100-100/(1+g5/l5r.replace(0,np.nan))).reindex(df.index, method='ffill')
|
||||
tr5=pd.concat([h5-l5,(h5-c5.shift(1)).abs(),(l5-c5.shift(1)).abs()],axis=1).max(axis=1)
|
||||
df['atr5m'] = (tr5.rolling(14).mean()/c5).reindex(df.index, method='ffill')
|
||||
for p in [1,5,20]:
|
||||
df[f'ret5m_{p}'] = c5.pct_change(p).reindex(df.index, method='ffill')
|
||||
|
||||
# 15分钟
|
||||
c15=c.resample('15min').last()
|
||||
e15=c15.ewm(span=21,adjust=False).mean()
|
||||
df['ema15m_trend'] = ((c15-e15)/c15).reindex(df.index, method='ffill')
|
||||
df['ret15m_5'] = c15.pct_change(5).reindex(df.index, method='ffill')
|
||||
|
||||
df['hour'] = df.index.hour; df['minute'] = df.index.minute
|
||||
df['hour_sin'] = np.sin(2*np.pi*df['hour']/24)
|
||||
df['hour_cos'] = np.cos(2*np.pi*df['hour']/24)
|
||||
df['weekday'] = df.index.weekday
|
||||
|
||||
return df
|
||||
|
||||
def get_fcols(df):
|
||||
exclude = {'ts','open','high','low','close','label','month',
|
||||
'ema_5','ema_8','ema_13','ema_21','ema_50','ema_120',
|
||||
'high_20','low_20','high_60','low_60'}
|
||||
return [c for c in df.columns if c not in exclude
|
||||
and df[c].dtype in ('float64','float32','int64','int32')]
|
||||
|
||||
def train_predict(df, fcols, fb, thresh, train_m=3):
|
||||
future_ret = df['close'].shift(-fb)/df['close'] - 1
|
||||
df['label'] = 0
|
||||
df.loc[future_ret > thresh, 'label'] = 1
|
||||
df.loc[future_ret < -thresh, 'label'] = -1
|
||||
|
||||
df['month'] = df.index.to_period('M')
|
||||
months = sorted(df['month'].unique())
|
||||
|
||||
pl = pd.Series(index=df.index, dtype=float); pl[:] = 0.0
|
||||
ps = pd.Series(index=df.index, dtype=float); ps[:] = 0.0
|
||||
|
||||
params = {
|
||||
'objective':'multiclass','num_class':3,'metric':'multi_logloss',
|
||||
'learning_rate':0.03,'num_leaves':63,'max_depth':8,
|
||||
'min_child_samples':80,'subsample':0.7,'colsample_bytree':0.7,
|
||||
'reg_alpha':0.3,'reg_lambda':0.3,'verbose':-1,'n_jobs':-1,'seed':42
|
||||
}
|
||||
|
||||
for i in range(train_m, len(months)):
|
||||
tm = months[i]; ts_ = months[i-train_m]
|
||||
tr_mask = (df['month']>=ts_)&(df['month']<tm)
|
||||
te_mask = df['month']==tm
|
||||
tr_df = df[tr_mask].dropna(subset=fcols+['label'])
|
||||
te_df = df[te_mask].dropna(subset=fcols)
|
||||
if len(tr_df)<1000 or len(te_df)<100: continue
|
||||
dt_ = lgb.Dataset(tr_df[fcols].values, label=tr_df['label'].values+1)
|
||||
model = lgb.train(params, dt_, num_boost_round=300)
|
||||
proba = model.predict(te_df[fcols].values)
|
||||
pl.loc[te_df.index] = proba[:,2]
|
||||
ps.loc[te_df.index] = proba[:,0]
|
||||
|
||||
return pl, ps
|
||||
|
||||
def backtest(df, pl, ps, prob_th, sl, tp, mh, gap=0.03):
|
||||
NOTIONAL=10000.0; FEE=NOTIONAL*0.0006*2; REB=FEE*0.9; NFEE=FEE-REB
|
||||
pos=0; op=0.0; ot=None; trades=[]
|
||||
|
||||
for i in range(len(df)):
|
||||
dt=df.index[i]; p=df['close'].iloc[i]; p_l=pl.iloc[i]; p_s=ps.iloc[i]
|
||||
if pos!=0 and ot is not None:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
hard_sl=max(sl*1.5,0.006)
|
||||
if -pp>=hard_sl: trades.append((pos,op,p,NOTIONAL*pp,hsec,'hsl',ot,dt));pos=0;continue
|
||||
if hsec>=200:
|
||||
if -pp>=sl: trades.append((pos,op,p,NOTIONAL*pp,hsec,'sl',ot,dt));pos=0;continue
|
||||
if pp>=tp: trades.append((pos,op,p,NOTIONAL*pp,hsec,'tp',ot,dt));pos=0;continue
|
||||
if hsec>=mh: trades.append((pos,op,p,NOTIONAL*pp,hsec,'to',ot,dt));pos=0;continue
|
||||
if pos==1 and p_s>prob_th+0.08:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'ai',ot,dt));pos=0
|
||||
elif pos==-1 and p_l>prob_th+0.08:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'ai',ot,dt));pos=0
|
||||
if pos==0:
|
||||
if p_l>prob_th and p_l>p_s+gap: pos=1;op=p;ot=dt
|
||||
elif p_s>prob_th and p_s>p_l+gap: pos=-1;op=p;ot=dt
|
||||
if pos!=0:
|
||||
p=df['close'].iloc[-1];dt=df.index[-1]
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
trades.append((pos,op,p,NOTIONAL*pp,(dt-ot).total_seconds(),'end',ot,dt))
|
||||
return trades
|
||||
|
||||
def score(trades):
|
||||
if not trades: return 0, 0, 0, 0, {}
|
||||
NFEE=1.2; n=len(trades)
|
||||
tpnl=sum(t[3] for t in trades); net=tpnl-NFEE*n
|
||||
wins=len([t for t in trades if t[3]>0]); wr=wins/n*100
|
||||
cum=0;peak=0;dd=0
|
||||
monthly=defaultdict(lambda:{'n':0,'net':0,'w':0})
|
||||
for t in trades:
|
||||
cum+=t[3]-NFEE
|
||||
if cum>peak:peak=cum
|
||||
if peak-cum>dd:dd=peak-cum
|
||||
k=t[7].strftime('%Y-%m')
|
||||
monthly[k]['n']+=1;monthly[k]['net']+=t[3]-NFEE
|
||||
if t[3]>0:monthly[k]['w']+=1
|
||||
pm=len([m for m in monthly.values() if m['net']>0])
|
||||
return net, wr, pm, dd, monthly
|
||||
|
||||
def main():
|
||||
t0=_time.time()
|
||||
print("="*70, flush=True)
|
||||
print(" AI快速优化 | 100U x 100倍 | LightGBM + 63特征", flush=True)
|
||||
print("="*70, flush=True)
|
||||
|
||||
df = load_data()
|
||||
df = add_features(df)
|
||||
fcols = get_fcols(df)
|
||||
print(f" {len(df):,} bars, {len(fcols)} features\n", flush=True)
|
||||
|
||||
# 预训练不同前瞻/阈值的模型(最耗时的部分)
|
||||
model_configs = [
|
||||
(10, 0.003, 3, "10bar/0.3%/3m"),
|
||||
(10, 0.003, 4, "10bar/0.3%/4m"),
|
||||
(10, 0.004, 3, "10bar/0.4%/3m"),
|
||||
(15, 0.004, 3, "15bar/0.4%/3m"),
|
||||
(20, 0.005, 3, "20bar/0.5%/3m"),
|
||||
]
|
||||
|
||||
predictions = {}
|
||||
for fb, thresh, tm, lbl in model_configs:
|
||||
print(f" Training: {lbl}...", flush=True)
|
||||
dfc = df.copy()
|
||||
pl, ps = train_predict(dfc, fcols, fb, thresh, tm)
|
||||
predictions[lbl] = (pl, ps)
|
||||
# 快速检查
|
||||
t_ = backtest(dfc, pl, ps, 0.45, 0.005, 0.008, 1800)
|
||||
n_, _, _, _, _ = score(t_)
|
||||
print(f" quick check: {len(t_)} trades, net={n_:+.0f}", flush=True)
|
||||
|
||||
# 扫描回测参数
|
||||
print(f"\n Scanning backtest params...\n", flush=True)
|
||||
bt_configs = [
|
||||
# prob_th, sl, tp, max_hold, gap
|
||||
(0.42, 0.005, 0.008, 1800, 0.02),
|
||||
(0.45, 0.005, 0.008, 1800, 0.03),
|
||||
(0.48, 0.005, 0.008, 1800, 0.03),
|
||||
(0.45, 0.005, 0.010, 2400, 0.03),
|
||||
(0.48, 0.005, 0.010, 2400, 0.03),
|
||||
(0.50, 0.005, 0.010, 2400, 0.03),
|
||||
(0.45, 0.006, 0.012, 2400, 0.03),
|
||||
(0.48, 0.006, 0.012, 3600, 0.03),
|
||||
(0.50, 0.006, 0.015, 3600, 0.03),
|
||||
(0.45, 0.004, 0.008, 1800, 0.03),
|
||||
(0.42, 0.004, 0.006, 1200, 0.02),
|
||||
]
|
||||
|
||||
results = []
|
||||
for mlbl, (pl, ps) in predictions.items():
|
||||
for prob_th, sl, tp, mh, gap in bt_configs:
|
||||
trades = backtest(df, pl, ps, prob_th, sl, tp, mh, gap)
|
||||
net, wr, pm, dd, monthly = score(trades)
|
||||
n = len(trades)
|
||||
if n > 0:
|
||||
results.append({
|
||||
'model': mlbl, 'prob': prob_th, 'sl': sl, 'tp': tp,
|
||||
'mh': mh, 'gap': gap, 'n': n, 'net': net,
|
||||
'wr': wr, 'pm': pm, 'dd': dd, 'monthly': monthly
|
||||
})
|
||||
|
||||
# 按净利排序
|
||||
results.sort(key=lambda x: x['net'], reverse=True)
|
||||
|
||||
# 打印Top 15
|
||||
print(f"{'='*100}", flush=True)
|
||||
print(f" TOP 15 配置 (100U保证金 x 100倍)", flush=True)
|
||||
print(f"{'='*100}", flush=True)
|
||||
print(f" {'#':>2} {'模型':<18} {'概率':>4} {'SL':>4} {'TP':>4} {'MH':>5} {'gap':>4} {'交易':>5} {'年净利':>8} {'月均':>6} {'胜率':>5} {'盈月':>4} {'回撤':>6}", flush=True)
|
||||
print(f" {'-'*95}", flush=True)
|
||||
|
||||
for i, r in enumerate(results[:15]):
|
||||
mavg = r['net']/12
|
||||
print(f" {i+1:>2} {r['model']:<18} {r['prob']:.2f} {r['sl']*100:.1f}% {r['tp']*100:.1f}% {r['mh']:>5} {r['gap']:.2f} {r['n']:>5} {r['net']:>+8.0f} {mavg:>+6.0f} {r['wr']:>4.1f}% {r['pm']:>2}/12 {r['dd']:>6.0f}", flush=True)
|
||||
|
||||
# 最佳方案详情
|
||||
best = results[0]
|
||||
print(f"\n{'='*70}", flush=True)
|
||||
print(f" 最佳方案: {best['model']}", flush=True)
|
||||
print(f" 概率>{best['prob']} SL={best['sl']*100:.1f}% TP={best['tp']*100:.1f}% MH={best['mh']}s gap={best['gap']}", flush=True)
|
||||
print(f" 年净利: {best['net']:+.0f} USDT = 月均 {best['net']/12:+.0f} USDT", flush=True)
|
||||
print(f" 交易: {best['n']}笔 | 胜率: {best['wr']:.1f}% | 盈利月: {best['pm']}/12 | 回撤: {best['dd']:.0f}", flush=True)
|
||||
print(f"{'='*70}", flush=True)
|
||||
|
||||
print(f"\n 月度明细:", flush=True)
|
||||
for m in sorted(best['monthly'].keys()):
|
||||
d = best['monthly'][m]
|
||||
wr_m = d['w']/d['n']*100 if d['n']>0 else 0
|
||||
s = "盈利" if d['net']>0 else "亏损"
|
||||
print(f" {m}: {d['n']:>4}笔 {d['net']:>+8.0f}U 胜率{wr_m:.0f}% [{s}]", flush=True)
|
||||
|
||||
# 对比
|
||||
print(f"\n 对比:", flush=True)
|
||||
print(f" 纯EMA策略: +1196/年 = +100/月 (227笔)", flush=True)
|
||||
print(f" AI v1基线: +4801/年 = +400/月 (923笔)", flush=True)
|
||||
print(f" AI v2优化: {best['net']:+.0f}/年 = {best['net']/12:+.0f}/月 ({best['n']}笔)", flush=True)
|
||||
if best['net'] > 4801:
|
||||
print(f" v2 vs v1提升: {(best['net']/4801-1)*100:+.0f}%", flush=True)
|
||||
|
||||
elapsed = _time.time()-t0
|
||||
print(f"\n 耗时: {elapsed:.0f}s", flush=True)
|
||||
print(f"{'='*70}", flush=True)
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
258
交易/bitmart-AI最佳回测.py
Normal file
258
交易/bitmart-AI最佳回测.py
Normal file
@@ -0,0 +1,258 @@
|
||||
"""
|
||||
AI最佳配置回测 — 基于之前扫描结果
|
||||
|
||||
最佳: AI-v4: 10bar前瞻, 方向阈值0.3%, 概率阈值0.45, SL=0.5%, TP=0.8%
|
||||
该配置在100U时年净利+5544, 月均+462, 935笔交易, 8/12月盈利
|
||||
"""
|
||||
import datetime, sqlite3, time as _time
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
def main():
|
||||
t0 = _time.time()
|
||||
print("Loading...", flush=True)
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp())*1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp())*1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
df = pd.read_sql_query(
|
||||
f"SELECT id as ts,open,high,low,close FROM bitmart_eth_1m WHERE id>={s} AND id<{e} ORDER BY id", conn)
|
||||
conn.close()
|
||||
df['datetime'] = pd.to_datetime(df['ts'], unit='ms')
|
||||
df.set_index('datetime', inplace=True)
|
||||
print(f" {len(df):,} bars", flush=True)
|
||||
|
||||
# ===== 特征 =====
|
||||
print("Features...", flush=True)
|
||||
c=df['close']; h=df['high']; l=df['low']; o=df['open']
|
||||
for p in [5,8,13,21,50,120]:
|
||||
df[f'ema_{p}'] = c.ewm(span=p, adjust=False).mean()
|
||||
df['ema_fast_slow'] = (df['ema_8']-df['ema_21'])/c
|
||||
df['ema_slow_big'] = (df['ema_21']-df['ema_120'])/c
|
||||
df['price_vs_ema120'] = (c-df['ema_120'])/c
|
||||
df['price_vs_ema50'] = (c-df['ema_50'])/c
|
||||
df['ema8_slope'] = df['ema_8'].pct_change(5)
|
||||
df['ema21_slope'] = df['ema_21'].pct_change(5)
|
||||
|
||||
delta = c.diff()
|
||||
gain = delta.clip(lower=0); loss = (-delta).clip(lower=0)
|
||||
for p in [7,14,21]:
|
||||
ag=gain.rolling(p).mean(); al=loss.rolling(p).mean()
|
||||
df[f'rsi_{p}'] = 100 - 100/(1+ag/al.replace(0,np.nan))
|
||||
|
||||
mid=c.rolling(20).mean(); std=c.rolling(20).std()
|
||||
df['bb_pct'] = (c-(mid-2*std))/((mid+2*std)-(mid-2*std)).replace(0,np.nan)
|
||||
df['bb_width'] = 4*std/mid
|
||||
|
||||
ema12=c.ewm(span=12,adjust=False).mean(); ema26=c.ewm(span=26,adjust=False).mean()
|
||||
df['macd'] = (ema12-ema26)/c
|
||||
df['macd_signal'] = df['macd'].ewm(span=9,adjust=False).mean()
|
||||
df['macd_hist'] = df['macd']-df['macd_signal']
|
||||
|
||||
tr = pd.concat([h-l,(h-c.shift(1)).abs(),(l-c.shift(1)).abs()],axis=1).max(axis=1)
|
||||
df['atr_pct'] = tr.rolling(14).mean()/c
|
||||
df['atr_7'] = tr.rolling(7).mean()/c
|
||||
|
||||
low14=l.rolling(14).min(); high14=h.rolling(14).max()
|
||||
df['stoch_k'] = (c-low14)/(high14-low14).replace(0,np.nan)*100
|
||||
df['stoch_d'] = df['stoch_k'].rolling(3).mean()
|
||||
|
||||
for p in [1,3,5,10,20,60]:
|
||||
df[f'ret_{p}'] = c.pct_change(p)
|
||||
|
||||
df['vol_5'] = c.pct_change().rolling(5).std()
|
||||
df['vol_20'] = c.pct_change().rolling(20).std()
|
||||
df['vol_ratio'] = df['vol_5']/df['vol_20'].replace(0,np.nan)
|
||||
|
||||
body = (c-o).abs()
|
||||
df['body_pct'] = body/c
|
||||
df['upper_shadow'] = (h-pd.concat([o,c],axis=1).max(axis=1))/c
|
||||
df['lower_shadow'] = (pd.concat([o,c],axis=1).min(axis=1)-l)/c
|
||||
df['body_vs_range'] = body/(h-l).replace(0,np.nan)
|
||||
df['is_bullish'] = (c>o).astype(float)
|
||||
|
||||
df['high_20'] = h.rolling(20).max()
|
||||
df['low_20'] = l.rolling(20).min()
|
||||
df['price_position'] = (c-df['low_20'])/(df['high_20']-df['low_20']).replace(0,np.nan)
|
||||
|
||||
df['hour'] = df.index.hour
|
||||
df['minute'] = df.index.minute
|
||||
df['hour_sin'] = np.sin(2*np.pi*df['hour']/24)
|
||||
df['hour_cos'] = np.cos(2*np.pi*df['hour']/24)
|
||||
|
||||
prev_body = body.shift(1)
|
||||
df['engulf_ratio'] = body/prev_body.replace(0,np.nan)
|
||||
|
||||
exclude = {'ts','open','high','low','close','label',
|
||||
'high_20','low_20','ema_5','ema_8','ema_13','ema_21','ema_50','ema_120'}
|
||||
fcols = [c_ for c_ in df.columns if c_ not in exclude
|
||||
and df[c_].dtype in ('float64','float32','int64','int32')]
|
||||
print(f" {len(fcols)} features", flush=True)
|
||||
|
||||
# ===== 标签: 10bar前瞻, 0.3%阈值 =====
|
||||
fb = 10; thresh = 0.003
|
||||
future_ret = df['close'].shift(-fb)/df['close'] - 1
|
||||
df['label'] = 0
|
||||
df.loc[future_ret > thresh, 'label'] = 1
|
||||
df.loc[future_ret < -thresh, 'label'] = -1
|
||||
|
||||
# ===== 滚动训练 =====
|
||||
print("Walk-forward training...", flush=True)
|
||||
df['month'] = df.index.to_period('M')
|
||||
months = sorted(df['month'].unique())
|
||||
|
||||
pl = pd.Series(index=df.index, dtype=float); pl[:] = 0.0
|
||||
ps = pd.Series(index=df.index, dtype=float); ps[:] = 0.0
|
||||
|
||||
params = {
|
||||
'objective':'multiclass','num_class':3,'metric':'multi_logloss',
|
||||
'learning_rate':0.05,'num_leaves':31,'max_depth':6,
|
||||
'min_child_samples':50,'subsample':0.8,'colsample_bytree':0.8,
|
||||
'reg_alpha':0.1,'reg_lambda':0.1,'verbose':-1,'n_jobs':-1,'seed':42
|
||||
}
|
||||
|
||||
for i in range(3, len(months)):
|
||||
tm = months[i]; ts_ = months[i-3]
|
||||
tr_mask = (df['month']>=ts_) & (df['month']<tm)
|
||||
te_mask = df['month']==tm
|
||||
tr_df = df[tr_mask].dropna(subset=fcols+['label'])
|
||||
te_df = df[te_mask].dropna(subset=fcols)
|
||||
if len(tr_df)<1000 or len(te_df)<100: continue
|
||||
X_tr = tr_df[fcols].values; y_tr = tr_df['label'].values + 1
|
||||
dt_ = lgb.Dataset(X_tr, label=y_tr)
|
||||
model = lgb.train(params, dt_, num_boost_round=200)
|
||||
proba = model.predict(te_df[fcols].values)
|
||||
pl.loc[te_df.index] = proba[:,2]
|
||||
ps.loc[te_df.index] = proba[:,0]
|
||||
lc = (proba[:,2]>0.45).sum(); sc = (proba[:,0]>0.45).sum()
|
||||
print(f" {tm}: long={lc} short={sc}", flush=True)
|
||||
|
||||
# ===== 回测 =====
|
||||
print("\nBacktest...", flush=True)
|
||||
NOTIONAL = 10000.0
|
||||
FEE = NOTIONAL*0.0006*2; REB = FEE*0.9; NFEE = FEE-REB
|
||||
prob_th = 0.45; sl_pct = 0.005; tp_pct = 0.008
|
||||
|
||||
pos=0; op=0.0; ot=None; trades=[]
|
||||
for i in range(len(df)):
|
||||
dt=df.index[i]; p=df['close'].iloc[i]; p_l=pl.iloc[i]; p_s=ps.iloc[i]
|
||||
if pos!=0 and ot is not None:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
if -pp>=sl_pct*1.5:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'hard_sl',ot,dt)); pos=0; continue
|
||||
if hsec>=200:
|
||||
if -pp>=sl_pct:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'sl',ot,dt)); pos=0; continue
|
||||
if pp>=tp_pct:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'tp',ot,dt)); pos=0; continue
|
||||
if hsec>=1800:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'timeout',ot,dt)); pos=0; continue
|
||||
if pos==1 and p_s>prob_th+0.05:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'ai_rev',ot,dt)); pos=0
|
||||
elif pos==-1 and p_l>prob_th+0.05:
|
||||
trades.append((pos,op,p,NOTIONAL*pp,hsec,'ai_rev',ot,dt)); pos=0
|
||||
if pos==0:
|
||||
if p_l>prob_th and p_l>p_s: pos=1; op=p; ot=dt
|
||||
elif p_s>prob_th and p_s>p_l: pos=-1; op=p; ot=dt
|
||||
if pos!=0:
|
||||
p=df['close'].iloc[-1]; dt=df.index[-1]
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
trades.append((pos,op,p,NOTIONAL*pp,(dt-ot).total_seconds(),'end',ot,dt))
|
||||
|
||||
# ===== 结果 =====
|
||||
n = len(trades)
|
||||
tpnl = sum(t[3] for t in trades)
|
||||
net = tpnl - NFEE*n
|
||||
treb = REB*n
|
||||
wins = len([t for t in trades if t[3]>0])
|
||||
wr = wins/n*100 if n else 0
|
||||
|
||||
monthly = defaultdict(lambda: {'n':0,'net':0,'w':0})
|
||||
for t in trades:
|
||||
k = t[7].strftime('%Y-%m')
|
||||
monthly[k]['n'] += 1
|
||||
monthly[k]['net'] += t[3] - NFEE
|
||||
if t[3]>0: monthly[k]['w'] += 1
|
||||
|
||||
cum=0; peak=0; dd=0
|
||||
for t in trades:
|
||||
cum += t[3]-NFEE
|
||||
if cum>peak: peak=cum
|
||||
if peak-cum>dd: dd=peak-cum
|
||||
|
||||
reasons = defaultdict(int)
|
||||
for t in trades:
|
||||
reasons[t[5]] += 1
|
||||
|
||||
elapsed = _time.time()-t0
|
||||
|
||||
print(f"\n{'='*70}", flush=True)
|
||||
print(f" AI策略最佳配置 (LightGBM + 42特征)", flush=True)
|
||||
print(f" 10bar前瞻 | 阈值0.3% | 概率>0.45 | SL=0.5% TP=0.8%", flush=True)
|
||||
print(f" 100U保证金 x 100倍杠杆 = 10,000U名义 | 耗时{elapsed:.0f}s", flush=True)
|
||||
print(f"{'='*70}", flush=True)
|
||||
print(f" 方向盈亏: {tpnl:>+10.0f} USDT", flush=True)
|
||||
print(f" 返佣(90%): {treb:>+10.0f} USDT", flush=True)
|
||||
print(f" 净手续费(10%):{NFEE*n:>10.0f} USDT", flush=True)
|
||||
print(f" ================================", flush=True)
|
||||
print(f" 年净利: {net:>+10.0f} USDT", flush=True)
|
||||
print(f" 月均: {net/12:>+10.0f} USDT", flush=True)
|
||||
print(f" 最大回撤: {dd:>10.0f} USDT", flush=True)
|
||||
print(f" 交易笔数: {n:>10}", flush=True)
|
||||
print(f" 胜率: {wr:>9.1f}%", flush=True)
|
||||
|
||||
if wins>0 and wins<n:
|
||||
aw = sum(t[3] for t in trades if t[3]>0)/wins
|
||||
al = sum(t[3] for t in trades if t[3]<=0)/(n-wins)
|
||||
print(f" 平均盈利: {aw:>+10.1f} USDT", flush=True)
|
||||
print(f" 平均亏损: {al:>+10.1f} USDT", flush=True)
|
||||
print(f" 盈亏比: {abs(aw/al):>10.2f}", flush=True)
|
||||
|
||||
print(f"\n 平仓原因:", flush=True)
|
||||
for r,cnt in sorted(reasons.items(), key=lambda x:-x[1]):
|
||||
print(f" {r:<10} {cnt:>5}笔 ({cnt/n*100:.1f}%)", flush=True)
|
||||
|
||||
print(f"\n 月度明细:", flush=True)
|
||||
pm = 0
|
||||
for m in sorted(monthly.keys()):
|
||||
d = monthly[m]
|
||||
wr_m = d['w']/d['n']*100 if d['n']>0 else 0
|
||||
status = "盈利" if d['net']>0 else "亏损"
|
||||
print(f" {m}: {d['n']:>4}笔 {d['net']:>+8.0f}U 胜率{wr_m:.0f}% [{status}]", flush=True)
|
||||
if d['net']>0: pm += 1
|
||||
print(f" 合计: {n:>4}笔 {net:>+8.0f}U 盈利月: {pm}/12", flush=True)
|
||||
|
||||
print(f"\n --- 不同保证金下的月均收入 ---", flush=True)
|
||||
for margin in [100, 200, 300, 500, 800, 1000]:
|
||||
sc = margin*100/NOTIONAL
|
||||
mn = net*sc/12
|
||||
ok = " <<< 达标!" if mn>=1000 else ""
|
||||
print(f" {margin:>5}U保证金: 月均 {mn:>+6.0f} USDT{ok}", flush=True)
|
||||
|
||||
# 对比EMA基线
|
||||
print(f"\n --- 对比: AI vs 纯EMA策略 ---", flush=True)
|
||||
ema_net = 1196 # 之前EMA基线100U的年净利
|
||||
print(f" 纯EMA: {ema_net:>+6.0f}/年 = {ema_net/12:>+4.0f}/月 (227笔)", flush=True)
|
||||
print(f" AI策略: {net:>+6.0f}/年 = {net/12:>+4.0f}/月 ({n}笔)", flush=True)
|
||||
if net > ema_net:
|
||||
print(f" AI提升: {(net/ema_net-1)*100:>+.0f}% ({net-ema_net:>+.0f} USDT)", flush=True)
|
||||
|
||||
print(f"\n{'='*70}", flush=True)
|
||||
|
||||
# 保存
|
||||
csv = Path(__file__).parent.parent / 'ai_trades.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("dir,open_px,close_px,pnl,hold_sec,reason,open_time,close_time\n")
|
||||
for t in trades:
|
||||
d = 'long' if t[0]==1 else 'short'
|
||||
f.write(f"{d},{t[1]:.2f},{t[2]:.2f},{t[3]:.2f},{t[4]:.0f},{t[5]},{t[6]},{t[7]}\n")
|
||||
print(f" Saved: {csv}", flush=True)
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
442
交易/bitmart-AI策略回测.py
Normal file
442
交易/bitmart-AI策略回测.py
Normal file
@@ -0,0 +1,442 @@
|
||||
"""
|
||||
AI/ML 交易策略回测 — LightGBM + 30+技术指标
|
||||
|
||||
核心思路:
|
||||
1. 用30+种技术指标作为特征(EMA/RSI/BB/MACD/ATR/K线形态/动量/波动率等)
|
||||
2. 标签:未来N根K线的收益方向(涨>阈值=做多,跌>阈值=做空,否则=不交易)
|
||||
3. 滚动训练:每月用过去3个月数据训练,预测下一个月
|
||||
4. 只在模型高置信度时开仓(概率>阈值)
|
||||
5. 同一时间只持1个仓
|
||||
|
||||
条件: 100U保证金, 100x杠杆, 90%返佣, >3分钟持仓
|
||||
"""
|
||||
import datetime
|
||||
import sqlite3
|
||||
import time as _time
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import lightgbm as lgb
|
||||
from sklearn.model_selection import TimeSeriesSplit
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
# ==================== 数据加载 ====================
|
||||
def load_data():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
df = pd.read_sql_query(
|
||||
f"SELECT id as ts, open, high, low, close FROM bitmart_eth_1m "
|
||||
f"WHERE id >= {s} AND id < {e} ORDER BY id", conn)
|
||||
conn.close()
|
||||
df['datetime'] = pd.to_datetime(df['ts'], unit='ms')
|
||||
df.set_index('datetime', inplace=True)
|
||||
return df
|
||||
|
||||
# ==================== 特征工程 ====================
|
||||
def add_features(df):
|
||||
"""生成30+技术指标特征"""
|
||||
c = df['close']; h = df['high']; l = df['low']; o = df['open']
|
||||
|
||||
# --- EMA ---
|
||||
for p in [5, 8, 13, 21, 50, 120]:
|
||||
df[f'ema_{p}'] = c.ewm(span=p, adjust=False).mean()
|
||||
|
||||
# EMA 相对位置
|
||||
df['ema_fast_slow'] = (df['ema_8'] - df['ema_21']) / c # 快慢线差距
|
||||
df['ema_slow_big'] = (df['ema_21'] - df['ema_120']) / c
|
||||
df['price_vs_ema120'] = (c - df['ema_120']) / c
|
||||
df['price_vs_ema50'] = (c - df['ema_50']) / c
|
||||
df['ema8_slope'] = df['ema_8'].pct_change(5) # EMA斜率
|
||||
df['ema21_slope'] = df['ema_21'].pct_change(5)
|
||||
|
||||
# --- RSI ---
|
||||
for p in [7, 14, 21]:
|
||||
delta = c.diff()
|
||||
gain = delta.clip(lower=0)
|
||||
loss = (-delta).clip(lower=0)
|
||||
avg_gain = gain.rolling(p).mean()
|
||||
avg_loss = loss.rolling(p).mean()
|
||||
rs = avg_gain / avg_loss.replace(0, np.nan)
|
||||
df[f'rsi_{p}'] = 100 - 100 / (1 + rs)
|
||||
|
||||
# --- Bollinger Bands ---
|
||||
for p in [20]:
|
||||
mid = c.rolling(p).mean()
|
||||
std = c.rolling(p).std()
|
||||
df['bb_upper'] = mid + 2 * std
|
||||
df['bb_lower'] = mid - 2 * std
|
||||
df['bb_mid'] = mid
|
||||
df['bb_pct'] = (c - df['bb_lower']) / (df['bb_upper'] - df['bb_lower']).replace(0, np.nan)
|
||||
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / mid # 波动率
|
||||
|
||||
# --- MACD ---
|
||||
ema12 = c.ewm(span=12, adjust=False).mean()
|
||||
ema26 = c.ewm(span=26, adjust=False).mean()
|
||||
df['macd'] = (ema12 - ema26) / c
|
||||
df['macd_signal'] = df['macd'].ewm(span=9, adjust=False).mean()
|
||||
df['macd_hist'] = df['macd'] - df['macd_signal']
|
||||
|
||||
# --- ATR ---
|
||||
tr = pd.concat([
|
||||
h - l,
|
||||
(h - c.shift(1)).abs(),
|
||||
(l - c.shift(1)).abs()
|
||||
], axis=1).max(axis=1)
|
||||
df['atr_14'] = tr.rolling(14).mean()
|
||||
df['atr_pct'] = df['atr_14'] / c
|
||||
df['atr_7'] = tr.rolling(7).mean() / c
|
||||
|
||||
# --- Stochastic ---
|
||||
low14 = l.rolling(14).min()
|
||||
high14 = h.rolling(14).max()
|
||||
df['stoch_k'] = (c - low14) / (high14 - low14).replace(0, np.nan) * 100
|
||||
df['stoch_d'] = df['stoch_k'].rolling(3).mean()
|
||||
|
||||
# --- 动量 ---
|
||||
for p in [1, 3, 5, 10, 20, 60]:
|
||||
df[f'ret_{p}'] = c.pct_change(p) # 过去N根收益率
|
||||
|
||||
# --- 波动率 ---
|
||||
df['vol_5'] = c.pct_change().rolling(5).std()
|
||||
df['vol_20'] = c.pct_change().rolling(20).std()
|
||||
df['vol_ratio'] = df['vol_5'] / df['vol_20'].replace(0, np.nan)
|
||||
|
||||
# --- K线形态 ---
|
||||
body = (c - o).abs()
|
||||
df['body_pct'] = body / c # 实体占比
|
||||
df['upper_shadow'] = (h - pd.concat([o, c], axis=1).max(axis=1)) / c
|
||||
df['lower_shadow'] = (pd.concat([o, c], axis=1).min(axis=1) - l) / c
|
||||
df['body_vs_range'] = body / (h - l).replace(0, np.nan) # 实体/全幅
|
||||
df['is_bullish'] = (c > o).astype(float)
|
||||
|
||||
# 连续同向K线
|
||||
bullish = (c > o).astype(int)
|
||||
df['consec_bull'] = bullish.groupby((bullish != bullish.shift()).cumsum()).cumcount() + 1
|
||||
df['consec_bull'] = df['consec_bull'] * bullish
|
||||
bearish = (c < o).astype(int)
|
||||
df['consec_bear'] = bearish.groupby((bearish != bearish.shift()).cumsum()).cumcount() + 1
|
||||
df['consec_bear'] = df['consec_bear'] * bearish
|
||||
|
||||
# 吞没形态
|
||||
prev_body = body.shift(1)
|
||||
df['engulf_ratio'] = body / prev_body.replace(0, np.nan)
|
||||
df['bullish_engulf'] = ((c.shift(1) < o.shift(1)) & (c > o) &
|
||||
(c > o.shift(1)) & (o <= c.shift(1))).astype(float)
|
||||
df['bearish_engulf'] = ((c.shift(1) > o.shift(1)) & (c < o) &
|
||||
(c < o.shift(1)) & (o >= c.shift(1))).astype(float)
|
||||
|
||||
# 相对高低位置
|
||||
df['high_20'] = h.rolling(20).max()
|
||||
df['low_20'] = l.rolling(20).min()
|
||||
df['price_position'] = (c - df['low_20']) / (df['high_20'] - df['low_20']).replace(0, np.nan)
|
||||
|
||||
# 小时/分钟时间特征
|
||||
df['hour'] = df.index.hour
|
||||
df['minute'] = df.index.minute
|
||||
df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
|
||||
df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
|
||||
|
||||
return df
|
||||
|
||||
# ==================== 标签生成 ====================
|
||||
def add_labels(df, forward_bars=10, threshold=0.002):
|
||||
"""
|
||||
未来N根K线的收益:
|
||||
> threshold → 1 (做多机会)
|
||||
< -threshold → -1 (做空机会)
|
||||
否则 → 0 (不交易)
|
||||
"""
|
||||
future_ret = df['close'].shift(-forward_bars) / df['close'] - 1
|
||||
df['label'] = 0
|
||||
df.loc[future_ret > threshold, 'label'] = 1
|
||||
df.loc[future_ret < -threshold, 'label'] = -1
|
||||
return df
|
||||
|
||||
# ==================== 模型训练 + 预测 ====================
|
||||
def get_feature_cols(df):
|
||||
exclude = {'ts', 'open', 'high', 'low', 'close', 'label',
|
||||
'bb_upper', 'bb_lower', 'bb_mid', 'high_20', 'low_20',
|
||||
'atr_14', 'ema_5', 'ema_8', 'ema_13', 'ema_21', 'ema_50', 'ema_120'}
|
||||
return [c for c in df.columns if c not in exclude and df[c].dtype in ('float64','float32','int64','int32')]
|
||||
|
||||
def train_predict_walkforward(df, feature_cols, train_months=3):
|
||||
"""
|
||||
滚动训练:
|
||||
用过去 train_months 个月训练 → 预测下一个月
|
||||
从第4个月开始有预测
|
||||
"""
|
||||
df['month'] = df.index.to_period('M')
|
||||
months = sorted(df['month'].unique())
|
||||
|
||||
all_preds = pd.Series(index=df.index, dtype=float)
|
||||
all_preds[:] = 0.0 # 默认不交易
|
||||
|
||||
all_proba_long = pd.Series(index=df.index, dtype=float)
|
||||
all_proba_short = pd.Series(index=df.index, dtype=float)
|
||||
all_proba_long[:] = 0.0
|
||||
all_proba_short[:] = 0.0
|
||||
|
||||
print(f"\n Walk-forward training ({len(months)} months, train={train_months}m):", flush=True)
|
||||
|
||||
for i in range(train_months, len(months)):
|
||||
test_month = months[i]
|
||||
train_start = months[i - train_months]
|
||||
|
||||
# 训练数据
|
||||
train_mask = (df['month'] >= train_start) & (df['month'] < test_month)
|
||||
test_mask = df['month'] == test_month
|
||||
|
||||
train_df = df[train_mask].dropna(subset=feature_cols + ['label'])
|
||||
test_df = df[test_mask].dropna(subset=feature_cols)
|
||||
|
||||
if len(train_df) < 1000 or len(test_df) < 100:
|
||||
print(f" {test_month}: skip (data insufficient)", flush=True)
|
||||
continue
|
||||
|
||||
X_train = train_df[feature_cols].values
|
||||
y_train = train_df['label'].values
|
||||
|
||||
X_test = test_df[feature_cols].values
|
||||
|
||||
# 将 -1,0,1 映射到 0,1,2 用于多分类
|
||||
y_train_cls = y_train + 1 # -1→0, 0→1, 1→2
|
||||
|
||||
# LightGBM 训练
|
||||
params = {
|
||||
'objective': 'multiclass',
|
||||
'num_class': 3,
|
||||
'metric': 'multi_logloss',
|
||||
'learning_rate': 0.05,
|
||||
'num_leaves': 31,
|
||||
'max_depth': 6,
|
||||
'min_child_samples': 50,
|
||||
'subsample': 0.8,
|
||||
'colsample_bytree': 0.8,
|
||||
'reg_alpha': 0.1,
|
||||
'reg_lambda': 0.1,
|
||||
'verbose': -1,
|
||||
'n_jobs': -1,
|
||||
'seed': 42,
|
||||
}
|
||||
|
||||
dtrain = lgb.Dataset(X_train, label=y_train_cls)
|
||||
model = lgb.train(params, dtrain, num_boost_round=200)
|
||||
|
||||
# 预测概率
|
||||
proba = model.predict(X_test) # shape: (n, 3) → [P(short), P(neutral), P(long)]
|
||||
|
||||
test_idx = test_df.index
|
||||
all_proba_short.loc[test_idx] = proba[:, 0] # P(short)
|
||||
all_proba_long.loc[test_idx] = proba[:, 2] # P(long)
|
||||
|
||||
# 特征重要性(只打印最后一个月的)
|
||||
if i == len(months) - 1:
|
||||
importance = model.feature_importance(importance_type='gain')
|
||||
feat_imp = sorted(zip(feature_cols, importance), key=lambda x: -x[1])
|
||||
print(f"\n Top 10 features:", flush=True)
|
||||
for fname, imp in feat_imp[:10]:
|
||||
print(f" {fname:<20} {imp:.0f}", flush=True)
|
||||
|
||||
long_cnt = (proba[:, 2] > 0.45).sum()
|
||||
short_cnt = (proba[:, 0] > 0.45).sum()
|
||||
print(f" {test_month}: train={len(train_df):,} test={len(test_df):,} "
|
||||
f"signals: long={long_cnt} short={short_cnt}", flush=True)
|
||||
|
||||
return all_proba_long, all_proba_short
|
||||
|
||||
# ==================== 回测引擎 ====================
|
||||
def backtest(df, proba_long, proba_short, notional=10000.0,
|
||||
prob_threshold=0.45, min_hold=200, max_hold=1800,
|
||||
sl_pct=0.004, tp_pct=0.006):
|
||||
FEE = notional * 0.0006 * 2
|
||||
REB = FEE * 0.9
|
||||
NFEE = FEE - REB
|
||||
|
||||
pos = 0; op = 0.0; ot = None
|
||||
trades = []
|
||||
|
||||
for i in range(len(df)):
|
||||
dt = df.index[i]
|
||||
p = df['close'].iloc[i]
|
||||
pl = proba_long.iloc[i]
|
||||
ps = proba_short.iloc[i]
|
||||
|
||||
# 持仓管理
|
||||
if pos != 0 and ot is not None:
|
||||
pp = (p - op) / op if pos == 1 else (op - p) / op
|
||||
hsec = (dt - ot).total_seconds()
|
||||
|
||||
# 硬止损
|
||||
if -pp >= sl_pct * 1.5:
|
||||
trades.append((pos, op, p, notional*pp, hsec, "硬止损", ot, dt))
|
||||
pos=0; continue
|
||||
|
||||
if hsec >= min_hold:
|
||||
if -pp >= sl_pct:
|
||||
trades.append((pos, op, p, notional*pp, hsec, "止损", ot, dt))
|
||||
pos=0; continue
|
||||
if pp >= tp_pct:
|
||||
trades.append((pos, op, p, notional*pp, hsec, "止盈", ot, dt))
|
||||
pos=0; continue
|
||||
if hsec >= max_hold:
|
||||
trades.append((pos, op, p, notional*pp, hsec, "超时", ot, dt))
|
||||
pos=0; continue
|
||||
|
||||
# AI反向信号平仓
|
||||
if pos == 1 and ps > prob_threshold + 0.05:
|
||||
trades.append((pos, op, p, notional*pp, hsec, "AI反转", ot, dt))
|
||||
pos=0
|
||||
elif pos == -1 and pl > prob_threshold + 0.05:
|
||||
trades.append((pos, op, p, notional*pp, hsec, "AI反转", ot, dt))
|
||||
pos=0
|
||||
|
||||
# 开仓
|
||||
if pos == 0:
|
||||
if pl > prob_threshold and pl > ps:
|
||||
pos = 1; op = p; ot = dt
|
||||
elif ps > prob_threshold and ps > pl:
|
||||
pos = -1; op = p; ot = dt
|
||||
|
||||
if pos != 0:
|
||||
p = df['close'].iloc[-1]; dt = df.index[-1]
|
||||
pp = (p-op)/op if pos==1 else (op-p)/op
|
||||
trades.append((pos, op, p, notional*pp, (dt-ot).total_seconds(), "结束", ot, dt))
|
||||
|
||||
return trades
|
||||
|
||||
# ==================== 结果分析 ====================
|
||||
def analyze(trades, notional, label):
|
||||
if not trades:
|
||||
print(f" [{label}] No trades", flush=True); return 0
|
||||
|
||||
n = len(trades)
|
||||
FEE = notional * 0.0006 * 2; REB = FEE * 0.9; NFEE = FEE - REB
|
||||
total_pnl = sum(t[3] for t in trades)
|
||||
net = total_pnl - NFEE * n
|
||||
wins = len([t for t in trades if t[3]>0]); wr = wins/n*100
|
||||
total_reb = REB * n
|
||||
|
||||
monthly = defaultdict(lambda: {'n':0,'net':0,'w':0})
|
||||
for t in trades:
|
||||
k = t[7].strftime('%Y-%m')
|
||||
monthly[k]['n']+=1; monthly[k]['net']+=t[3]-NFEE
|
||||
if t[3]>0: monthly[k]['w']+=1
|
||||
|
||||
cum=0;peak=0;dd=0
|
||||
for t in trades:
|
||||
cum+=t[3]-NFEE
|
||||
if cum>peak: peak=cum
|
||||
if peak-cum>dd: dd=peak-cum
|
||||
|
||||
pm = len([m for m in monthly.values() if m['net']>0])
|
||||
|
||||
reasons = defaultdict(int)
|
||||
for t in trades: reasons[t[5]]+=1
|
||||
|
||||
print(f"\n{'='*75}", flush=True)
|
||||
print(f" {label}", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
print(f" 方向盈亏: {total_pnl:>+10.0f} USDT", flush=True)
|
||||
print(f" 返佣: {total_reb:>+10.0f} USDT", flush=True)
|
||||
print(f" 净手续费: {NFEE*n:>10.0f} USDT", flush=True)
|
||||
print(f" ================================", flush=True)
|
||||
print(f" 年净利: {net:>+10.0f} USDT (月均 {net/12:>+.0f})", flush=True)
|
||||
print(f" 交易: {n}笔 | 胜率: {wr:.1f}% | 盈利月: {pm}/12", flush=True)
|
||||
print(f" 最大回撤: {dd:>.0f} USDT", flush=True)
|
||||
|
||||
if wins > 0:
|
||||
avg_win = sum(t[3] for t in trades if t[3]>0) / wins
|
||||
avg_loss = sum(t[3] for t in trades if t[3]<=0) / (n-wins) if n>wins else 0
|
||||
print(f" 均赢: {avg_win:>+.2f} | 均亏: {avg_loss:>+.2f} | 盈亏比: {abs(avg_win/avg_loss) if avg_loss!=0 else 999:.2f}", flush=True)
|
||||
|
||||
print(f"\n 平仓原因:", flush=True)
|
||||
for r, cnt in sorted(reasons.items(), key=lambda x:-x[1]):
|
||||
print(f" {r:<10} {cnt:>5}笔 ({cnt/n*100:.1f}%)", flush=True)
|
||||
|
||||
print(f"\n 月度:", flush=True)
|
||||
for m in sorted(monthly.keys()):
|
||||
d = monthly[m]; wr_m=d['w']/d['n']*100 if d['n']>0 else 0
|
||||
bar = "+" * min(30, max(0, int(d['net']/100))) + "-" * min(30, max(0, int(-d['net']/100)))
|
||||
print(f" {m} {d['n']:>4}笔 {d['net']:>+8.0f} {wr_m:>4.0f}% {bar}", flush=True)
|
||||
print(f" {'合计':>7} {n:>4}笔 {net:>+8.0f}", flush=True)
|
||||
|
||||
print(f"\n 仓位放大:", flush=True)
|
||||
for margin in [100, 300, 500, 800, 1000]:
|
||||
scale = margin * 100 / notional
|
||||
print(f" {margin}U: 月均 {net*scale/12:>+.0f} USDT {'<<< 达标' if net*scale/12>=1000 else ''}", flush=True)
|
||||
|
||||
print(f"{'='*75}", flush=True)
|
||||
return net
|
||||
|
||||
# ==================== 主函数 ====================
|
||||
def main():
|
||||
t0 = _time.time()
|
||||
print("="*75, flush=True)
|
||||
print(" AI/ML 交易策略 — LightGBM + 30+技术指标", flush=True)
|
||||
print("="*75, flush=True)
|
||||
|
||||
print("\n[1/4] 加载数据...", flush=True)
|
||||
df = load_data()
|
||||
print(f" {len(df):,} 根 1分钟K线", flush=True)
|
||||
|
||||
print("\n[2/4] 特征工程 (30+指标)...", flush=True)
|
||||
df = add_features(df)
|
||||
feature_cols = get_feature_cols(df)
|
||||
print(f" 生成 {len(feature_cols)} 个特征", flush=True)
|
||||
|
||||
# 测试不同的前瞻期和阈值
|
||||
configs = [
|
||||
# (forward_bars, threshold, prob_threshold, sl, tp, label)
|
||||
(5, 0.001, 0.42, 0.003, 0.004, "AI-v1: 5bar前瞻 阈值0.1%"),
|
||||
(10, 0.002, 0.42, 0.004, 0.006, "AI-v2: 10bar前瞻 阈值0.2%"),
|
||||
(10, 0.002, 0.45, 0.004, 0.006, "AI-v3: 10bar 高置信0.45"),
|
||||
(10, 0.003, 0.45, 0.005, 0.008, "AI-v4: 10bar 阈值0.3% 宽SL"),
|
||||
(20, 0.003, 0.42, 0.005, 0.008, "AI-v5: 20bar前瞻 阈值0.3%"),
|
||||
(20, 0.004, 0.45, 0.005, 0.010, "AI-v6: 20bar 阈值0.4% 大TP"),
|
||||
]
|
||||
|
||||
best_net = -999999; best_label = ""
|
||||
|
||||
for fb, thresh, prob_th, sl, tp, label in configs:
|
||||
print(f"\n{'='*75}", flush=True)
|
||||
print(f" [{label}]", flush=True)
|
||||
print(f" 前瞻={fb}bar 方向阈值={thresh*100:.1f}% 概率阈值={prob_th} SL={sl*100:.1f}% TP={tp*100:.1f}%", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
|
||||
print("\n[3/4] 生成标签...", flush=True)
|
||||
df_labeled = add_labels(df.copy(), forward_bars=fb, threshold=thresh)
|
||||
labels = df_labeled['label']
|
||||
print(f" 多={int((labels==1).sum()):,} 空={int((labels==-1).sum()):,} 中性={int((labels==0).sum()):,}", flush=True)
|
||||
|
||||
print("\n[4/4] 滚动训练+预测...", flush=True)
|
||||
proba_long, proba_short = train_predict_walkforward(df_labeled, feature_cols, train_months=3)
|
||||
|
||||
print("\n 回测...", flush=True)
|
||||
trades = backtest(df_labeled, proba_long, proba_short,
|
||||
notional=10000.0, prob_threshold=prob_th,
|
||||
sl_pct=sl, tp_pct=tp)
|
||||
|
||||
net = analyze(trades, 10000.0, label)
|
||||
if net > best_net:
|
||||
best_net = net; best_label = label
|
||||
|
||||
elapsed = _time.time() - t0
|
||||
print(f"\n\n{'='*75}", flush=True)
|
||||
print(f" 总结 | 耗时 {elapsed:.0f}s", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
print(f" 最佳: {best_label}", flush=True)
|
||||
print(f" 年净利: {best_net:+.0f} USDT = 月均 {best_net/12:+.0f} USDT", flush=True)
|
||||
if best_net > 0:
|
||||
needed = int(12000 / best_net * 100) + 1
|
||||
print(f" 达到1000U/月需保证金: ~{needed}U", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
187
交易/bitmart-优化v2.py
Normal file
187
交易/bitmart-优化v2.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
EMA趋势策略 - 精简参数优化 v2
|
||||
|
||||
已知:EMA(8/21/120) ATR>0.03% SL=0.4% MaxH=1800 → 方向PnL +327, 净亏 -101
|
||||
优化目标:找到净盈利 > 0 的参数组合
|
||||
|
||||
策略核心:减少交易次数(更长EMA/更高ATR门槛),提高每笔质量
|
||||
"""
|
||||
import sys, time, datetime, sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1)
|
||||
self.v = None
|
||||
def update(self, price):
|
||||
if self.v is None:
|
||||
self.v = price
|
||||
else:
|
||||
self.v = price * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
# pre-convert to list of tuples for speed
|
||||
out = []
|
||||
for r in rows:
|
||||
out.append((datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]))
|
||||
return out
|
||||
|
||||
def bt(data, fp, sp, bp, atr_min, sl_pct, mh):
|
||||
bal = 1000.0
|
||||
pos = 0; op = 0.0; ot = None; ps = 0.0; pend = None
|
||||
ef = EMA(fp); es = EMA(sp); eb = EMA(bp)
|
||||
hs_buf = []; ls_buf = []; cs_buf = []
|
||||
pf_ = None; ps_ = None
|
||||
tc=0; wc=0; dpnl=0.0; tfee=0.0; treb=0.0
|
||||
hsl = sl_pct * 1.5
|
||||
LEV=50; RP=0.005; TF=0.0006; RR=0.90; MH=200; AP=14
|
||||
|
||||
for dt, o_, h_, l_, c_ in data:
|
||||
p = c_
|
||||
hs_buf.append(h_); ls_buf.append(l_); cs_buf.append(p)
|
||||
fast = ef.update(p); slow = es.update(p); big = eb.update(p)
|
||||
|
||||
atr_pct = 0.0
|
||||
if len(hs_buf) > AP + 1:
|
||||
s = 0.0
|
||||
for i in range(-AP, 0):
|
||||
tr = hs_buf[i] - ls_buf[i]
|
||||
d1 = abs(hs_buf[i] - cs_buf[i-1])
|
||||
d2 = abs(ls_buf[i] - cs_buf[i-1])
|
||||
if d1 > tr: tr = d1
|
||||
if d2 > tr: tr = d2
|
||||
s += tr
|
||||
atr_pct = s / (AP * p) if p > 0 else 0
|
||||
|
||||
cu = pf_ is not None and pf_ <= ps_ and fast > slow
|
||||
cd = pf_ is not None and pf_ >= ps_ and fast < slow
|
||||
pf_ = fast; ps_ = slow
|
||||
|
||||
if pos != 0 and ot is not None:
|
||||
pp = (p - op) / op if pos == 1 else (op - p) / op
|
||||
hsec = (dt - ot).total_seconds()
|
||||
|
||||
if -pp >= hsl:
|
||||
pnl_ = ps * pp; cv = ps*(1+pp); cf = cv*TF; of_=ps*TF; tt=of_+cf; rb=tt*RR
|
||||
bal += pnl_ - cf + rb; dpnl += pnl_; tfee += tt; treb += rb
|
||||
tc += 1; wc += (1 if pnl_ > 0 else 0)
|
||||
pos=0; op=0; ot=None; ps=0; pend=None
|
||||
continue
|
||||
|
||||
if hsec >= MH:
|
||||
do_c = False
|
||||
if -pp >= sl_pct: do_c = True
|
||||
elif hsec >= mh: do_c = True
|
||||
elif pos == 1 and cd: do_c = True
|
||||
elif pos == -1 and cu: do_c = True
|
||||
elif pend == 'cl' and pos == 1: do_c = True
|
||||
elif pend == 'cs' and pos == -1: do_c = True
|
||||
|
||||
if do_c:
|
||||
pnl_ = ps * pp; cv = ps*(1+pp); cf = cv*TF; of_=ps*TF; tt=of_+cf; rb=tt*RR
|
||||
bal += pnl_ - cf + rb; dpnl += pnl_; tfee += tt; treb += rb
|
||||
tc += 1; wc += (1 if pnl_ > 0 else 0)
|
||||
pos=0; op=0; ot=None; ps=0; pend=None
|
||||
if atr_pct >= atr_min:
|
||||
if (cd or fast < slow) and p < big:
|
||||
ns = bal * RP * LEV
|
||||
if ns >= 1: bal -= ns*TF; pos=-1; op=p; ot=dt; ps=ns
|
||||
elif (cu or fast > slow) and p > big:
|
||||
ns = bal * RP * LEV
|
||||
if ns >= 1: bal -= ns*TF; pos=1; op=p; ot=dt; ps=ns
|
||||
continue
|
||||
else:
|
||||
if pos == 1 and cd: pend = 'cl'
|
||||
elif pos == -1 and cu: pend = 'cs'
|
||||
|
||||
if pos == 0 and atr_pct >= atr_min:
|
||||
if cu and p > big:
|
||||
ns = bal * RP * LEV
|
||||
if ns >= 1: bal -= ns*TF; pos=1; op=p; ot=dt; ps=ns
|
||||
elif cd and p < big:
|
||||
ns = bal * RP * LEV
|
||||
if ns >= 1: bal -= ns*TF; pos=-1; op=p; ot=dt; ps=ns
|
||||
|
||||
if pos != 0:
|
||||
p = data[-1][4]
|
||||
pp = (p - op) / op if pos == 1 else (op - p) / op
|
||||
pnl_ = ps * pp; cv = ps*(1+pp); cf = cv*TF; of_=ps*TF; tt=of_+cf; rb=tt*RR
|
||||
bal += pnl_ - cf + rb; dpnl += pnl_; tfee += tt; treb += rb
|
||||
tc += 1; wc += (1 if pnl_ > 0 else 0)
|
||||
|
||||
net = bal - 1000.0
|
||||
wr = wc/tc*100 if tc > 0 else 0
|
||||
return net, tc, wr, dpnl, treb, tfee - treb
|
||||
|
||||
def main():
|
||||
print("Loading...", flush=True)
|
||||
data = load()
|
||||
print(f"{len(data)} bars loaded\n", flush=True)
|
||||
|
||||
# 精简参数网格 - 只测最有潜力的范围
|
||||
combos = []
|
||||
for fp in [8, 13, 20, 30]:
|
||||
for sp in [21, 34, 55, 80]:
|
||||
if fp >= sp: continue
|
||||
for bp in [120, 200]:
|
||||
for am in [0.0003, 0.0006, 0.001, 0.0015, 0.002]:
|
||||
for sl in [0.004, 0.006, 0.008, 0.01]:
|
||||
for mh in [1200, 1800, 3600]:
|
||||
combos.append((fp, sp, bp, am, sl, mh))
|
||||
|
||||
print(f"Combos: {len(combos)}", flush=True)
|
||||
results = []
|
||||
t0 = time.time()
|
||||
|
||||
for idx, (fp, sp, bp, am, sl, mh) in enumerate(combos):
|
||||
net, tc, wr, dp, reb, nf = bt(data, fp, sp, bp, am, sl, mh)
|
||||
results.append((net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh))
|
||||
if (idx+1) % 30 == 0:
|
||||
el = time.time() - t0
|
||||
eta = el/(idx+1)*(len(combos)-idx-1)
|
||||
print(f" [{idx+1}/{len(combos)}] {el:.0f}s done, ~{eta:.0f}s left", flush=True)
|
||||
|
||||
tt = time.time() - t0
|
||||
print(f"\nAll done! {len(results)} combos in {tt:.1f}s\n", flush=True)
|
||||
|
||||
results.sort(key=lambda x: x[0], reverse=True)
|
||||
profitable = [r for r in results if r[0] > 0]
|
||||
print(f"Profitable: {len(profitable)} / {len(results)} ({len(profitable)/len(results)*100:.1f}%)\n", flush=True)
|
||||
|
||||
print(f"{'='*130}", flush=True)
|
||||
print(f" TOP 30", flush=True)
|
||||
print(f"{'='*130}", flush=True)
|
||||
print(f" {'#':>3} {'F':>3} {'S':>3} {'B':>4} {'ATR':>6} {'SL':>5} {'MH':>5} | {'Net%':>7} {'Net$':>9} {'#Trd':>6} {'WR':>6} {'DirPnL':>9} {'Rebate':>9} {'NetFee':>8}", flush=True)
|
||||
print(f" {'-'*120}", flush=True)
|
||||
|
||||
for i, (net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh) in enumerate(results[:30]):
|
||||
mk = " <-- $$" if net > 0 else ""
|
||||
print(f" {i+1:>3} {fp:>3} {sp:>3} {bp:>4} {am*100:>5.2f}% {sl*100:>4.1f}% {mh:>5} | {net/10:>+6.2f}% {net:>+8.2f} {tc:>6} {wr:>5.1f}% {dp:>+8.2f} {reb:>8.2f} {nf:>8.2f}{mk}", flush=True)
|
||||
|
||||
if profitable:
|
||||
print(f"\n{'='*130}", flush=True)
|
||||
print(f" ALL PROFITABLE COMBOS", flush=True)
|
||||
print(f"{'='*130}", flush=True)
|
||||
for i, (net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh) in enumerate(profitable):
|
||||
print(f" {i+1:>3} EMA({fp}/{sp}/{bp}) ATR>{am*100:.2f}% SL={sl*100:.1f}% MH={mh}s | net={net:+.2f} ({net/10:+.2f}%) trades={tc} WR={wr:.1f}% dirPnL={dp:+.2f} rebate={reb:.2f} netFee={nf:.2f}", flush=True)
|
||||
|
||||
# Save
|
||||
csv = Path(__file__).parent.parent / 'param_results.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("fast,slow,big,atr_min,stop_loss,max_hold,net_pct,net_usd,trades,win_rate,dir_pnl,rebate,net_fee\n")
|
||||
for net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh in results:
|
||||
f.write(f"{fp},{sp},{bp},{am},{sl},{mh},{net/10:.4f},{net:.4f},{tc},{wr:.2f},{dp:.4f},{reb:.4f},{nf:.4f}\n")
|
||||
print(f"\nSaved: {csv}", flush=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
376
交易/bitmart-参数优化.py
Normal file
376
交易/bitmart-参数优化.py
Normal file
@@ -0,0 +1,376 @@
|
||||
"""
|
||||
EMA趋势策略参数优化扫描
|
||||
|
||||
基于前一轮回测发现 EMA-Trend 方向盈利 +327 USDT,仅差 101 USDT 即可盈利。
|
||||
核心优化方向:减少交易次数(降低费用),同时保持方向盈利。
|
||||
|
||||
扫描参数:
|
||||
- fast_ema: [8, 13, 15, 20]
|
||||
- slow_ema: [21, 34, 40, 55]
|
||||
- big_ema: [120, 200, 300]
|
||||
- atr_min_pct: [0.0003, 0.0005, 0.0008, 0.0012]
|
||||
- stop_loss: [0.003, 0.004, 0.005, 0.006]
|
||||
- max_hold: [900, 1200, 1800, 2700, 3600]
|
||||
"""
|
||||
|
||||
import time
|
||||
import datetime
|
||||
import sqlite3
|
||||
import itertools
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
# ========================= EMA =========================
|
||||
class EMA:
|
||||
def __init__(self, period):
|
||||
self.k = 2.0 / (period + 1)
|
||||
self.value = None
|
||||
|
||||
def update(self, price):
|
||||
if self.value is None:
|
||||
self.value = price
|
||||
else:
|
||||
self.value = price * self.k + self.value * (1 - self.k)
|
||||
return self.value
|
||||
|
||||
|
||||
# ========================= 数据加载 =========================
|
||||
def load_data(start_date='2025-01-01', end_date='2025-12-31'):
|
||||
db_path = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
start_ms = int(datetime.datetime.strptime(start_date, '%Y-%m-%d').timestamp()) * 1000
|
||||
end_ms = int((datetime.datetime.strptime(end_date, '%Y-%m-%d') + datetime.timedelta(days=1)).timestamp()) * 1000
|
||||
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id, open, high, low, close FROM bitmart_eth_1m WHERE id >= ? AND id < ? ORDER BY id",
|
||||
(start_ms, end_ms)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
|
||||
data = []
|
||||
for r in rows:
|
||||
data.append({
|
||||
'datetime': datetime.datetime.fromtimestamp(r[0] / 1000.0),
|
||||
'open': r[1], 'high': r[2], 'low': r[3], 'close': r[4],
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
# ========================= 快速回测引擎 =========================
|
||||
def run_ema_backtest(data, fast_p, slow_p, big_p, atr_period, atr_min,
|
||||
stop_loss_pct, hard_sl_pct, max_hold_sec,
|
||||
initial_balance=1000.0, leverage=50, risk_pct=0.005,
|
||||
taker_fee=0.0006, rebate_rate=0.90, min_hold_sec=200):
|
||||
"""
|
||||
快速回测 EMA 趋势策略,返回关键指标字典
|
||||
"""
|
||||
balance = initial_balance
|
||||
position = 0 # -1/0/1
|
||||
open_price = 0.0
|
||||
open_time = None
|
||||
pos_size = 0.0
|
||||
pending = None
|
||||
|
||||
ema_f = EMA(fast_p)
|
||||
ema_s = EMA(slow_p)
|
||||
ema_b = EMA(big_p)
|
||||
|
||||
highs = []
|
||||
lows = []
|
||||
closes = []
|
||||
prev_fast = None
|
||||
prev_slow = None
|
||||
|
||||
trade_count = 0
|
||||
win_count = 0
|
||||
total_dir_pnl = 0.0
|
||||
total_fee = 0.0
|
||||
total_rebate = 0.0
|
||||
|
||||
def calc_atr():
|
||||
if len(highs) < atr_period + 1:
|
||||
return None
|
||||
trs = []
|
||||
for i in range(-atr_period, 0):
|
||||
tr = max(highs[i] - lows[i],
|
||||
abs(highs[i] - closes[i-1]),
|
||||
abs(lows[i] - closes[i-1]))
|
||||
trs.append(tr)
|
||||
return sum(trs) / len(trs)
|
||||
|
||||
def do_open(direction, price, dt_):
|
||||
nonlocal balance, position, open_price, open_time, pos_size, total_fee
|
||||
ps = balance * risk_pct * leverage
|
||||
if ps < 1:
|
||||
return
|
||||
fee_ = ps * taker_fee
|
||||
balance -= fee_
|
||||
position = 1 if direction == 'long' else -1
|
||||
open_price = price
|
||||
open_time = dt_
|
||||
pos_size = ps
|
||||
|
||||
def do_close(price, dt_):
|
||||
nonlocal balance, position, open_price, open_time, pos_size
|
||||
nonlocal trade_count, win_count, total_dir_pnl, total_fee, total_rebate, pending
|
||||
if position == 0:
|
||||
return
|
||||
if position == 1:
|
||||
pp = (price - open_price) / open_price
|
||||
else:
|
||||
pp = (open_price - price) / open_price
|
||||
|
||||
pnl_ = pos_size * pp
|
||||
cv = pos_size * (1 + pp)
|
||||
cf = cv * taker_fee
|
||||
of = pos_size * taker_fee
|
||||
tf = of + cf
|
||||
rb = tf * rebate_rate
|
||||
|
||||
balance += pnl_ - cf + rb
|
||||
total_dir_pnl += pnl_
|
||||
total_fee += tf
|
||||
total_rebate += rb
|
||||
trade_count += 1
|
||||
if pnl_ > 0:
|
||||
win_count += 1
|
||||
|
||||
position = 0
|
||||
open_price = 0.0
|
||||
open_time = None
|
||||
pos_size = 0.0
|
||||
pending = None
|
||||
|
||||
for bar in data:
|
||||
price = bar['close']
|
||||
dt = bar['datetime']
|
||||
|
||||
highs.append(bar['high'])
|
||||
lows.append(bar['low'])
|
||||
closes.append(price)
|
||||
|
||||
fast = ema_f.update(price)
|
||||
slow = ema_s.update(price)
|
||||
big = ema_b.update(price)
|
||||
|
||||
atr = calc_atr()
|
||||
atr_pct = atr / price if atr and price > 0 else 0
|
||||
|
||||
cross_up = (prev_fast is not None and prev_fast <= prev_slow and fast > slow)
|
||||
cross_down = (prev_fast is not None and prev_fast >= prev_slow and fast < slow)
|
||||
prev_fast = fast
|
||||
prev_slow = slow
|
||||
|
||||
# === 有持仓 ===
|
||||
if position != 0 and open_time:
|
||||
if position == 1:
|
||||
p = (price - open_price) / open_price
|
||||
else:
|
||||
p = (open_price - price) / open_price
|
||||
hs = (dt - open_time).total_seconds()
|
||||
|
||||
# 硬止损
|
||||
if -p >= hard_sl_pct:
|
||||
do_close(price, dt)
|
||||
continue
|
||||
|
||||
can_close_ = hs >= min_hold_sec
|
||||
|
||||
if can_close_:
|
||||
# 止损
|
||||
if -p >= stop_loss_pct:
|
||||
do_close(price, dt)
|
||||
continue
|
||||
# 超时
|
||||
if hs >= max_hold_sec:
|
||||
do_close(price, dt)
|
||||
continue
|
||||
# 反手
|
||||
if position == 1 and cross_down:
|
||||
do_close(price, dt)
|
||||
if price < big and atr_pct >= atr_min:
|
||||
do_open('short', price, dt)
|
||||
continue
|
||||
if position == -1 and cross_up:
|
||||
do_close(price, dt)
|
||||
if price > big and atr_pct >= atr_min:
|
||||
do_open('long', price, dt)
|
||||
continue
|
||||
# 延迟信号
|
||||
if pending == 'close_long' and position == 1:
|
||||
do_close(price, dt)
|
||||
if fast < slow and price < big and atr_pct >= atr_min:
|
||||
do_open('short', price, dt)
|
||||
continue
|
||||
if pending == 'close_short' and position == -1:
|
||||
do_close(price, dt)
|
||||
if fast > slow and price > big and atr_pct >= atr_min:
|
||||
do_open('long', price, dt)
|
||||
continue
|
||||
else:
|
||||
if position == 1 and cross_down:
|
||||
pending = 'close_long'
|
||||
elif position == -1 and cross_up:
|
||||
pending = 'close_short'
|
||||
|
||||
# === 无持仓 ===
|
||||
if position == 0 and atr_pct >= atr_min:
|
||||
if cross_up and price > big:
|
||||
do_open('long', price, dt)
|
||||
elif cross_down and price < big:
|
||||
do_open('short', price, dt)
|
||||
|
||||
# 强制平仓
|
||||
if position != 0:
|
||||
do_close(data[-1]['close'], data[-1]['datetime'])
|
||||
|
||||
net = balance - initial_balance
|
||||
net_fee_cost = total_fee - total_rebate
|
||||
vol = total_fee / taker_fee if taker_fee > 0 else 0
|
||||
wr = win_count / trade_count * 100 if trade_count > 0 else 0
|
||||
avg_dir = total_dir_pnl / trade_count if trade_count > 0 else 0
|
||||
|
||||
return {
|
||||
'balance': balance,
|
||||
'net': net,
|
||||
'net_pct': net / initial_balance * 100,
|
||||
'trades': trade_count,
|
||||
'win_rate': wr,
|
||||
'dir_pnl': total_dir_pnl,
|
||||
'total_fee': total_fee,
|
||||
'rebate': total_rebate,
|
||||
'net_fee': net_fee_cost,
|
||||
'volume': vol,
|
||||
'avg_dir_pnl': avg_dir,
|
||||
}
|
||||
|
||||
|
||||
# ========================= 参数扫描 =========================
|
||||
def main():
|
||||
print("Loading data...")
|
||||
data = load_data('2025-01-01', '2025-12-31')
|
||||
print(f"Loaded {len(data)} bars\n")
|
||||
|
||||
# 参数组合
|
||||
param_grid = {
|
||||
'fast_p': [8, 13, 15, 20],
|
||||
'slow_p': [21, 34, 40, 55],
|
||||
'big_p': [120, 200, 300],
|
||||
'atr_min': [0.0003, 0.0005, 0.0008, 0.0012],
|
||||
'stop_loss_pct':[0.003, 0.004, 0.005, 0.008],
|
||||
'max_hold_sec': [900, 1200, 1800, 3600],
|
||||
}
|
||||
|
||||
# 过滤无效组合 (fast >= slow)
|
||||
combos = []
|
||||
for fp, sp, bp, am, sl, mh in itertools.product(
|
||||
param_grid['fast_p'], param_grid['slow_p'], param_grid['big_p'],
|
||||
param_grid['atr_min'], param_grid['stop_loss_pct'], param_grid['max_hold_sec']
|
||||
):
|
||||
if fp >= sp:
|
||||
continue
|
||||
combos.append((fp, sp, bp, am, sl, mh))
|
||||
|
||||
print(f"Total parameter combinations: {len(combos)}")
|
||||
print(f"Estimated time: ~{len(combos) * 2 / 60:.0f} minutes\n")
|
||||
|
||||
# 只跑最有潜力的子集(减少扫描时间)
|
||||
# 基于前次回测,聚焦在更长周期EMA(减少交易)+ 更高ATR过滤(质量过滤)
|
||||
focused_combos = []
|
||||
for fp, sp, bp, am, sl, mh in combos:
|
||||
# 过滤:聚焦减少交易次数的参数
|
||||
if fp < 8:
|
||||
continue
|
||||
if sp < 21:
|
||||
continue
|
||||
focused_combos.append((fp, sp, bp, am, sl, mh))
|
||||
|
||||
print(f"Focused combinations: {len(focused_combos)}")
|
||||
|
||||
# 如果组合太多,进一步采样
|
||||
if len(focused_combos) > 500:
|
||||
# 分两轮:先粗扫,再精调
|
||||
print("Phase 1: Coarse scan with subset...")
|
||||
coarse_combos = []
|
||||
for fp, sp, bp, am, sl, mh in focused_combos:
|
||||
if am in [0.0003, 0.0008] and sl in [0.004, 0.006] and mh in [1200, 1800]:
|
||||
coarse_combos.append((fp, sp, bp, am, sl, mh))
|
||||
elif am in [0.0005, 0.0012] and sl in [0.003, 0.005, 0.008] and mh in [900, 3600]:
|
||||
coarse_combos.append((fp, sp, bp, am, sl, mh))
|
||||
focused_combos = coarse_combos[:600] # cap
|
||||
print(f" Reduced to {len(focused_combos)} combos")
|
||||
|
||||
results = []
|
||||
t0 = time.time()
|
||||
|
||||
for idx, (fp, sp, bp, am, sl, mh) in enumerate(focused_combos):
|
||||
r = run_ema_backtest(
|
||||
data, fast_p=fp, slow_p=sp, big_p=bp,
|
||||
atr_period=14, atr_min=am,
|
||||
stop_loss_pct=sl, hard_sl_pct=sl * 1.5,
|
||||
max_hold_sec=mh,
|
||||
)
|
||||
r['params'] = f"EMA({fp}/{sp}/{bp}) ATR>{am*100:.2f}% SL={sl*100:.1f}% MaxH={mh}s"
|
||||
r['fp'] = fp
|
||||
r['sp'] = sp
|
||||
r['bp'] = bp
|
||||
r['am'] = am
|
||||
r['sl'] = sl
|
||||
r['mh'] = mh
|
||||
results.append(r)
|
||||
|
||||
if (idx + 1) % 50 == 0:
|
||||
elapsed = time.time() - t0
|
||||
eta = elapsed / (idx + 1) * (len(focused_combos) - idx - 1)
|
||||
print(f" [{idx+1}/{len(focused_combos)}] elapsed={elapsed:.0f}s eta={eta:.0f}s")
|
||||
|
||||
total_time = time.time() - t0
|
||||
print(f"\nScan complete! {len(results)} combos in {total_time:.1f}s")
|
||||
|
||||
# 按净收益排序
|
||||
results.sort(key=lambda x: x['net'], reverse=True)
|
||||
|
||||
# === 打印 Top 20 ===
|
||||
print(f"\n{'='*120}")
|
||||
print(f" TOP 20 PARAMETER COMBINATIONS (by Net P&L)")
|
||||
print(f"{'='*120}")
|
||||
print(f" {'#':>3} {'Params':<52} {'Net%':>7} {'Net$':>9} {'Trades':>7} {'WR':>6} {'DirPnL':>9} {'Rebate':>9} {'NetFee':>8}")
|
||||
print(f" {'-'*116}")
|
||||
|
||||
for i, r in enumerate(results[:20]):
|
||||
print(f" {i+1:>3} {r['params']:<52} {r['net_pct']:>+6.2f}% {r['net']:>+8.2f} {r['trades']:>7} {r['win_rate']:>5.1f}% {r['dir_pnl']:>+8.2f} {r['rebate']:>8.2f} {r['net_fee']:>8.2f}")
|
||||
|
||||
# === 打印 Bottom 5 (最差) ===
|
||||
print(f"\n BOTTOM 5:")
|
||||
print(f" {'-'*116}")
|
||||
for i, r in enumerate(results[-5:]):
|
||||
print(f" {len(results)-4+i:>3} {r['params']:<52} {r['net_pct']:>+6.2f}% {r['net']:>+8.2f} {r['trades']:>7} {r['win_rate']:>5.1f}% {r['dir_pnl']:>+8.2f} {r['rebate']:>8.2f} {r['net_fee']:>8.2f}")
|
||||
|
||||
print(f"{'='*120}")
|
||||
|
||||
# === 盈利的参数组合统计 ===
|
||||
profitable = [r for r in results if r['net'] > 0]
|
||||
print(f"\nProfitable combinations: {len(profitable)} / {len(results)} ({len(profitable)/len(results)*100:.1f}%)")
|
||||
|
||||
if profitable:
|
||||
print(f"\nAll profitable combinations:")
|
||||
print(f" {'#':>3} {'Params':<52} {'Net%':>7} {'Net$':>9} {'Trades':>7} {'WR':>6} {'DirPnL':>9} {'Rebate':>9}")
|
||||
print(f" {'-'*106}")
|
||||
for i, r in enumerate(profitable):
|
||||
print(f" {i+1:>3} {r['params']:<52} {r['net_pct']:>+6.2f}% {r['net']:>+8.2f} {r['trades']:>7} {r['win_rate']:>5.1f}% {r['dir_pnl']:>+8.2f} {r['rebate']:>8.2f}")
|
||||
|
||||
# 保存全部结果到CSV
|
||||
csv_path = Path(__file__).parent.parent / 'param_scan_results.csv'
|
||||
with open(csv_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("fast,slow,big,atr_min,stop_loss,max_hold,net_pct,net_usd,trades,win_rate,dir_pnl,rebate,net_fee,volume\n")
|
||||
for r in results:
|
||||
f.write(f"{r['fp']},{r['sp']},{r['bp']},{r['am']},{r['sl']},{r['mh']},"
|
||||
f"{r['net_pct']:.4f},{r['net']:.4f},{r['trades']},{r['win_rate']:.2f},"
|
||||
f"{r['dir_pnl']:.4f},{r['rebate']:.4f},{r['net_fee']:.4f},{r['volume']:.0f}\n")
|
||||
print(f"\nFull results saved: {csv_path}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
363
交易/bitmart-多策略回测.py
Normal file
363
交易/bitmart-多策略回测.py
Normal file
@@ -0,0 +1,363 @@
|
||||
"""
|
||||
多策略组合回测 — 目标: 1000 USDT/月
|
||||
|
||||
思路: 同时运行多个不同参数的 EMA 策略,它们在不同时间段产生信号,
|
||||
彼此信号不重叠时各自独立开仓,等于"多个策略并行跑"。
|
||||
|
||||
条件:
|
||||
- 每笔: 100 USDT 保证金, 100x 杠杆, 名义 10,000 USDT
|
||||
- 90% 返佣
|
||||
- 最低持仓 > 3 分钟
|
||||
- ETH 合约, 2025 全年
|
||||
|
||||
测试方案:
|
||||
A) 单策略加大仓位 (500U/1000U)
|
||||
B) 多策略组合 (3-5个不同参数策略并行)
|
||||
C) 降低 ATR 门槛 + 更宽止损
|
||||
D) 综合最优方案
|
||||
"""
|
||||
import sys, time, datetime, sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
|
||||
def run_strategy(data, fp, sp, bp, atr_min, sl_pct, mh, notional=10000.0):
|
||||
"""单策略回测,返回交易列表"""
|
||||
ef=EMA(fp); es=EMA(sp); eb=EMA(bp)
|
||||
H=[]; L=[]; C=[]
|
||||
pf_=None; ps_=None
|
||||
pos=0; op=0.0; ot=None; pend=None
|
||||
hsl=sl_pct*1.5; AP=14
|
||||
FEE_RATE=0.0006; REB_RATE=0.90; MIN_H=200
|
||||
trades=[]
|
||||
|
||||
for dt,o_,h_,l_,c_ in data:
|
||||
p=c_; H.append(h_); L.append(l_); C.append(p)
|
||||
fast=ef.update(p); slow=es.update(p); big=eb.update(p)
|
||||
atr_pct=0.0
|
||||
if len(H)>AP+1:
|
||||
s=0.0
|
||||
for i in range(-AP,0):
|
||||
tr=H[i]-L[i]; d1=abs(H[i]-C[i-1]); d2=abs(L[i]-C[i-1])
|
||||
if d1>tr: tr=d1
|
||||
if d2>tr: tr=d2
|
||||
s+=tr
|
||||
atr_pct=s/(AP*p) if p>0 else 0
|
||||
cu=pf_ is not None and pf_<=ps_ and fast>slow
|
||||
cd=pf_ is not None and pf_>=ps_ and fast<slow
|
||||
pf_=fast; ps_=slow
|
||||
|
||||
if pos!=0 and ot is not None:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
if -pp>=hsl:
|
||||
pnl=notional*pp; fee=notional*FEE_RATE*2; reb=fee*REB_RATE
|
||||
trades.append((pos, op, p, pnl, fee, reb, hsec, ot, dt))
|
||||
pos=0; op=0; ot=None; pend=None; continue
|
||||
if hsec>=MIN_H:
|
||||
dc=False
|
||||
if -pp>=sl_pct: dc=True
|
||||
elif hsec>=mh: dc=True
|
||||
elif pos==1 and cd: dc=True
|
||||
elif pos==-1 and cu: dc=True
|
||||
elif pend=='cl' and pos==1: dc=True
|
||||
elif pend=='cs' and pos==-1: dc=True
|
||||
if dc:
|
||||
pnl=notional*pp; fee=notional*FEE_RATE*2; reb=fee*REB_RATE
|
||||
trades.append((pos, op, p, pnl, fee, reb, hsec, ot, dt))
|
||||
pos=0; op=0; ot=None; pend=None
|
||||
if atr_pct>=atr_min:
|
||||
if (cd or fast<slow) and p<big: pos=-1; op=p; ot=dt
|
||||
elif (cu or fast>slow) and p>big: pos=1; op=p; ot=dt
|
||||
continue
|
||||
else:
|
||||
if pos==1 and cd: pend='cl'
|
||||
elif pos==-1 and cu: pend='cs'
|
||||
|
||||
if pos==0 and atr_pct>=atr_min:
|
||||
if cu and p>big: pos=1; op=p; ot=dt
|
||||
elif cd and p<big: pos=-1; op=p; ot=dt
|
||||
|
||||
if pos!=0:
|
||||
p=data[-1][4]; dt=data[-1][0]
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
pnl=notional*pp; fee=notional*FEE_RATE*2; reb=fee*REB_RATE
|
||||
trades.append((pos, op, p, pnl, fee, reb, (dt-ot).total_seconds(), ot, dt))
|
||||
return trades
|
||||
|
||||
|
||||
def analyze(trades, label, notional=10000.0):
|
||||
"""分析交易结果,返回摘要字典"""
|
||||
if not trades:
|
||||
return {'label': label, 'n': 0, 'net': 0, 'pnl': 0, 'reb': 0, 'fee': 0, 'dd': 0, 'monthly': {}}
|
||||
n = len(trades)
|
||||
total_pnl = sum(t[3] for t in trades)
|
||||
total_fee = sum(t[4] for t in trades)
|
||||
total_reb = sum(t[5] for t in trades)
|
||||
net = total_pnl - (total_fee - total_reb)
|
||||
|
||||
# 最大回撤
|
||||
cum=0; peak=0; dd=0
|
||||
for t in trades:
|
||||
cum += t[3] - (t[4] - t[5])
|
||||
if cum > peak: peak = cum
|
||||
if peak - cum > dd: dd = peak - cum
|
||||
|
||||
# 月度
|
||||
monthly = {}
|
||||
for t in trades:
|
||||
k = t[8].strftime('%Y-%m')
|
||||
if k not in monthly: monthly[k] = {'n': 0, 'net': 0}
|
||||
monthly[k]['n'] += 1
|
||||
monthly[k]['net'] += t[3] - (t[4] - t[5])
|
||||
|
||||
wr = len([t for t in trades if t[3]>0]) / n * 100
|
||||
return {'label': label, 'n': n, 'net': net, 'pnl': total_pnl, 'reb': total_reb,
|
||||
'fee': total_fee, 'dd': dd, 'wr': wr, 'monthly': monthly}
|
||||
|
||||
|
||||
def merge_trades(all_trade_lists):
|
||||
"""合并多个策略的交易(检查时间冲突:同一时间只能有一个持仓)"""
|
||||
# 简单合并:按开仓时间排序,跳过与已有持仓重叠的交易
|
||||
all_trades = []
|
||||
for trades in all_trade_lists:
|
||||
for t in trades:
|
||||
all_trades.append(t)
|
||||
all_trades.sort(key=lambda x: x[7]) # 按开仓时间排序
|
||||
|
||||
merged = []
|
||||
last_close = None
|
||||
for t in all_trades:
|
||||
open_time = t[7]
|
||||
close_time = t[8]
|
||||
if last_close is None or open_time >= last_close:
|
||||
merged.append(t)
|
||||
last_close = close_time
|
||||
return merged
|
||||
|
||||
|
||||
def print_comparison(results):
|
||||
"""打印对比表"""
|
||||
print(f"\n{'='*110}", flush=True)
|
||||
print(f" COMPARISON: Target = 1000 USDT/month = 12,000 USDT/year", flush=True)
|
||||
print(f"{'='*110}", flush=True)
|
||||
print(f" {'方案':<40} {'交易数':>6} {'年净利':>10} {'月均':>8} {'胜率':>6} {'返佣':>10} {'最大回撤':>10} {'达标':>4}", flush=True)
|
||||
print(f" {'-'*104}", flush=True)
|
||||
|
||||
for r in results:
|
||||
monthly_avg = r['net'] / 12
|
||||
ok = "Yes" if monthly_avg >= 1000 else "No"
|
||||
print(f" {r['label']:<40} {r['n']:>6} {r['net']:>+10.0f} {monthly_avg:>+8.0f} {r.get('wr',0):>5.1f}% {r['reb']:>10.0f} {r['dd']:>10.0f} {ok:>4}", flush=True)
|
||||
|
||||
print(f"{'='*110}", flush=True)
|
||||
|
||||
# 打印最佳方案的月度明细
|
||||
best = max(results, key=lambda x: x['net'])
|
||||
print(f"\n 最佳方案: {best['label']}", flush=True)
|
||||
print(f" 年净利: {best['net']:+.0f} USDT | 月均: {best['net']/12:+.0f} USDT\n", flush=True)
|
||||
|
||||
if best['monthly']:
|
||||
print(f" {'月份':<8} {'笔数':>5} {'净利润':>10}", flush=True)
|
||||
print(f" {'-'*28}", flush=True)
|
||||
for m in sorted(best['monthly'].keys()):
|
||||
d = best['monthly'][m]
|
||||
print(f" {m:<8} {d['n']:>5} {d['net']:>+10.0f}", flush=True)
|
||||
print(f" {'-'*28}", flush=True)
|
||||
print(f" {'合计':<8} {best['n']:>5} {best['net']:>+10.0f}", flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
print("Loading data...", flush=True)
|
||||
data = load()
|
||||
print(f"{len(data)} bars loaded\n", flush=True)
|
||||
|
||||
NOTIONAL = 10000.0 # 100U * 100x
|
||||
results = []
|
||||
|
||||
# ============================
|
||||
# 方案 1: 原始策略(基线)
|
||||
# ============================
|
||||
t1 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, NOTIONAL)
|
||||
results.append(analyze(t1, "A1: EMA(8/21) ATR>0.3% [基线]", NOTIONAL))
|
||||
print(f" A1 done: {len(t1)} trades", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 2: 加大仓位 - 500U (5倍)
|
||||
# ============================
|
||||
t2 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 50000.0)
|
||||
results.append(analyze(t2, "A2: 基线 x5 (500U保证金)", 50000.0))
|
||||
print(f" A2 done: {len(t2)} trades", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 3: 加大仓位 - 1000U (10倍)
|
||||
# ============================
|
||||
t3 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 100000.0)
|
||||
results.append(analyze(t3, "A3: 基线 x10 (1000U保证金)", 100000.0))
|
||||
print(f" A3 done: {len(t3)} trades", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 4: 降低ATR到0.15%(更多交易)
|
||||
# ============================
|
||||
t4 = run_strategy(data, 8, 21, 120, 0.0015, 0.004, 1800, NOTIONAL)
|
||||
results.append(analyze(t4, "B1: ATR>0.15% (更频繁)", NOTIONAL))
|
||||
print(f" B1 done: {len(t4)} trades", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 5: ATR>0.1% + 宽止损0.8%
|
||||
# ============================
|
||||
t5 = run_strategy(data, 8, 21, 120, 0.001, 0.008, 1800, NOTIONAL)
|
||||
results.append(analyze(t5, "B2: ATR>0.1% SL=0.8%", NOTIONAL))
|
||||
print(f" B2 done: {len(t5)} trades", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 6: ATR>0.2% + SL=0.8% + 更长持仓3600s
|
||||
# ============================
|
||||
t6 = run_strategy(data, 8, 21, 120, 0.002, 0.008, 3600, NOTIONAL)
|
||||
results.append(analyze(t6, "B3: ATR>0.2% SL=0.8% MH=3600", NOTIONAL))
|
||||
print(f" B3 done: {len(t6)} trades", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 7: 多策略组合(3个不同EMA参数并行,无时间重叠)
|
||||
# ============================
|
||||
s1 = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, NOTIONAL)
|
||||
s2 = run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, NOTIONAL)
|
||||
s3 = run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, NOTIONAL)
|
||||
merged3 = merge_trades([s1, s2, s3])
|
||||
results.append(analyze(merged3, "C1: 3策略组合 (不重叠)", NOTIONAL))
|
||||
print(f" C1 done: {len(s1)}+{len(s2)}+{len(s3)} -> {len(merged3)} merged", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 8: 5策略组合
|
||||
# ============================
|
||||
s4 = run_strategy(data, 20, 55, 120, 0.002, 0.006, 1800, NOTIONAL)
|
||||
s5 = run_strategy(data, 8, 34, 200, 0.002, 0.005, 1800, NOTIONAL)
|
||||
merged5 = merge_trades([s1, s2, s3, s4, s5])
|
||||
results.append(analyze(merged5, "C2: 5策略组合 (不重叠)", NOTIONAL))
|
||||
print(f" C2 done: -> {len(merged5)} merged", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 9: 3策略组合 + 加大仓位到 300U
|
||||
# ============================
|
||||
s1b = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 30000.0)
|
||||
s2b = run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, 30000.0)
|
||||
s3b = run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, 30000.0)
|
||||
merged3b = merge_trades([s1b, s2b, s3b])
|
||||
results.append(analyze(merged3b, "D1: 3策略+300U仓位", 30000.0))
|
||||
print(f" D1 done: -> {len(merged3b)} merged", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 10: 3策略组合 + 500U仓位
|
||||
# ============================
|
||||
s1c = run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, 50000.0)
|
||||
s2c = run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, 50000.0)
|
||||
s3c = run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, 50000.0)
|
||||
merged3c = merge_trades([s1c, s2c, s3c])
|
||||
results.append(analyze(merged3c, "D2: 3策略+500U仓位", 50000.0))
|
||||
print(f" D2 done: -> {len(merged3c)} merged", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 11: 多策略并行(允许同时持仓,各策略独立运行)
|
||||
# ============================
|
||||
# 如果账户允许同时持有多个仓位(不同策略各自独立)
|
||||
s1_r = analyze(s1, "sub1")
|
||||
s2_r = analyze(run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, NOTIONAL), "sub2")
|
||||
s3_r = analyze(run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, NOTIONAL), "sub3")
|
||||
s4_r = analyze(run_strategy(data, 20, 55, 120, 0.002, 0.006, 1800, NOTIONAL), "sub4")
|
||||
s5_r = analyze(run_strategy(data, 8, 34, 200, 0.002, 0.005, 1800, NOTIONAL), "sub5")
|
||||
|
||||
# 合并月度(允许重叠 = 各自独立计算再加总)
|
||||
parallel_net = s1_r['net'] + s2_r['net'] + s3_r['net'] + s4_r['net'] + s5_r['net']
|
||||
parallel_reb = s1_r['reb'] + s2_r['reb'] + s3_r['reb'] + s4_r['reb'] + s5_r['reb']
|
||||
parallel_fee = s1_r['fee'] + s2_r['fee'] + s3_r['fee'] + s4_r['fee'] + s5_r['fee']
|
||||
parallel_pnl = s1_r['pnl'] + s2_r['pnl'] + s3_r['pnl'] + s4_r['pnl'] + s5_r['pnl']
|
||||
parallel_n = s1_r['n'] + s2_r['n'] + s3_r['n'] + s4_r['n'] + s5_r['n']
|
||||
parallel_dd = max(s1_r['dd'], s2_r['dd'], s3_r['dd'], s4_r['dd'], s5_r['dd']) * 2 # 保守估计
|
||||
|
||||
# 合并月度
|
||||
all_months = set()
|
||||
for sr in [s1_r, s2_r, s3_r, s4_r, s5_r]:
|
||||
all_months.update(sr['monthly'].keys())
|
||||
parallel_monthly = {}
|
||||
for m in all_months:
|
||||
n_m = 0; net_m = 0
|
||||
for sr in [s1_r, s2_r, s3_r, s4_r, s5_r]:
|
||||
if m in sr['monthly']:
|
||||
n_m += sr['monthly'][m]['n']
|
||||
net_m += sr['monthly'][m]['net']
|
||||
parallel_monthly[m] = {'n': n_m, 'net': net_m}
|
||||
|
||||
results.append({
|
||||
'label': "E1: 5策略并行(允许同时持仓) 100U each",
|
||||
'n': parallel_n, 'net': parallel_net, 'pnl': parallel_pnl,
|
||||
'reb': parallel_reb, 'fee': parallel_fee, 'dd': parallel_dd,
|
||||
'wr': 0, 'monthly': parallel_monthly
|
||||
})
|
||||
print(f" E1 done: {parallel_n} total trades (parallel)", flush=True)
|
||||
|
||||
# ============================
|
||||
# 方案 12: 5策略并行 + 200U仓位
|
||||
# ============================
|
||||
N2 = 20000.0
|
||||
ps1 = analyze(run_strategy(data, 8, 21, 120, 0.003, 0.004, 1800, N2), "p1")
|
||||
ps2 = analyze(run_strategy(data, 13, 55, 200, 0.002, 0.005, 1800, N2), "p2")
|
||||
ps3 = analyze(run_strategy(data, 30, 80, 200, 0.002, 0.008, 3600, N2), "p3")
|
||||
ps4 = analyze(run_strategy(data, 20, 55, 120, 0.002, 0.006, 1800, N2), "p4")
|
||||
ps5 = analyze(run_strategy(data, 8, 34, 200, 0.002, 0.005, 1800, N2), "p5")
|
||||
|
||||
p_net = ps1['net']+ps2['net']+ps3['net']+ps4['net']+ps5['net']
|
||||
p_reb = ps1['reb']+ps2['reb']+ps3['reb']+ps4['reb']+ps5['reb']
|
||||
p_fee = ps1['fee']+ps2['fee']+ps3['fee']+ps4['fee']+ps5['fee']
|
||||
p_pnl = ps1['pnl']+ps2['pnl']+ps3['pnl']+ps4['pnl']+ps5['pnl']
|
||||
p_n = ps1['n']+ps2['n']+ps3['n']+ps4['n']+ps5['n']
|
||||
p_dd = max(ps1['dd'],ps2['dd'],ps3['dd'],ps4['dd'],ps5['dd'])*2
|
||||
|
||||
p_monthly = {}
|
||||
for m in all_months:
|
||||
n_m=0; net_m=0
|
||||
for sr in [ps1,ps2,ps3,ps4,ps5]:
|
||||
if m in sr['monthly']:
|
||||
n_m+=sr['monthly'][m]['n']; net_m+=sr['monthly'][m]['net']
|
||||
p_monthly[m] = {'n': n_m, 'net': net_m}
|
||||
|
||||
results.append({
|
||||
'label': "E2: 5策略并行 200U each",
|
||||
'n': p_n, 'net': p_net, 'pnl': p_pnl,
|
||||
'reb': p_reb, 'fee': p_fee, 'dd': p_dd,
|
||||
'wr': 0, 'monthly': p_monthly
|
||||
})
|
||||
print(f" E2 done: {p_n} total trades (parallel 200U)", flush=True)
|
||||
|
||||
# ============================
|
||||
# 打印对比
|
||||
# ============================
|
||||
print_comparison(results)
|
||||
|
||||
# 保存
|
||||
csv = Path(__file__).parent.parent / 'multi_strategy_results.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("方案,交易数,年净利,月均,返佣,最大回撤\n")
|
||||
for r in results:
|
||||
f.write(f"{r['label']},{r['n']},{r['net']:.0f},{r['net']/12:.0f},{r['reb']:.0f},{r['dd']:.0f}\n")
|
||||
print(f"\nSaved: {csv}", flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
289
交易/bitmart-快速优化.py
Normal file
289
交易/bitmart-快速优化.py
Normal file
@@ -0,0 +1,289 @@
|
||||
"""
|
||||
EMA趋势策略 - 快速参数优化(精简版)
|
||||
|
||||
前次发现:EMA(8/21/120) 方向盈利 +327,但10%费用成本 428,净亏 -101。
|
||||
优化方向:用更长EMA减少交易次数 + 更高ATR过滤提高质量。
|
||||
"""
|
||||
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class EMA:
|
||||
def __init__(self, period):
|
||||
self.k = 2.0 / (period + 1)
|
||||
self.value = None
|
||||
def update(self, price):
|
||||
if self.value is None:
|
||||
self.value = price
|
||||
else:
|
||||
self.value = price * self.k + self.value * (1 - self.k)
|
||||
return self.value
|
||||
|
||||
|
||||
def load_data():
|
||||
db_path = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
start_ms = int(datetime.datetime(2025, 1, 1).timestamp()) * 1000
|
||||
end_ms = int(datetime.datetime(2026, 1, 1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id, open, high, low, close FROM bitmart_eth_1m WHERE id >= ? AND id < ? ORDER BY id",
|
||||
(start_ms, end_ms)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
data = []
|
||||
for r in rows:
|
||||
data.append((
|
||||
datetime.datetime.fromtimestamp(r[0] / 1000.0),
|
||||
r[1], r[2], r[3], r[4], # open, high, low, close
|
||||
))
|
||||
return data
|
||||
|
||||
|
||||
def backtest(data, fp, sp, bp, atr_min, sl_pct, mh):
|
||||
bal = 1000.0
|
||||
pos = 0
|
||||
op = 0.0
|
||||
ot = None
|
||||
ps = 0.0
|
||||
pending = None
|
||||
|
||||
ef = EMA(fp)
|
||||
es = EMA(sp)
|
||||
eb = EMA(bp)
|
||||
|
||||
highs = []
|
||||
lows = []
|
||||
closes = []
|
||||
pf = None
|
||||
pslow = None
|
||||
|
||||
tc = 0
|
||||
wc = 0
|
||||
dir_pnl = 0.0
|
||||
tot_fee = 0.0
|
||||
tot_reb = 0.0
|
||||
|
||||
hsl = sl_pct * 1.5
|
||||
lev = 50
|
||||
rp = 0.005
|
||||
tf_rate = 0.0006
|
||||
reb_rate = 0.90
|
||||
min_h = 200
|
||||
|
||||
for dt, o_, h_, l_, c_ in data:
|
||||
price = c_
|
||||
highs.append(h_)
|
||||
lows.append(l_)
|
||||
closes.append(price)
|
||||
|
||||
fast = ef.update(price)
|
||||
slow = es.update(price)
|
||||
big = eb.update(price)
|
||||
|
||||
# ATR
|
||||
atr_pct = 0
|
||||
ap = 14
|
||||
if len(highs) > ap + 1:
|
||||
trs = []
|
||||
for i in range(-ap, 0):
|
||||
tr = max(highs[i] - lows[i], abs(highs[i] - closes[i-1]), abs(lows[i] - closes[i-1]))
|
||||
trs.append(tr)
|
||||
atr_pct = (sum(trs) / ap) / price if price > 0 else 0
|
||||
|
||||
cu = pf is not None and pf <= pslow and fast > slow
|
||||
cd = pf is not None and pf >= pslow and fast < slow
|
||||
pf = fast
|
||||
pslow = slow
|
||||
|
||||
if pos != 0 and ot:
|
||||
if pos == 1:
|
||||
p = (price - op) / op
|
||||
else:
|
||||
p = (op - price) / op
|
||||
hs = (dt - ot).total_seconds()
|
||||
|
||||
if -p >= hsl:
|
||||
# close
|
||||
pnl_ = ps * p
|
||||
cv = ps * (1 + p)
|
||||
cf = cv * tf_rate
|
||||
of_ = ps * tf_rate
|
||||
ttf = of_ + cf
|
||||
rb = ttf * reb_rate
|
||||
bal += pnl_ - cf + rb
|
||||
dir_pnl += pnl_
|
||||
tot_fee += ttf
|
||||
tot_reb += rb
|
||||
tc += 1
|
||||
if pnl_ > 0: wc += 1
|
||||
pos = 0; op = 0; ot = None; ps = 0; pending = None
|
||||
continue
|
||||
|
||||
can_c = hs >= min_h
|
||||
|
||||
if can_c:
|
||||
do_close = False
|
||||
if -p >= sl_pct:
|
||||
do_close = True
|
||||
elif hs >= mh:
|
||||
do_close = True
|
||||
elif pos == 1 and cd:
|
||||
do_close = True
|
||||
elif pos == -1 and cu:
|
||||
do_close = True
|
||||
elif pending == 'cl' and pos == 1:
|
||||
do_close = True
|
||||
elif pending == 'cs' and pos == -1:
|
||||
do_close = True
|
||||
|
||||
if do_close:
|
||||
pnl_ = ps * p
|
||||
cv = ps * (1 + p)
|
||||
cf = cv * tf_rate
|
||||
of_ = ps * tf_rate
|
||||
ttf = of_ + cf
|
||||
rb = ttf * reb_rate
|
||||
bal += pnl_ - cf + rb
|
||||
dir_pnl += pnl_
|
||||
tot_fee += ttf
|
||||
tot_reb += rb
|
||||
tc += 1
|
||||
if pnl_ > 0: wc += 1
|
||||
pos = 0; op = 0; ot = None; ps = 0; pending = None
|
||||
|
||||
# re-enter
|
||||
if atr_pct >= atr_min:
|
||||
if (cd or (fast < slow)) and price < big:
|
||||
ns = bal * rp * lev
|
||||
if ns >= 1:
|
||||
bal -= ns * tf_rate
|
||||
pos = -1; op = price; ot = dt; ps = ns
|
||||
elif (cu or (fast > slow)) and price > big:
|
||||
ns = bal * rp * lev
|
||||
if ns >= 1:
|
||||
bal -= ns * tf_rate
|
||||
pos = 1; op = price; ot = dt; ps = ns
|
||||
continue
|
||||
else:
|
||||
if pos == 1 and cd: pending = 'cl'
|
||||
elif pos == -1 and cu: pending = 'cs'
|
||||
|
||||
if pos == 0 and atr_pct >= atr_min:
|
||||
if cu and price > big:
|
||||
ns = bal * rp * lev
|
||||
if ns >= 1:
|
||||
bal -= ns * tf_rate
|
||||
pos = 1; op = price; ot = dt; ps = ns
|
||||
elif cd and price < big:
|
||||
ns = bal * rp * lev
|
||||
if ns >= 1:
|
||||
bal -= ns * tf_rate
|
||||
pos = -1; op = price; ot = dt; ps = ns
|
||||
|
||||
# force close
|
||||
if pos != 0:
|
||||
price = data[-1][4]
|
||||
if pos == 1:
|
||||
p = (price - op) / op
|
||||
else:
|
||||
p = (op - price) / op
|
||||
pnl_ = ps * p
|
||||
cv = ps * (1 + p)
|
||||
cf = cv * tf_rate
|
||||
of_ = ps * tf_rate
|
||||
ttf = of_ + cf
|
||||
rb = ttf * reb_rate
|
||||
bal += pnl_ - cf + rb
|
||||
dir_pnl += pnl_
|
||||
tot_fee += ttf
|
||||
tot_reb += rb
|
||||
tc += 1
|
||||
if pnl_ > 0: wc += 1
|
||||
|
||||
net = bal - 1000.0
|
||||
wr = wc / tc * 100 if tc > 0 else 0
|
||||
return net, tc, wr, dir_pnl, tot_reb, tot_fee - tot_reb
|
||||
|
||||
|
||||
def main():
|
||||
print("Loading data...", flush=True)
|
||||
data = load_data()
|
||||
print(f"Loaded {len(data)} bars", flush=True)
|
||||
|
||||
# Focused parameter grid (smaller)
|
||||
fast_list = [8, 13, 20]
|
||||
slow_list = [21, 34, 55]
|
||||
big_list = [120, 200]
|
||||
atr_list = [0.0003, 0.0006, 0.001, 0.0015]
|
||||
sl_list = [0.003, 0.005, 0.008]
|
||||
mh_list = [900, 1800, 3600]
|
||||
|
||||
combos = []
|
||||
for fp in fast_list:
|
||||
for sp in slow_list:
|
||||
if fp >= sp: continue
|
||||
for bp in big_list:
|
||||
for am in atr_list:
|
||||
for sl in sl_list:
|
||||
for mh in mh_list:
|
||||
combos.append((fp, sp, bp, am, sl, mh))
|
||||
|
||||
print(f"\nTotal combos: {len(combos)}", flush=True)
|
||||
results = []
|
||||
t0 = time.time()
|
||||
|
||||
for idx, (fp, sp, bp, am, sl, mh) in enumerate(combos):
|
||||
net, tc, wr, dp, reb, nf = backtest(data, fp, sp, bp, am, sl, mh)
|
||||
results.append((net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh))
|
||||
|
||||
if (idx + 1) % 20 == 0:
|
||||
elapsed = time.time() - t0
|
||||
eta = elapsed / (idx + 1) * (len(combos) - idx - 1)
|
||||
print(f" [{idx+1}/{len(combos)}] {elapsed:.0f}s elapsed, ~{eta:.0f}s remaining", flush=True)
|
||||
|
||||
total_time = time.time() - t0
|
||||
print(f"\nDone! {len(results)} combos in {total_time:.1f}s\n", flush=True)
|
||||
|
||||
# Sort by net P&L
|
||||
results.sort(key=lambda x: x[0], reverse=True)
|
||||
|
||||
# Print results
|
||||
profitable = [r for r in results if r[0] > 0]
|
||||
print(f"Profitable: {len(profitable)} / {len(results)} ({len(profitable)/len(results)*100:.1f}%)\n")
|
||||
|
||||
print(f"{'='*130}")
|
||||
print(f" TOP 30 RESULTS")
|
||||
print(f"{'='*130}")
|
||||
header = f" {'#':>3} {'Fast':>4} {'Slow':>4} {'Big':>4} {'ATR%':>6} {'SL%':>5} {'MaxH':>5} | {'Net%':>7} {'Net$':>9} {'Trades':>7} {'WR':>6} {'DirPnL':>9} {'Rebate':>9} {'NetFee':>8}"
|
||||
print(header)
|
||||
print(f" {'-'*126}")
|
||||
|
||||
for i, (net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh) in enumerate(results[:30]):
|
||||
net_pct = net / 10 # net / 1000 * 100
|
||||
marker = " ***" if net > 0 else ""
|
||||
print(f" {i+1:>3} {fp:>4} {sp:>4} {bp:>4} {am*100:>5.2f}% {sl*100:>4.1f}% {mh:>5} | {net_pct:>+6.2f}% {net:>+8.2f} {tc:>7} {wr:>5.1f}% {dp:>+8.2f} {reb:>8.2f} {nf:>8.2f}{marker}")
|
||||
|
||||
print(f"{'='*130}")
|
||||
|
||||
if profitable:
|
||||
print(f"\nALL PROFITABLE:")
|
||||
print(f" {'-'*126}")
|
||||
for i, (net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh) in enumerate(profitable):
|
||||
net_pct = net / 10
|
||||
print(f" {i+1:>3} EMA({fp}/{sp}/{bp}) ATR>{am*100:.2f}% SL={sl*100:.1f}% MaxH={mh}s | {net_pct:>+6.2f}% {net:>+8.2f} trades={tc} WR={wr:.1f}% DirPnL={dp:+.2f} Rebate={reb:.2f}")
|
||||
|
||||
# Save CSV
|
||||
csv_path = Path(__file__).parent.parent / 'param_results.csv'
|
||||
with open(csv_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("fast,slow,big,atr_min,stop_loss,max_hold,net_pct,net_usd,trades,win_rate,dir_pnl,rebate,net_fee\n")
|
||||
for net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh in results:
|
||||
f.write(f"{fp},{sp},{bp},{am},{sl},{mh},{net/10:.4f},{net:.4f},{tc},{wr:.2f},{dp:.4f},{reb:.4f},{nf:.4f}\n")
|
||||
print(f"\nResults saved: {csv_path}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
673
交易/bitmart-新策略回测.py
Normal file
673
交易/bitmart-新策略回测.py
Normal file
@@ -0,0 +1,673 @@
|
||||
"""
|
||||
BitMart 返佣策略回测 — 双策略对比
|
||||
|
||||
策略A: 网格交易 (Grid Trading)
|
||||
- 围绕EMA中轨设定网格,价格触及网格线时开仓
|
||||
- 固定止盈(1格)、固定止损(3格)
|
||||
- 趋势过滤:只在趋势方向开仓
|
||||
|
||||
策略B: EMA趋势跟随 (EMA Trend Following)
|
||||
- 快慢EMA金叉做多、死叉做空
|
||||
- 始终持仓,信号反转时反手
|
||||
- 大级别趋势过滤避免逆势
|
||||
|
||||
两个策略都:
|
||||
- 严格执行 >3分钟最低持仓
|
||||
- 计算90%返佣收益
|
||||
- 输出详细对比报告
|
||||
"""
|
||||
|
||||
import time
|
||||
import datetime
|
||||
import statistics
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
# ========================= 简易 Logger =========================
|
||||
class _L:
|
||||
@staticmethod
|
||||
def info(m): print(f"[INFO] {m}")
|
||||
@staticmethod
|
||||
def ok(m): print(f"[ OK ] {m}")
|
||||
@staticmethod
|
||||
def warn(m): print(f"[WARN] {m}")
|
||||
@staticmethod
|
||||
def err(m): print(f"[ERR ] {m}")
|
||||
|
||||
log = _L()
|
||||
|
||||
|
||||
# ========================= 交易记录 =========================
|
||||
@dataclass
|
||||
class Trade:
|
||||
open_time: datetime.datetime
|
||||
close_time: datetime.datetime
|
||||
direction: str
|
||||
open_price: float
|
||||
close_price: float
|
||||
size: float
|
||||
pnl: float
|
||||
pnl_pct: float
|
||||
fee: float
|
||||
rebate: float
|
||||
hold_seconds: float
|
||||
close_reason: str
|
||||
|
||||
|
||||
# ========================= 数据加载 =========================
|
||||
def load_1m_klines(start_date='2025-01-01', end_date='2025-12-31'):
|
||||
db_path = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
start_dt = datetime.datetime.strptime(start_date, '%Y-%m-%d')
|
||||
end_dt = datetime.datetime.strptime(end_date, '%Y-%m-%d') + datetime.timedelta(days=1)
|
||||
start_ms = int(start_dt.timestamp()) * 1000
|
||||
end_ms = int(end_dt.timestamp()) * 1000
|
||||
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT id, open, high, low, close FROM bitmart_eth_1m "
|
||||
"WHERE id >= ? AND id < ? ORDER BY id",
|
||||
(start_ms, end_ms)
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
data = []
|
||||
for r in rows:
|
||||
dt = datetime.datetime.fromtimestamp(r[0] / 1000.0)
|
||||
data.append({
|
||||
'datetime': dt,
|
||||
'open': r[1], 'high': r[2], 'low': r[3], 'close': r[4],
|
||||
})
|
||||
log.info(f"Loaded {len(data)} bars ({start_date} ~ {end_date})")
|
||||
return data
|
||||
|
||||
|
||||
# ========================= EMA 工具 =========================
|
||||
class EMA:
|
||||
def __init__(self, period):
|
||||
self.period = period
|
||||
self.k = 2.0 / (period + 1)
|
||||
self.value = None
|
||||
|
||||
def update(self, price):
|
||||
if self.value is None:
|
||||
self.value = price
|
||||
else:
|
||||
self.value = price * self.k + self.value * (1 - self.k)
|
||||
return self.value
|
||||
|
||||
|
||||
# ========================= 基础回测引擎 =========================
|
||||
class BaseBacktest:
|
||||
def __init__(self, name, initial_balance=1000.0, leverage=50,
|
||||
risk_pct=0.005, taker_fee=0.0006, rebate_rate=0.90,
|
||||
min_hold_sec=200, max_hold_sec=1800):
|
||||
self.name = name
|
||||
self.initial_balance = initial_balance
|
||||
self.balance = initial_balance
|
||||
self.leverage = leverage
|
||||
self.risk_pct = risk_pct
|
||||
self.taker_fee = taker_fee
|
||||
self.rebate_rate = rebate_rate
|
||||
self.min_hold_sec = min_hold_sec
|
||||
self.max_hold_sec = max_hold_sec
|
||||
|
||||
self.position = 0
|
||||
self.open_price = 0.0
|
||||
self.open_time = None
|
||||
self.pos_size = 0.0
|
||||
|
||||
self.trades: List[Trade] = []
|
||||
self.equity_curve = []
|
||||
self.peak_equity = initial_balance
|
||||
self.max_dd_pct = 0.0
|
||||
|
||||
def _open(self, direction, price, dt):
|
||||
self.pos_size = self.balance * self.risk_pct * self.leverage
|
||||
if self.pos_size < 1:
|
||||
return False
|
||||
fee = self.pos_size * self.taker_fee
|
||||
self.balance -= fee
|
||||
self.position = 1 if direction == 'long' else -1
|
||||
self.open_price = price
|
||||
self.open_time = dt
|
||||
return True
|
||||
|
||||
def _close(self, price, dt, reason):
|
||||
if self.position == 0:
|
||||
return None
|
||||
if self.position == 1:
|
||||
pnl_pct = (price - self.open_price) / self.open_price
|
||||
else:
|
||||
pnl_pct = (self.open_price - price) / self.open_price
|
||||
|
||||
pnl = self.pos_size * pnl_pct
|
||||
close_val = self.pos_size * (1 + pnl_pct)
|
||||
close_fee = close_val * self.taker_fee
|
||||
open_fee = self.pos_size * self.taker_fee
|
||||
total_fee = open_fee + close_fee
|
||||
rebate = total_fee * self.rebate_rate
|
||||
|
||||
self.balance += pnl - close_fee + rebate
|
||||
hold_sec = (dt - self.open_time).total_seconds()
|
||||
|
||||
trade = Trade(
|
||||
open_time=self.open_time, close_time=dt,
|
||||
direction='long' if self.position == 1 else 'short',
|
||||
open_price=self.open_price, close_price=price,
|
||||
size=self.pos_size, pnl=pnl, pnl_pct=pnl_pct,
|
||||
fee=total_fee, rebate=rebate,
|
||||
hold_seconds=hold_sec, close_reason=reason,
|
||||
)
|
||||
self.trades.append(trade)
|
||||
self.position = 0
|
||||
self.open_price = 0.0
|
||||
self.open_time = None
|
||||
self.pos_size = 0.0
|
||||
return trade
|
||||
|
||||
def hold_seconds(self, dt):
|
||||
if self.open_time is None:
|
||||
return 0
|
||||
return (dt - self.open_time).total_seconds()
|
||||
|
||||
def can_close(self, dt):
|
||||
return self.hold_seconds(dt) >= self.min_hold_sec
|
||||
|
||||
def cur_pnl_pct(self, price):
|
||||
if self.position == 1:
|
||||
return (price - self.open_price) / self.open_price
|
||||
elif self.position == -1:
|
||||
return (self.open_price - price) / self.open_price
|
||||
return 0
|
||||
|
||||
def track_equity(self, dt, price, every_n=60, bar_idx=0):
|
||||
if bar_idx % every_n != 0:
|
||||
return
|
||||
eq = self.balance
|
||||
if self.position != 0 and self.open_price > 0:
|
||||
eq += self.pos_size * self.cur_pnl_pct(price)
|
||||
self.equity_curve.append({'datetime': dt, 'equity': eq})
|
||||
if eq > self.peak_equity:
|
||||
self.peak_equity = eq
|
||||
dd = (self.peak_equity - eq) / self.peak_equity if self.peak_equity > 0 else 0
|
||||
if dd > self.max_dd_pct:
|
||||
self.max_dd_pct = dd
|
||||
|
||||
|
||||
# ========================= 策略A: 网格交易 =========================
|
||||
class GridStrategy(BaseBacktest):
|
||||
"""
|
||||
网格交易 + 趋势过滤
|
||||
|
||||
- 用 EMA(120) 判断趋势方向
|
||||
- 网格间距 = grid_pct (如 0.20%)
|
||||
- 顺势开仓:上涨趋势中价格回落到网格线做多,下跌趋势中价格反弹到网格线做空
|
||||
- TP: tp_grids 格 (如 1格 = 0.20%)
|
||||
- SL: sl_grids 格 (如 3格 = 0.60%)
|
||||
- 最低持仓 200 秒
|
||||
"""
|
||||
|
||||
def __init__(self, grid_pct=0.0020, tp_grids=1, sl_grids=3,
|
||||
trend_ema_period=120, **kwargs):
|
||||
super().__init__(name="Grid+Trend", **kwargs)
|
||||
self.grid_pct = grid_pct
|
||||
self.tp_pct = grid_pct * tp_grids
|
||||
self.sl_pct = grid_pct * sl_grids
|
||||
self.hard_sl_pct = grid_pct * (sl_grids + 1)
|
||||
self.trend_ema = EMA(trend_ema_period)
|
||||
self.last_grid_cross = None # 上一次穿越的网格线价格
|
||||
self.cooldown_until = None # 冷却期
|
||||
|
||||
def get_grid_level(self, price, direction='below'):
|
||||
"""获取价格最近的网格线"""
|
||||
grid_size = price * self.grid_pct
|
||||
if grid_size == 0:
|
||||
return price
|
||||
if direction == 'below':
|
||||
return price - (price % grid_size)
|
||||
else:
|
||||
return price - (price % grid_size) + grid_size
|
||||
|
||||
def run(self, data):
|
||||
log.info(f"[{self.name}] Starting... grid={self.grid_pct*100:.2f}% TP={self.tp_pct*100:.2f}% SL={self.sl_pct*100:.2f}%")
|
||||
t0 = time.time()
|
||||
prev_close = None
|
||||
|
||||
for i, bar in enumerate(data):
|
||||
price = bar['close']
|
||||
high = bar['high']
|
||||
low = bar['low']
|
||||
dt = bar['datetime']
|
||||
|
||||
ema_val = self.trend_ema.update(price)
|
||||
|
||||
# 冷却期检查
|
||||
if self.cooldown_until and dt < self.cooldown_until:
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
prev_close = price
|
||||
continue
|
||||
self.cooldown_until = None
|
||||
|
||||
# === 有持仓:检查平仓 ===
|
||||
if self.position != 0:
|
||||
p = self.cur_pnl_pct(price)
|
||||
hs = self.hold_seconds(dt)
|
||||
|
||||
# 硬止损(不受时间限制)
|
||||
if -p >= self.hard_sl_pct:
|
||||
self._close(price, dt, f"hard_SL ({p*100:+.3f}%)")
|
||||
self.cooldown_until = dt + datetime.timedelta(seconds=120)
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
prev_close = price
|
||||
continue
|
||||
|
||||
# 满足最低持仓后
|
||||
if self.can_close(dt):
|
||||
# 止盈
|
||||
if p >= self.tp_pct:
|
||||
self._close(price, dt, f"TP ({p*100:+.3f}%)")
|
||||
prev_close = price
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
# 止损
|
||||
if -p >= self.sl_pct:
|
||||
self._close(price, dt, f"SL ({p*100:+.3f}%)")
|
||||
self.cooldown_until = dt + datetime.timedelta(seconds=120)
|
||||
prev_close = price
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
# 超时
|
||||
if hs >= self.max_hold_sec:
|
||||
self._close(price, dt, f"timeout ({hs:.0f}s)")
|
||||
prev_close = price
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
|
||||
# === 无持仓:检查开仓 ===
|
||||
if self.position == 0 and prev_close is not None:
|
||||
grid_below = self.get_grid_level(prev_close, 'below')
|
||||
grid_above = self.get_grid_level(prev_close, 'above')
|
||||
|
||||
# 上涨趋势:价格回落到下方网格线 → 做多
|
||||
if price > ema_val and low <= grid_below and prev_close > grid_below:
|
||||
self._open('long', price, dt)
|
||||
# 下跌趋势:价格反弹到上方网格线 → 做空
|
||||
elif price < ema_val and high >= grid_above and prev_close < grid_above:
|
||||
self._open('short', price, dt)
|
||||
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
prev_close = price
|
||||
|
||||
if i > 0 and i % (len(data) // 10) == 0:
|
||||
log.info(f" [{self.name}] {i/len(data)*100:.0f}% | bal={self.balance:.2f} | trades={len(self.trades)}")
|
||||
|
||||
# 强制平仓
|
||||
if self.position != 0:
|
||||
self._close(data[-1]['close'], data[-1]['datetime'], "backtest_end")
|
||||
|
||||
log.ok(f"[{self.name}] Done in {time.time()-t0:.1f}s | {len(self.trades)} trades")
|
||||
return self.trades
|
||||
|
||||
|
||||
# ========================= 策略B: EMA趋势跟随 =========================
|
||||
class EMATrendStrategy(BaseBacktest):
|
||||
"""
|
||||
EMA 趋势跟随
|
||||
|
||||
- 快线 EMA(8),慢线 EMA(21)
|
||||
- 大级别过滤 EMA(120)
|
||||
- 金叉且价格在大EMA上方 → 做多
|
||||
- 死叉且价格在大EMA下方 → 做空
|
||||
- 反向交叉时反手(满足持仓时间后)
|
||||
- 加入 ATR 波动率过滤,低波动时不交易
|
||||
"""
|
||||
|
||||
def __init__(self, fast_period=8, slow_period=21, big_period=120,
|
||||
atr_period=14, atr_min_pct=0.0003, **kwargs):
|
||||
super().__init__(name="EMA-Trend", **kwargs)
|
||||
self.ema_fast = EMA(fast_period)
|
||||
self.ema_slow = EMA(slow_period)
|
||||
self.ema_big = EMA(big_period)
|
||||
self.atr_period = atr_period
|
||||
self.atr_min_pct = atr_min_pct # 最低波动率过滤
|
||||
self.highs = []
|
||||
self.lows = []
|
||||
self.closes = []
|
||||
self.prev_fast = None
|
||||
self.prev_slow = None
|
||||
self.pending_signal = None # 等待持仓时间满足后执行的信号
|
||||
|
||||
def calc_atr(self):
|
||||
if len(self.highs) < self.atr_period + 1:
|
||||
return None
|
||||
trs = []
|
||||
for i in range(-self.atr_period, 0):
|
||||
h = self.highs[i]
|
||||
l = self.lows[i]
|
||||
pc = self.closes[i - 1]
|
||||
tr = max(h - l, abs(h - pc), abs(l - pc))
|
||||
trs.append(tr)
|
||||
return sum(trs) / len(trs)
|
||||
|
||||
def run(self, data):
|
||||
log.info(f"[{self.name}] Starting... fast=EMA8 slow=EMA21 big=EMA120")
|
||||
t0 = time.time()
|
||||
stop_loss_pct = 0.004 # 0.4% 止损
|
||||
hard_sl_pct = 0.006 # 0.6% 硬止损
|
||||
|
||||
for i, bar in enumerate(data):
|
||||
price = bar['close']
|
||||
dt = bar['datetime']
|
||||
|
||||
self.highs.append(bar['high'])
|
||||
self.lows.append(bar['low'])
|
||||
self.closes.append(price)
|
||||
|
||||
fast = self.ema_fast.update(price)
|
||||
slow = self.ema_slow.update(price)
|
||||
big = self.ema_big.update(price)
|
||||
|
||||
# ATR 波动率过滤
|
||||
atr = self.calc_atr()
|
||||
if atr is not None and price > 0:
|
||||
atr_pct = atr / price
|
||||
else:
|
||||
atr_pct = 0
|
||||
|
||||
# 检测交叉
|
||||
cross_up = (self.prev_fast is not None and
|
||||
self.prev_fast <= self.prev_slow and fast > slow)
|
||||
cross_down = (self.prev_fast is not None and
|
||||
self.prev_fast >= self.prev_slow and fast < slow)
|
||||
|
||||
self.prev_fast = fast
|
||||
self.prev_slow = slow
|
||||
|
||||
# === 有持仓 ===
|
||||
if self.position != 0:
|
||||
p = self.cur_pnl_pct(price)
|
||||
|
||||
# 硬止损
|
||||
if -p >= hard_sl_pct:
|
||||
self._close(price, dt, f"hard_SL ({p*100:+.3f}%)")
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
|
||||
if self.can_close(dt):
|
||||
# 止损
|
||||
if -p >= stop_loss_pct:
|
||||
self._close(price, dt, f"SL ({p*100:+.3f}%)")
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
|
||||
# 超时
|
||||
hs = self.hold_seconds(dt)
|
||||
if hs >= self.max_hold_sec:
|
||||
self._close(price, dt, f"timeout ({hs:.0f}s)")
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
|
||||
# 反手信号:持多遇到死叉 → 平多
|
||||
if self.position == 1 and cross_down:
|
||||
self._close(price, dt, "cross_reverse")
|
||||
if price < big and atr_pct >= self.atr_min_pct:
|
||||
self._open('short', price, dt)
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
|
||||
# 反手信号:持空遇到金叉 → 平空
|
||||
if self.position == -1 and cross_up:
|
||||
self._close(price, dt, "cross_reverse")
|
||||
if price > big and atr_pct >= self.atr_min_pct:
|
||||
self._open('long', price, dt)
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
continue
|
||||
|
||||
else:
|
||||
# 未满最低持仓时间,记录待处理信号
|
||||
if self.position == 1 and cross_down:
|
||||
self.pending_signal = 'close_long'
|
||||
elif self.position == -1 and cross_up:
|
||||
self.pending_signal = 'close_short'
|
||||
|
||||
# 处理待处理信号(持仓时间刚好满足)
|
||||
if self.pending_signal and self.can_close(dt):
|
||||
if self.pending_signal == 'close_long' and self.position == 1:
|
||||
self._close(price, dt, "delayed_cross")
|
||||
if fast < slow and price < big and atr_pct >= self.atr_min_pct:
|
||||
self._open('short', price, dt)
|
||||
elif self.pending_signal == 'close_short' and self.position == -1:
|
||||
self._close(price, dt, "delayed_cross")
|
||||
if fast > slow and price > big and atr_pct >= self.atr_min_pct:
|
||||
self._open('long', price, dt)
|
||||
self.pending_signal = None
|
||||
|
||||
# === 无持仓:检查开仓 ===
|
||||
if self.position == 0 and atr_pct >= self.atr_min_pct:
|
||||
if cross_up and price > big:
|
||||
self._open('long', price, dt)
|
||||
elif cross_down and price < big:
|
||||
self._open('short', price, dt)
|
||||
|
||||
self.track_equity(dt, price, bar_idx=i)
|
||||
|
||||
if i > 0 and i % (len(data) // 10) == 0:
|
||||
log.info(f" [{self.name}] {i/len(data)*100:.0f}% | bal={self.balance:.2f} | trades={len(self.trades)}")
|
||||
|
||||
if self.position != 0:
|
||||
self._close(data[-1]['close'], data[-1]['datetime'], "backtest_end")
|
||||
|
||||
log.ok(f"[{self.name}] Done in {time.time()-t0:.1f}s | {len(self.trades)} trades")
|
||||
return self.trades
|
||||
|
||||
|
||||
# ========================= 报告生成 =========================
|
||||
def print_report(strategy: BaseBacktest):
|
||||
trades = strategy.trades
|
||||
if not trades:
|
||||
print(f"\n[{strategy.name}] No trades.")
|
||||
return
|
||||
|
||||
n = len(trades)
|
||||
wins = [t for t in trades if t.pnl > 0]
|
||||
losses = [t for t in trades if t.pnl <= 0]
|
||||
wr = len(wins) / n * 100
|
||||
|
||||
total_pnl = sum(t.pnl for t in trades)
|
||||
total_fee = sum(t.fee for t in trades)
|
||||
total_rebate = sum(t.rebate for t in trades)
|
||||
net = strategy.balance - strategy.initial_balance
|
||||
total_vol = sum(t.size for t in trades) * 2
|
||||
|
||||
avg_pnl = total_pnl / n
|
||||
avg_win = statistics.mean([t.pnl for t in wins]) if wins else 0
|
||||
avg_loss = statistics.mean([t.pnl for t in losses]) if losses else 0
|
||||
avg_hold = statistics.mean([t.hold_seconds for t in trades])
|
||||
|
||||
pf_num = sum(t.pnl for t in wins)
|
||||
pf_den = abs(sum(t.pnl for t in losses))
|
||||
pf = pf_num / pf_den if pf_den > 0 else float('inf')
|
||||
|
||||
# 连续亏损
|
||||
max_streak = 0
|
||||
cur = 0
|
||||
for t in trades:
|
||||
if t.pnl <= 0:
|
||||
cur += 1
|
||||
max_streak = max(max_streak, cur)
|
||||
else:
|
||||
cur = 0
|
||||
|
||||
long_t = [t for t in trades if t.direction == 'long']
|
||||
short_t = [t for t in trades if t.direction == 'short']
|
||||
long_wr = len([t for t in long_t if t.pnl > 0]) / len(long_t) * 100 if long_t else 0
|
||||
short_wr = len([t for t in short_t if t.pnl > 0]) / len(short_t) * 100 if short_t else 0
|
||||
|
||||
# 平仓原因
|
||||
reasons = {}
|
||||
for t in trades:
|
||||
r = t.close_reason.split(' (')[0]
|
||||
reasons[r] = reasons.get(r, 0) + 1
|
||||
|
||||
under_3m = len([t for t in trades if t.hold_seconds < 180])
|
||||
|
||||
w = 65
|
||||
print(f"\n{'='*w}")
|
||||
print(f" [{strategy.name}] Backtest Report")
|
||||
print(f"{'='*w}")
|
||||
|
||||
print(f"\n--- Account ---")
|
||||
print(f" Initial: {strategy.initial_balance:>12.2f} USDT")
|
||||
print(f" Final: {strategy.balance:>12.2f} USDT")
|
||||
print(f" Net P&L: {net:>+12.2f} USDT ({net/strategy.initial_balance*100:+.2f}%)")
|
||||
print(f" Max Drawdown: {strategy.max_dd_pct*100:>11.2f}%")
|
||||
|
||||
print(f"\n--- Trades ---")
|
||||
print(f" Total: {n:>8}")
|
||||
print(f" Wins: {len(wins):>8} ({wr:.1f}%)")
|
||||
print(f" Losses: {len(losses):>8} ({100-wr:.1f}%)")
|
||||
print(f" Long: {len(long_t):>8} (WR {long_wr:.1f}%)")
|
||||
print(f" Short: {len(short_t):>8} (WR {short_wr:.1f}%)")
|
||||
print(f" Profit Factor: {pf:>8.2f}")
|
||||
print(f" Max Loss Streak:{max_streak:>8}")
|
||||
|
||||
print(f"\n--- P&L ---")
|
||||
print(f" Direction P&L: {total_pnl:>+12.4f} USDT")
|
||||
print(f" Avg per trade: {avg_pnl:>+12.4f} USDT")
|
||||
print(f" Avg win: {avg_win:>+12.4f} USDT")
|
||||
print(f" Avg loss: {avg_loss:>+12.4f} USDT")
|
||||
print(f" Best trade: {max(t.pnl for t in trades):>+12.4f} USDT")
|
||||
print(f" Worst trade: {min(t.pnl for t in trades):>+12.4f} USDT")
|
||||
|
||||
print(f"\n--- Fees & Rebate ---")
|
||||
print(f" Volume: {total_vol:>12.2f} USDT")
|
||||
print(f" Total Fees: {total_fee:>12.4f} USDT")
|
||||
print(f" Rebate (90%): {total_rebate:>+12.4f} USDT")
|
||||
print(f" Net Fee Cost: {total_fee - total_rebate:>12.4f} USDT")
|
||||
|
||||
print(f"\n--- Hold Time ---")
|
||||
print(f" Average: {avg_hold:>8.0f}s ({avg_hold/60:.1f}min)")
|
||||
print(f" Shortest: {min(t.hold_seconds for t in trades):>8.0f}s")
|
||||
print(f" Longest: {max(t.hold_seconds for t in trades):>8.0f}s")
|
||||
print(f" Under 3min: {under_3m:>8} (hard SL only)")
|
||||
|
||||
print(f"\n--- Close Reasons ---")
|
||||
for r, c in sorted(reasons.items(), key=lambda x: -x[1]):
|
||||
print(f" {r:<22} {c:>6} ({c/n*100:.1f}%)")
|
||||
|
||||
# 月度统计
|
||||
print(f"\n--- Monthly ---")
|
||||
print(f" {'Month':<10} {'Trades':>6} {'Dir PnL':>10} {'Rebate':>10} {'Net':>10} {'WR':>6}")
|
||||
print(f" {'-'*54}")
|
||||
|
||||
monthly = {}
|
||||
for t in trades:
|
||||
k = t.close_time.strftime('%Y-%m')
|
||||
if k not in monthly:
|
||||
monthly[k] = {'n': 0, 'pnl': 0, 'rebate': 0, 'fee': 0, 'wins': 0}
|
||||
monthly[k]['n'] += 1
|
||||
monthly[k]['pnl'] += t.pnl
|
||||
monthly[k]['rebate'] += t.rebate
|
||||
monthly[k]['fee'] += t.fee
|
||||
if t.pnl > 0:
|
||||
monthly[k]['wins'] += 1
|
||||
|
||||
for month in sorted(monthly.keys()):
|
||||
m = monthly[month]
|
||||
net_m = m['pnl'] - m['fee'] + m['rebate'] # 正确的月度净收益
|
||||
wr_m = m['wins'] / m['n'] * 100 if m['n'] > 0 else 0
|
||||
print(f" {month:<10} {m['n']:>6} {m['pnl']:>+10.2f} {m['rebate']:>10.2f} {net_m:>+10.2f} {wr_m:>5.1f}%")
|
||||
|
||||
print(f"{'='*w}")
|
||||
|
||||
# 保存CSV
|
||||
csv_path = Path(__file__).parent.parent / f'{strategy.name}_trades.csv'
|
||||
with open(csv_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("open_time,close_time,dir,open_px,close_px,size,pnl,pnl_pct,fee,rebate,hold_sec,reason\n")
|
||||
for t in trades:
|
||||
f.write(f"{t.open_time},{t.close_time},{t.direction},"
|
||||
f"{t.open_price:.2f},{t.close_price:.2f},{t.size:.2f},"
|
||||
f"{t.pnl:.4f},{t.pnl_pct*100:.4f}%,{t.fee:.4f},{t.rebate:.4f},"
|
||||
f"{t.hold_seconds:.0f},{t.close_reason}\n")
|
||||
log.ok(f"Trades saved: {csv_path}")
|
||||
|
||||
|
||||
# ========================= 主函数 =========================
|
||||
def main():
|
||||
data = load_1m_klines('2025-01-01', '2025-12-31')
|
||||
if not data:
|
||||
log.err("No data!")
|
||||
return
|
||||
|
||||
common = dict(
|
||||
initial_balance=1000.0,
|
||||
leverage=50,
|
||||
risk_pct=0.005,
|
||||
taker_fee=0.0006,
|
||||
rebate_rate=0.90,
|
||||
min_hold_sec=200,
|
||||
max_hold_sec=1800,
|
||||
)
|
||||
|
||||
# === 策略A: 网格交易 ===
|
||||
grid = GridStrategy(
|
||||
grid_pct=0.0020, # 0.20% 网格间距
|
||||
tp_grids=1, # 止盈1格 (0.20%)
|
||||
sl_grids=3, # 止损3格 (0.60%)
|
||||
trend_ema_period=120, # 2小时EMA趋势过滤
|
||||
**common,
|
||||
)
|
||||
grid.run(data)
|
||||
print_report(grid)
|
||||
|
||||
# === 策略B: EMA趋势跟随 ===
|
||||
ema = EMATrendStrategy(
|
||||
fast_period=8,
|
||||
slow_period=21,
|
||||
big_period=120,
|
||||
atr_period=14,
|
||||
atr_min_pct=0.0003, # 最低波动率过滤
|
||||
**common,
|
||||
)
|
||||
ema.run(data)
|
||||
print_report(ema)
|
||||
|
||||
# === 对比摘要 ===
|
||||
print(f"\n{'='*65}")
|
||||
print(f" COMPARISON SUMMARY")
|
||||
print(f"{'='*65}")
|
||||
print(f" {'Metric':<25} {'Grid+Trend':>18} {'EMA-Trend':>18}")
|
||||
print(f" {'-'*61}")
|
||||
|
||||
for s in [grid, ema]:
|
||||
s._net = s.balance - s.initial_balance
|
||||
s._trades_n = len(s.trades)
|
||||
s._wr = len([t for t in s.trades if t.pnl > 0]) / len(s.trades) * 100 if s.trades else 0
|
||||
s._dir_pnl = sum(t.pnl for t in s.trades)
|
||||
s._rebate = sum(t.rebate for t in s.trades)
|
||||
s._fee = sum(t.fee for t in s.trades)
|
||||
s._vol = sum(t.size for t in s.trades) * 2
|
||||
|
||||
rows = [
|
||||
("Net P&L (USDT)", f"{grid._net:+.2f}", f"{ema._net:+.2f}"),
|
||||
("Net P&L (%)", f"{grid._net/grid.initial_balance*100:+.2f}%", f"{ema._net/ema.initial_balance*100:+.2f}%"),
|
||||
("Max Drawdown", f"{grid.max_dd_pct*100:.2f}%", f"{ema.max_dd_pct*100:.2f}%"),
|
||||
("Total Trades", f"{grid._trades_n}", f"{ema._trades_n}"),
|
||||
("Win Rate", f"{grid._wr:.1f}%", f"{ema._wr:.1f}%"),
|
||||
("Direction P&L", f"{grid._dir_pnl:+.2f}", f"{ema._dir_pnl:+.2f}"),
|
||||
("Total Volume", f"{grid._vol:,.0f}", f"{ema._vol:,.0f}"),
|
||||
("Total Fees", f"{grid._fee:.2f}", f"{ema._fee:.2f}"),
|
||||
("Rebate Income", f"{grid._rebate:+.2f}", f"{ema._rebate:+.2f}"),
|
||||
]
|
||||
for label, v1, v2 in rows:
|
||||
print(f" {label:<25} {v1:>18} {v2:>18}")
|
||||
print(f"{'='*65}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
293
交易/bitmart-最终回测.py
Normal file
293
交易/bitmart-最终回测.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""
|
||||
最终回测 - Top 3 盈利参数 + 不同仓位大小
|
||||
|
||||
Top 3 profitable combos from scan:
|
||||
1. EMA(8/21/120) ATR>0.30% SL=0.4% MH=1800s → +2.88%
|
||||
2. EMA(30/80/200) ATR>0.20% SL=0.8% MH=3600s → +2.53%
|
||||
3. EMA(8/21/120) ATR>0.20% SL=0.8% MH=1800s → +1.94%
|
||||
|
||||
每组参数测试 risk_pct = [0.005, 0.01, 0.02, 0.03, 0.05]
|
||||
输出详细月度报告和交易明细
|
||||
"""
|
||||
import sys, time, datetime, sqlite3, statistics
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
@dataclass
|
||||
class Trade:
|
||||
open_time: datetime.datetime
|
||||
close_time: datetime.datetime
|
||||
direction: str
|
||||
open_price: float
|
||||
close_price: float
|
||||
size: float
|
||||
pnl: float
|
||||
pnl_pct: float
|
||||
fee: float
|
||||
rebate: float
|
||||
hold_seconds: float
|
||||
close_reason: str
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
def run_detailed(data, fp, sp, bp, atr_min, sl_pct, mh, risk_pct=0.005,
|
||||
leverage=50, taker_fee=0.0006, rebate_rate=0.90, min_hold=200):
|
||||
bal = 1000.0; pos = 0; op_ = 0.0; ot_ = None; ps_ = 0.0; pend = None
|
||||
ef = EMA(fp); es = EMA(sp); eb = EMA(bp)
|
||||
H = []; L = []; C = []
|
||||
pf = None; pslow = None
|
||||
trades: List[Trade] = []
|
||||
hsl = sl_pct * 1.5
|
||||
eq_curve = []; peak = 1000.0; max_dd = 0.0
|
||||
|
||||
def _close(price, dt, reason):
|
||||
nonlocal bal, pos, op_, ot_, ps_, pend
|
||||
pp = (price - op_) / op_ if pos == 1 else (op_ - price) / op_
|
||||
pnl = ps_ * pp
|
||||
cv = ps_ * (1 + pp); cf = cv * taker_fee; of = ps_ * taker_fee
|
||||
tf = of + cf; rb = tf * rebate_rate
|
||||
bal += pnl - cf + rb
|
||||
hs = (dt - ot_).total_seconds()
|
||||
trades.append(Trade(ot_, dt, 'long' if pos == 1 else 'short',
|
||||
op_, price, ps_, pnl, pp, tf, rb, hs, reason))
|
||||
pos = 0; op_ = 0; ot_ = None; ps_ = 0; pend = None
|
||||
|
||||
def _open(d, price, dt):
|
||||
nonlocal bal, pos, op_, ot_, ps_
|
||||
ns = bal * risk_pct * leverage
|
||||
if ns < 1: return
|
||||
bal -= ns * taker_fee
|
||||
pos = 1 if d == 'L' else -1; op_ = price; ot_ = dt; ps_ = ns
|
||||
|
||||
for i, (dt, o, h, l, c) in enumerate(data):
|
||||
H.append(h); L.append(l); C.append(c)
|
||||
fast = ef.update(c); slow = es.update(c); big = eb.update(c)
|
||||
atr_pct = 0.0
|
||||
AP = 14
|
||||
if len(H) > AP + 1:
|
||||
s = 0.0
|
||||
for j in range(-AP, 0):
|
||||
tr = H[j] - L[j]; d1 = abs(H[j] - C[j-1]); d2 = abs(L[j] - C[j-1])
|
||||
if d1 > tr: tr = d1
|
||||
if d2 > tr: tr = d2
|
||||
s += tr
|
||||
atr_pct = s / (AP * c) if c > 0 else 0
|
||||
|
||||
cu = pf is not None and pf <= pslow and fast > slow
|
||||
cd = pf is not None and pf >= pslow and fast < slow
|
||||
pf = fast; pslow = slow
|
||||
|
||||
if pos != 0 and ot_:
|
||||
pp = (c - op_) / op_ if pos == 1 else (op_ - c) / op_
|
||||
hsec = (dt - ot_).total_seconds()
|
||||
if -pp >= hsl:
|
||||
_close(c, dt, f"hard_SL({pp*100:+.2f}%)")
|
||||
continue
|
||||
if hsec >= min_hold:
|
||||
dc = False; reason = ""
|
||||
if -pp >= sl_pct: dc = True; reason = f"SL({pp*100:+.2f}%)"
|
||||
elif hsec >= mh: dc = True; reason = f"timeout({hsec:.0f}s)"
|
||||
elif pos == 1 and cd: dc = True; reason = "cross_rev"
|
||||
elif pos == -1 and cu: dc = True; reason = "cross_rev"
|
||||
elif pend == 'cl' and pos == 1: dc = True; reason = "delayed_cross"
|
||||
elif pend == 'cs' and pos == -1: dc = True; reason = "delayed_cross"
|
||||
if dc:
|
||||
_close(c, dt, reason)
|
||||
if atr_pct >= atr_min:
|
||||
if (cd or fast < slow) and c < big: _open('S', c, dt)
|
||||
elif (cu or fast > slow) and c > big: _open('L', c, dt)
|
||||
continue
|
||||
else:
|
||||
if pos == 1 and cd: pend = 'cl'
|
||||
elif pos == -1 and cu: pend = 'cs'
|
||||
|
||||
if pos == 0 and atr_pct >= atr_min:
|
||||
if cu and c > big: _open('L', c, dt)
|
||||
elif cd and c < big: _open('S', c, dt)
|
||||
|
||||
# equity tracking every hour
|
||||
if i % 60 == 0:
|
||||
eq = bal
|
||||
if pos != 0 and op_ > 0:
|
||||
pp = (c - op_) / op_ if pos == 1 else (op_ - c) / op_
|
||||
eq += ps_ * pp
|
||||
eq_curve.append((dt, eq))
|
||||
if eq > peak: peak = eq
|
||||
dd = (peak - eq) / peak if peak > 0 else 0
|
||||
if dd > max_dd: max_dd = dd
|
||||
|
||||
if pos != 0: _close(data[-1][4], data[-1][0], "backtest_end")
|
||||
|
||||
return trades, bal, max_dd, eq_curve
|
||||
|
||||
|
||||
def print_report(name, trades, balance, max_dd, init=1000.0):
|
||||
if not trades:
|
||||
print(f"\n[{name}] No trades.\n", flush=True)
|
||||
return
|
||||
|
||||
n = len(trades)
|
||||
wins = [t for t in trades if t.pnl > 0]
|
||||
losses = [t for t in trades if t.pnl <= 0]
|
||||
wr = len(wins) / n * 100
|
||||
net = balance - init
|
||||
tot_pnl = sum(t.pnl for t in trades)
|
||||
tot_fee = sum(t.fee for t in trades)
|
||||
tot_reb = sum(t.rebate for t in trades)
|
||||
avg_hold = statistics.mean([t.hold_seconds for t in trades])
|
||||
vol = sum(t.size for t in trades) * 2
|
||||
|
||||
pf_n = sum(t.pnl for t in wins) if wins else 0
|
||||
pf_d = abs(sum(t.pnl for t in losses)) if losses else 0
|
||||
pf = pf_n / pf_d if pf_d > 0 else float('inf')
|
||||
|
||||
long_t = [t for t in trades if t.direction == 'long']
|
||||
short_t = [t for t in trades if t.direction == 'short']
|
||||
long_wr = len([t for t in long_t if t.pnl > 0]) / len(long_t) * 100 if long_t else 0
|
||||
short_wr = len([t for t in short_t if t.pnl > 0]) / len(short_t) * 100 if short_t else 0
|
||||
|
||||
reasons = {}
|
||||
for t in trades:
|
||||
r = t.close_reason.split('(')[0]
|
||||
reasons[r] = reasons.get(r, 0) + 1
|
||||
|
||||
print(f"\n{'='*70}", flush=True)
|
||||
print(f" [{name}]", flush=True)
|
||||
print(f"{'='*70}", flush=True)
|
||||
|
||||
print(f" Initial: {init:>10.2f} USDT", flush=True)
|
||||
print(f" Final: {balance:>10.2f} USDT", flush=True)
|
||||
print(f" Net P&L: {net:>+10.2f} USDT ({net/init*100:+.2f}%)", flush=True)
|
||||
print(f" Max Drawdown: {max_dd*100:>9.2f}%", flush=True)
|
||||
|
||||
print(f"\n Trades: {n:>6} (Long {len(long_t)} WR={long_wr:.0f}% | Short {len(short_t)} WR={short_wr:.0f}%)", flush=True)
|
||||
print(f" Win Rate: {wr:>5.1f}%", flush=True)
|
||||
print(f" Profit Factor:{pf:>6.2f}", flush=True)
|
||||
print(f" Avg Hold: {avg_hold:>5.0f}s ({avg_hold/60:.1f}min)", flush=True)
|
||||
|
||||
print(f"\n Dir P&L: {tot_pnl:>+10.2f}", flush=True)
|
||||
print(f" Total Fee: {tot_fee:>10.2f}", flush=True)
|
||||
print(f" Rebate(90%): {tot_reb:>+10.2f}", flush=True)
|
||||
print(f" Net Fee: {tot_fee - tot_reb:>10.2f}", flush=True)
|
||||
print(f" Volume: {vol:>10.0f}", flush=True)
|
||||
|
||||
if wins:
|
||||
print(f"\n Avg Win: {statistics.mean([t.pnl for t in wins]):>+10.4f}", flush=True)
|
||||
if losses:
|
||||
print(f" Avg Loss: {statistics.mean([t.pnl for t in losses]):>+10.4f}", flush=True)
|
||||
print(f" Best: {max(t.pnl for t in trades):>+10.4f}", flush=True)
|
||||
print(f" Worst: {min(t.pnl for t in trades):>+10.4f}", flush=True)
|
||||
|
||||
print(f"\n Close Reasons:", flush=True)
|
||||
for r, c in sorted(reasons.items(), key=lambda x: -x[1]):
|
||||
print(f" {r:<20} {c:>5} ({c/n*100:.1f}%)", flush=True)
|
||||
|
||||
# Monthly
|
||||
print(f"\n {'Month':<8} {'Trd':>5} {'DirPnL':>9} {'Rebate':>9} {'Net':>9} {'WR':>6}", flush=True)
|
||||
print(f" {'-'*50}", flush=True)
|
||||
monthly = {}
|
||||
for t in trades:
|
||||
k = t.close_time.strftime('%Y-%m')
|
||||
if k not in monthly:
|
||||
monthly[k] = {'n': 0, 'pnl': 0, 'reb': 0, 'fee': 0, 'w': 0}
|
||||
monthly[k]['n'] += 1
|
||||
monthly[k]['pnl'] += t.pnl
|
||||
monthly[k]['reb'] += t.rebate
|
||||
monthly[k]['fee'] += t.fee
|
||||
if t.pnl > 0: monthly[k]['w'] += 1
|
||||
|
||||
for m in sorted(monthly.keys()):
|
||||
d = monthly[m]
|
||||
net_m = d['pnl'] - d['fee'] + d['reb']
|
||||
wr_m = d['w'] / d['n'] * 100 if d['n'] > 0 else 0
|
||||
print(f" {m:<8} {d['n']:>5} {d['pnl']:>+9.2f} {d['reb']:>9.2f} {net_m:>+9.2f} {wr_m:>5.1f}%", flush=True)
|
||||
|
||||
print(f"{'='*70}", flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
print("Loading data...", flush=True)
|
||||
data = load()
|
||||
print(f"{len(data)} bars\n", flush=True)
|
||||
|
||||
# Top 3 parameter combos
|
||||
configs = [
|
||||
{"name": "A", "fp": 8, "sp": 21, "bp": 120, "atr": 0.003, "sl": 0.004, "mh": 1800},
|
||||
{"name": "B", "fp": 30, "sp": 80, "bp": 200, "atr": 0.002, "sl": 0.008, "mh": 3600},
|
||||
{"name": "C", "fp": 8, "sp": 21, "bp": 120, "atr": 0.002, "sl": 0.008, "mh": 1800},
|
||||
]
|
||||
|
||||
risk_levels = [0.005, 0.01, 0.02, 0.03, 0.05]
|
||||
|
||||
print(f"{'='*100}", flush=True)
|
||||
print(f" RISK LEVEL COMPARISON", flush=True)
|
||||
print(f"{'='*100}", flush=True)
|
||||
print(f" {'Config':<50} {'Risk%':>6} {'Net%':>7} {'Net$':>9} {'Trades':>7} {'MaxDD':>7}", flush=True)
|
||||
print(f" {'-'*96}", flush=True)
|
||||
|
||||
best_result = None
|
||||
best_net = -9999
|
||||
|
||||
for cfg in configs:
|
||||
for rp in risk_levels:
|
||||
label = f"EMA({cfg['fp']}/{cfg['sp']}/{cfg['bp']}) ATR>{cfg['atr']*100:.1f}% SL={cfg['sl']*100:.1f}% MH={cfg['mh']}"
|
||||
trades, bal, mdd, eq = run_detailed(
|
||||
data, cfg['fp'], cfg['sp'], cfg['bp'],
|
||||
cfg['atr'], cfg['sl'], cfg['mh'], risk_pct=rp
|
||||
)
|
||||
net = bal - 1000.0
|
||||
mk = " <<<" if net > 0 else ""
|
||||
print(f" {label:<50} {rp*100:>5.1f}% {net/10:>+6.2f}% {net:>+8.2f} {len(trades):>7} {mdd*100:>6.2f}%{mk}", flush=True)
|
||||
|
||||
if net > best_net:
|
||||
best_net = net
|
||||
best_result = (cfg, rp, trades, bal, mdd, eq)
|
||||
|
||||
print(f"{'='*100}\n", flush=True)
|
||||
|
||||
# Detailed report for best
|
||||
if best_result:
|
||||
cfg, rp, trades, bal, mdd, eq = best_result
|
||||
label = f"EMA({cfg['fp']}/{cfg['sp']}/{cfg['bp']}) ATR>{cfg['atr']*100:.1f}% SL={cfg['sl']*100:.1f}% MH={cfg['mh']} Risk={rp*100:.1f}%"
|
||||
print_report(f"BEST: {label}", trades, bal, mdd)
|
||||
|
||||
# Save trades CSV
|
||||
csv = Path(__file__).parent.parent / 'best_trades.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("open_time,close_time,dir,open_px,close_px,size,pnl,pnl_pct,fee,rebate,hold_sec,reason\n")
|
||||
for t in trades:
|
||||
f.write(f"{t.open_time},{t.close_time},{t.direction},"
|
||||
f"{t.open_price:.2f},{t.close_price:.2f},{t.size:.2f},"
|
||||
f"{t.pnl:.4f},{t.pnl_pct*100:.4f}%,{t.fee:.4f},{t.rebate:.4f},"
|
||||
f"{t.hold_seconds:.0f},{t.close_reason}\n")
|
||||
print(f"\nBest trades saved: {csv}", flush=True)
|
||||
|
||||
# Save equity curve
|
||||
eq_csv = Path(__file__).parent.parent / 'best_equity.csv'
|
||||
with open(eq_csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("time,equity\n")
|
||||
for dt, e in eq:
|
||||
f.write(f"{dt},{e:.2f}\n")
|
||||
print(f"Equity curve saved: {eq_csv}", flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
315
交易/bitmart-盈利组合回测.py
Normal file
315
交易/bitmart-盈利组合回测.py
Normal file
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
盈利信号组合策略 — 只保留回测验证盈利的3种信号
|
||||
|
||||
信号1: EMA交叉 (83笔, +1773) — 趋势变化捕捉
|
||||
信号2: 吞没形态 (1909笔, +2471) — K线反转形态
|
||||
信号3: BB反弹 (438笔, +171) — 均值回归
|
||||
|
||||
去掉: 三分之一(-17290)、PinBar(-1936) — 亏损信号
|
||||
|
||||
条件: 同一时间只持1个仓, 100U保证金, 100x杠杆, 90%返佣
|
||||
"""
|
||||
import sys, time, datetime, sqlite3
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
def calc_bb(closes, period=20, nstd=2.0):
|
||||
if len(closes) < period: return None, None, None
|
||||
rec = closes[-period:]
|
||||
mid = sum(rec) / period
|
||||
var = sum((x - mid)**2 for x in rec) / period
|
||||
std = var ** 0.5
|
||||
return mid + nstd * std, mid, mid - nstd * std
|
||||
|
||||
def calc_rsi(closes, period=14):
|
||||
if len(closes) < period + 1: return None
|
||||
g = 0; l = 0
|
||||
for i in range(-period, 0):
|
||||
d = closes[i] - closes[i-1]
|
||||
if d > 0: g += d
|
||||
else: l -= d
|
||||
if l == 0: return 100.0
|
||||
return 100 - 100 / (1 + (g/period)/(l/period))
|
||||
|
||||
def run_combo(data, notional, engulf_body_min, engulf_ratio, engulf_sl, engulf_tp,
|
||||
bb_rsi_long, bb_rsi_short, bb_sl, bb_tp, bb_atr_min,
|
||||
ema_atr_min, ema_sl):
|
||||
N = len(data)
|
||||
FEE = notional * 0.0006 * 2
|
||||
REB = FEE * 0.9
|
||||
NFEE = FEE - REB
|
||||
MIN_HOLD = 200; MAX_HOLD = 1800
|
||||
|
||||
ema_f = EMA(8); ema_s = EMA(21); ema_b = EMA(120)
|
||||
pf_ = None; ps_ = None
|
||||
CB = []; HB = []; LB = []
|
||||
pos = 0; op = 0.0; ot = None; st = ""; sl = 0; tp = 0
|
||||
trades = []
|
||||
|
||||
def close_(price, dt_, reason):
|
||||
nonlocal pos, op, ot, st
|
||||
pp = (price-op)/op if pos==1 else (op-price)/op
|
||||
trades.append((st, 'L' if pos==1 else 'S', op, price, notional*pp,
|
||||
(dt_-ot).total_seconds(), reason, ot, dt_))
|
||||
pos=0; op=0; ot=None; st=""
|
||||
|
||||
for i in range(N):
|
||||
dt, o_, h_, l_, c_ = data[i]
|
||||
p = c_
|
||||
CB.append(p); HB.append(h_); LB.append(l_)
|
||||
if len(CB) > 300: CB=CB[-300:]; HB=HB[-300:]; LB=LB[-300:]
|
||||
|
||||
fast = ema_f.update(p); slow = ema_s.update(p); big = ema_b.update(p)
|
||||
atr = 0.0
|
||||
if len(HB) > 15:
|
||||
s=0
|
||||
for j in range(-14, 0):
|
||||
tr=HB[j]-LB[j]; d1=abs(HB[j]-CB[j-1]); d2=abs(LB[j]-CB[j-1])
|
||||
if d1>tr: tr=d1
|
||||
if d2>tr: tr=d2
|
||||
s+=tr
|
||||
atr = s/(14*p) if p>0 else 0
|
||||
|
||||
cu = pf_ is not None and pf_<=ps_ and fast>slow
|
||||
cd = pf_ is not None and pf_>=ps_ and fast<slow
|
||||
pf_=fast; ps_=slow
|
||||
|
||||
bb_u, bb_m, bb_l = calc_bb(CB, 20, 2.0)
|
||||
rsi = calc_rsi(CB, 14)
|
||||
|
||||
# 持仓管理
|
||||
if pos!=0 and ot:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
hsl = max(sl*1.5, 0.006)
|
||||
if -pp>=hsl: close_(p, dt, "硬止损"); continue
|
||||
if hsec>=MIN_HOLD:
|
||||
if -pp>=sl: close_(p, dt, "止损"); continue
|
||||
if tp>0 and pp>=tp: close_(p, dt, "止盈"); continue
|
||||
if hsec>=MAX_HOLD: close_(p, dt, "超时"); continue
|
||||
if st=="EMA":
|
||||
if pos==1 and cd: close_(p, dt, "EMA反转"); continue
|
||||
if pos==-1 and cu: close_(p, dt, "EMA反转"); continue
|
||||
if st=="BB" and bb_m:
|
||||
if pos==1 and p>=bb_m: close_(p, dt, "BB回中轨"); continue
|
||||
if pos==-1 and p<=bb_m: close_(p, dt, "BB回中轨"); continue
|
||||
|
||||
# 开仓
|
||||
if pos==0 and i>20:
|
||||
sig=None; s_sl=0; s_tp=0; s_type=""
|
||||
|
||||
# 信号1: EMA
|
||||
if atr>=ema_atr_min:
|
||||
if cu and p>big: sig='L'; s_type="EMA"; s_sl=ema_sl; s_tp=0
|
||||
elif cd and p<big: sig='S'; s_type="EMA"; s_sl=ema_sl; s_tp=0
|
||||
|
||||
# 信号2: 吞没
|
||||
if sig is None and i>0:
|
||||
pb_o,pb_c = data[i-1][1], data[i-1][4]
|
||||
cb_o,cb_c = o_, c_
|
||||
pb_body = abs(pb_c-pb_o)
|
||||
cb_body = abs(cb_c-cb_o)
|
||||
cb_pct = cb_body/p if p>0 else 0
|
||||
|
||||
if cb_pct>=engulf_body_min and cb_body>pb_body*engulf_ratio:
|
||||
if pb_c<pb_o and cb_c>cb_o and cb_c>pb_o and cb_o<=pb_c:
|
||||
if p>big and atr>=0.001:
|
||||
sig='L'; s_type="吞没"; s_sl=engulf_sl; s_tp=engulf_tp
|
||||
elif pb_c>pb_o and cb_c<cb_o and cb_c<pb_o and cb_o>=pb_c:
|
||||
if p<big and atr>=0.001:
|
||||
sig='S'; s_type="吞没"; s_sl=engulf_sl; s_tp=engulf_tp
|
||||
|
||||
# 信号3: BB
|
||||
if sig is None and bb_u and rsi is not None:
|
||||
if p<=bb_l and rsi<bb_rsi_long and p>big and atr>=bb_atr_min:
|
||||
sig='L'; s_type="BB"; s_sl=bb_sl; s_tp=bb_tp
|
||||
elif p>=bb_u and rsi>bb_rsi_short and p<big and atr>=bb_atr_min:
|
||||
sig='S'; s_type="BB"; s_sl=bb_sl; s_tp=bb_tp
|
||||
|
||||
if sig:
|
||||
pos=1 if sig=='L' else -1; op=p; ot=dt; st=s_type; sl=s_sl; tp=s_tp
|
||||
|
||||
if pos!=0: close_(data[-1][4], data[-1][0], "结束")
|
||||
return trades
|
||||
|
||||
def analyze_print(trades, notional, label=""):
|
||||
if not trades:
|
||||
print(f" [{label}] No trades", flush=True); return 0
|
||||
n = len(trades)
|
||||
FEE = notional * 0.0006 * 2
|
||||
REB = FEE * 0.9
|
||||
NFEE = FEE - REB
|
||||
|
||||
total_pnl = sum(t[4] for t in trades)
|
||||
net = total_pnl - NFEE * n
|
||||
total_reb = REB * n
|
||||
wins = len([t for t in trades if t[4]>0])
|
||||
wr = wins/n*100
|
||||
|
||||
by_type = defaultdict(lambda: {'n':0,'pnl':0,'w':0})
|
||||
for t in trades:
|
||||
by_type[t[0]]['n']+=1; by_type[t[0]]['pnl']+=t[4]
|
||||
if t[4]>0: by_type[t[0]]['w']+=1
|
||||
|
||||
monthly = defaultdict(lambda: {'n':0,'net':0,'w':0})
|
||||
for t in trades:
|
||||
k = t[8].strftime('%Y-%m')
|
||||
monthly[k]['n']+=1
|
||||
monthly[k]['net']+=t[4]-NFEE
|
||||
if t[4]>0: monthly[k]['w']+=1
|
||||
|
||||
cum=0; peak=0; dd=0
|
||||
for t in trades:
|
||||
cum+=t[4]-NFEE
|
||||
if cum>peak: peak=cum
|
||||
if peak-cum>dd: dd=peak-cum
|
||||
|
||||
# 月度盈利月数
|
||||
profit_months = len([m for m in monthly.values() if m['net']>0])
|
||||
min_month = min(monthly.values(), key=lambda x: x['net'])
|
||||
max_month = max(monthly.values(), key=lambda x: x['net'])
|
||||
|
||||
print(f"\n{'='*75}", flush=True)
|
||||
print(f" {label}", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
print(f" 年净利: {net:>+10.2f} | 月均: {net/12:>+8.2f} | 交易: {n}笔 | 胜率: {wr:.1f}%", flush=True)
|
||||
print(f" 返佣: {total_reb:>.0f} | 最大回撤: {dd:>.0f} | 盈利月: {profit_months}/12", flush=True)
|
||||
|
||||
print(f"\n 信号拆分:", flush=True)
|
||||
for st in sorted(by_type.keys()):
|
||||
d=by_type[st]
|
||||
nt=d['pnl']-NFEE*d['n']
|
||||
wt=d['w']/d['n']*100 if d['n']>0 else 0
|
||||
print(f" {st:<8} {d['n']:>5}笔 净利{nt:>+8.0f} 胜率{wt:.0f}%", flush=True)
|
||||
|
||||
print(f"\n 月度:", flush=True)
|
||||
print(f" {'月份':<8} {'笔':>4} {'净利':>9} {'胜率':>6}", flush=True)
|
||||
print(f" {'-'*30}", flush=True)
|
||||
for m in sorted(monthly.keys()):
|
||||
d=monthly[m]
|
||||
wr_m=d['w']/d['n']*100 if d['n']>0 else 0
|
||||
mark=" <<" if d['net']<0 else ""
|
||||
print(f" {m:<8} {d['n']:>4} {d['net']:>+9.0f} {wr_m:>5.1f}%{mark}", flush=True)
|
||||
print(f" {'-'*30}", flush=True)
|
||||
print(f" {'合计':<8} {n:>4} {net:>+9.0f}", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
return net
|
||||
|
||||
def main():
|
||||
print("Loading...", flush=True)
|
||||
data = load()
|
||||
print(f"{len(data)} bars\n", flush=True)
|
||||
|
||||
# === 测试多种参数组合 ===
|
||||
configs = [
|
||||
# (label, notional, engulf_body_min, engulf_ratio, engulf_sl, engulf_tp,
|
||||
# bb_rsi_long, bb_rsi_short, bb_sl, bb_tp, bb_atr_min, ema_atr_min, ema_sl)
|
||||
|
||||
# 基线:上一轮参数
|
||||
("v1: 基线", 10000,
|
||||
0.001, 1.5, 0.004, 0.005,
|
||||
30, 70, 0.003, 0.002, 0.0008,
|
||||
0.003, 0.004),
|
||||
|
||||
# v2: 更严格的吞没(减少低质量交易)
|
||||
("v2: 严格吞没", 10000,
|
||||
0.0015, 2.0, 0.004, 0.006,
|
||||
30, 70, 0.003, 0.002, 0.0008,
|
||||
0.003, 0.004),
|
||||
|
||||
# v3: 更严格吞没 + 更宽BB RSI
|
||||
("v3: 严格吞没+宽BB", 10000,
|
||||
0.0015, 2.0, 0.004, 0.006,
|
||||
25, 75, 0.003, 0.003, 0.001,
|
||||
0.003, 0.004),
|
||||
|
||||
# v4: 最严格吞没 + 最严BB
|
||||
("v4: 超严格", 10000,
|
||||
0.002, 2.5, 0.005, 0.008,
|
||||
20, 80, 0.004, 0.003, 0.001,
|
||||
0.003, 0.004),
|
||||
|
||||
# v5: 中等吞没 + 更大止盈
|
||||
("v5: 大止盈", 10000,
|
||||
0.0012, 1.8, 0.005, 0.008,
|
||||
28, 72, 0.004, 0.003, 0.001,
|
||||
0.003, 0.004),
|
||||
|
||||
# v6: 去掉BB(只用EMA+吞没)
|
||||
("v6: 仅EMA+吞没", 10000,
|
||||
0.0015, 2.0, 0.004, 0.006,
|
||||
999, -999, 0.003, 0.002, 999, # BB不会触发
|
||||
0.003, 0.004),
|
||||
|
||||
# v7: 放宽EMA的ATR
|
||||
("v7: EMA ATR>0.2%", 10000,
|
||||
0.0015, 2.0, 0.004, 0.006,
|
||||
25, 75, 0.003, 0.003, 0.001,
|
||||
0.002, 0.004),
|
||||
|
||||
# v8: v3的最优 x 300U仓位
|
||||
("v8: v3 x 300U仓位", 30000,
|
||||
0.0015, 2.0, 0.004, 0.006,
|
||||
25, 75, 0.003, 0.003, 0.001,
|
||||
0.003, 0.004),
|
||||
|
||||
# v9: v3 x 500U
|
||||
("v9: v3 x 500U仓位", 50000,
|
||||
0.0015, 2.0, 0.004, 0.006,
|
||||
25, 75, 0.003, 0.003, 0.001,
|
||||
0.003, 0.004),
|
||||
|
||||
# v10: v4 x 500U
|
||||
("v10: v4 x 500U", 50000,
|
||||
0.002, 2.5, 0.005, 0.008,
|
||||
20, 80, 0.004, 0.003, 0.001,
|
||||
0.003, 0.004),
|
||||
]
|
||||
|
||||
best_net = -99999; best_label = ""
|
||||
summary = []
|
||||
|
||||
for cfg in configs:
|
||||
label = cfg[0]
|
||||
trades = run_combo(data, *cfg[1:])
|
||||
net = analyze_print(trades, cfg[1], label)
|
||||
summary.append((label, cfg[1], len(trades), net))
|
||||
if net > best_net:
|
||||
best_net = net; best_label = label
|
||||
|
||||
# 总览
|
||||
print(f"\n\n{'='*80}", flush=True)
|
||||
print(f" SUMMARY — 目标: 月均 1000 USDT", flush=True)
|
||||
print(f"{'='*80}", flush=True)
|
||||
print(f" {'方案':<25} {'名义值':>10} {'交易数':>6} {'年净利':>10} {'月均':>8} {'达标':>4}", flush=True)
|
||||
print(f" {'-'*72}", flush=True)
|
||||
for label, notional, n, net in summary:
|
||||
mavg = net/12
|
||||
ok = "Yes" if mavg>=1000 else "No"
|
||||
print(f" {label:<25} {notional:>10,.0f} {n:>6} {net:>+10.0f} {mavg:>+8.0f} {ok:>4}", flush=True)
|
||||
print(f" {'-'*72}", flush=True)
|
||||
print(f" Best: {best_label} → {best_net:+.0f}/年 = {best_net/12:+.0f}/月", flush=True)
|
||||
print(f"{'='*80}", flush=True)
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
184
交易/bitmart-精准优化.py
Normal file
184
交易/bitmart-精准优化.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
EMA趋势策略 - 精准优化(~50组参数,约2分钟)
|
||||
|
||||
已知基线:EMA(8/21/120) ATR>0.03% SL=0.4% MH=1800s
|
||||
→ 14066 trades, dirPnL +327, netFee 428, net -101
|
||||
|
||||
核心优化思路:
|
||||
1. 提高 ATR 门槛 → 减少交易次数,只在波动大时交易
|
||||
2. 加长 EMA 周期 → 减少交叉频率
|
||||
3. 放宽止损 → 让趋势有更多空间发展
|
||||
4. 延长最大持仓 → 捕获更大行情
|
||||
"""
|
||||
import sys, time, datetime, sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
def bt(data, fp, sp, bp, atr_min, sl_pct, mh):
|
||||
bal=1000.0; pos=0; op=0.0; ot=None; ps=0.0; pend=None
|
||||
ef=EMA(fp); es=EMA(sp); eb=EMA(bp)
|
||||
H=[]; L=[]; C=[]
|
||||
pf_=None; ps_=None
|
||||
tc=0; wc=0; dpnl=0.0; tfee=0.0; treb=0.0
|
||||
hsl=sl_pct*1.5; AP=14
|
||||
|
||||
def _close(price, dt_):
|
||||
nonlocal bal,pos,op,ot,ps,pend,tc,wc,dpnl,tfee,treb
|
||||
pp = (price-op)/op if pos==1 else (op-price)/op
|
||||
pnl_=ps*pp; cv=ps*(1+pp); cf=cv*0.0006; of_=ps*0.0006; tt=of_+cf; rb=tt*0.9
|
||||
bal+=pnl_-cf+rb; dpnl+=pnl_; tfee+=tt; treb+=rb
|
||||
tc+=1; wc+=(1 if pnl_>0 else 0)
|
||||
pos=0; op=0; ot=None; ps=0; pend=None
|
||||
|
||||
def _open(d, price, dt_):
|
||||
nonlocal bal,pos,op,ot,ps
|
||||
ns=bal*0.005*50
|
||||
if ns<1: return
|
||||
bal-=ns*0.0006
|
||||
pos=1 if d=='L' else -1; op=price; ot=dt_; ps=ns
|
||||
|
||||
for dt,o_,h_,l_,c_ in data:
|
||||
p=c_; H.append(h_); L.append(l_); C.append(p)
|
||||
fast=ef.update(p); slow=es.update(p); big=eb.update(p)
|
||||
atr_pct=0.0
|
||||
if len(H)>AP+1:
|
||||
s=0.0
|
||||
for i in range(-AP,0):
|
||||
tr=H[i]-L[i]; d1=abs(H[i]-C[i-1]); d2=abs(L[i]-C[i-1])
|
||||
if d1>tr: tr=d1
|
||||
if d2>tr: tr=d2
|
||||
s+=tr
|
||||
atr_pct=s/(AP*p) if p>0 else 0
|
||||
cu=pf_ is not None and pf_<=ps_ and fast>slow
|
||||
cd=pf_ is not None and pf_>=ps_ and fast<slow
|
||||
pf_=fast; ps_=slow
|
||||
|
||||
if pos!=0 and ot:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
if -pp>=hsl: _close(p,dt); continue
|
||||
if hsec>=200:
|
||||
dc=False
|
||||
if -pp>=sl_pct: dc=True
|
||||
elif hsec>=mh: dc=True
|
||||
elif pos==1 and cd: dc=True
|
||||
elif pos==-1 and cu: dc=True
|
||||
elif pend=='cl' and pos==1: dc=True
|
||||
elif pend=='cs' and pos==-1: dc=True
|
||||
if dc:
|
||||
_close(p,dt)
|
||||
if atr_pct>=atr_min:
|
||||
if (cd or fast<slow) and p<big: _open('S',p,dt)
|
||||
elif (cu or fast>slow) and p>big: _open('L',p,dt)
|
||||
continue
|
||||
else:
|
||||
if pos==1 and cd: pend='cl'
|
||||
elif pos==-1 and cu: pend='cs'
|
||||
|
||||
if pos==0 and atr_pct>=atr_min:
|
||||
if cu and p>big: _open('L',p,dt)
|
||||
elif cd and p<big: _open('S',p,dt)
|
||||
|
||||
if pos!=0: _close(data[-1][4], data[-1][0])
|
||||
net=bal-1000.0; wr=wc/tc*100 if tc>0 else 0
|
||||
return net,tc,wr,dpnl,treb,tfee-treb
|
||||
|
||||
def main():
|
||||
print("Loading...", flush=True)
|
||||
data = load()
|
||||
print(f"{len(data)} bars\n", flush=True)
|
||||
|
||||
# ~50 targeted combos
|
||||
combos = []
|
||||
# Group 1: baseline variations (tweak one param at a time)
|
||||
base = (8, 21, 120, 0.0003, 0.004, 1800)
|
||||
# Vary ATR threshold (most impactful for reducing trades)
|
||||
for am in [0.0003, 0.0005, 0.0008, 0.001, 0.0013, 0.0015, 0.002, 0.003]:
|
||||
combos.append((8, 21, 120, am, 0.004, 1800))
|
||||
combos.append((8, 21, 120, am, 0.006, 1800))
|
||||
combos.append((8, 21, 120, am, 0.008, 1800))
|
||||
# Vary EMA periods
|
||||
for fp, sp in [(13, 34), (13, 55), (20, 55), (20, 80), (30, 80)]:
|
||||
for am in [0.0005, 0.001, 0.0015, 0.002]:
|
||||
combos.append((fp, sp, 120, am, 0.005, 1800))
|
||||
combos.append((fp, sp, 200, am, 0.005, 1800))
|
||||
combos.append((fp, sp, 120, am, 0.008, 3600))
|
||||
combos.append((fp, sp, 200, am, 0.008, 3600))
|
||||
# Vary max hold
|
||||
for mh in [900, 1800, 3600, 5400, 7200]:
|
||||
combos.append((8, 21, 120, 0.001, 0.005, mh))
|
||||
combos.append((13, 34, 120, 0.001, 0.005, mh))
|
||||
combos.append((13, 34, 200, 0.001, 0.008, mh))
|
||||
# Remove duplicates
|
||||
combos = list(set(combos))
|
||||
combos.sort()
|
||||
|
||||
print(f"Combos: {len(combos)}\n", flush=True)
|
||||
results = []
|
||||
t0 = time.time()
|
||||
|
||||
for idx, (fp, sp, bp, am, sl, mh) in enumerate(combos):
|
||||
net, tc, wr, dp, reb, nf = bt(data, fp, sp, bp, am, sl, mh)
|
||||
results.append((net, tc, wr, dp, reb, nf, fp, sp, bp, am, sl, mh))
|
||||
if (idx+1) % 10 == 0:
|
||||
el=time.time()-t0; eta=el/(idx+1)*(len(combos)-idx-1)
|
||||
print(f" [{idx+1}/{len(combos)}] {el:.0f}s / ~{eta:.0f}s left", flush=True)
|
||||
|
||||
tt = time.time() - t0
|
||||
results.sort(key=lambda x: x[0], reverse=True)
|
||||
profitable = [r for r in results if r[0] > 0]
|
||||
|
||||
print(f"\nDone! {tt:.1f}s | Profitable: {len(profitable)}/{len(results)}\n", flush=True)
|
||||
|
||||
print("="*125, flush=True)
|
||||
print(" TOP 30 RESULTS (sorted by Net P&L)", flush=True)
|
||||
print("="*125, flush=True)
|
||||
print(f" {'#':>3} {'F':>3} {'S':>3} {'B':>4} {'ATR':>6} {'SL':>5} {'MH':>5} | {'Net%':>7} {'Net$':>9} {'#Trd':>6} {'WR':>6} {'DirPnL':>9} {'Rebate':>9} {'NetFee':>8}", flush=True)
|
||||
print(f" {'-'*119}", flush=True)
|
||||
for i,(net,tc,wr,dp,reb,nf,fp,sp,bp,am,sl,mh) in enumerate(results[:30]):
|
||||
mk=" <<<" if net>0 else ""
|
||||
print(f" {i+1:>3} {fp:>3} {sp:>3} {bp:>4} {am*100:>5.2f}% {sl*100:>4.1f}% {mh:>5} | {net/10:>+6.2f}% {net:>+8.2f} {tc:>6} {wr:>5.1f}% {dp:>+8.2f} {reb:>8.2f} {nf:>8.2f}{mk}", flush=True)
|
||||
|
||||
if profitable:
|
||||
print(f"\n{'='*125}", flush=True)
|
||||
print(f" ALL {len(profitable)} PROFITABLE COMBOS", flush=True)
|
||||
print(f"{'='*125}", flush=True)
|
||||
for i,(net,tc,wr,dp,reb,nf,fp,sp,bp,am,sl,mh) in enumerate(profitable):
|
||||
print(f" {i+1:>3} EMA({fp}/{sp}/{bp}) ATR>{am*100:.2f}% SL={sl*100:.1f}% MH={mh}s | net={net:+.2f}$ ({net/10:+.2f}%) trades={tc} WR={wr:.1f}% dir={dp:+.2f} reb={reb:.2f} fee={nf:.2f}", flush=True)
|
||||
else:
|
||||
print("\n No profitable combos found. The EMA trend strategy may need a fundamentally different approach.", flush=True)
|
||||
# Show the closest to profitable
|
||||
print(f"\n Closest to breakeven:", flush=True)
|
||||
for i,(net,tc,wr,dp,reb,nf,fp,sp,bp,am,sl,mh) in enumerate(results[:5]):
|
||||
gap = -net
|
||||
print(f" EMA({fp}/{sp}/{bp}) ATR>{am*100:.2f}% SL={sl*100:.1f}% MH={mh}s | net={net:+.2f}$ gap_to_profit={gap:.2f}$ trades={tc} dir={dp:+.2f}", flush=True)
|
||||
|
||||
print("="*125, flush=True)
|
||||
|
||||
csv = Path(__file__).parent.parent / 'param_results.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("fast,slow,big,atr_min,stop_loss,max_hold,net_pct,net_usd,trades,win_rate,dir_pnl,rebate,net_fee\n")
|
||||
for net,tc,wr,dp,reb,nf,fp,sp,bp,am,sl,mh in results:
|
||||
f.write(f"{fp},{sp},{bp},{am},{sl},{mh},{net/10:.4f},{net:.4f},{tc},{wr:.2f},{dp:.4f},{reb:.4f},{nf:.4f}\n")
|
||||
print(f"\nSaved: {csv}", flush=True)
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
399
交易/bitmart-组合策略回测.py
Normal file
399
交易/bitmart-组合策略回测.py
Normal file
@@ -0,0 +1,399 @@
|
||||
"""
|
||||
多信号组合策略回测 — 目标 1000 USDT/月
|
||||
|
||||
5种信号源,同一时间只持一个仓位,每笔100U保证金100x杠杆:
|
||||
|
||||
信号1: EMA金叉死叉 + ATR过滤 + 大趋势方向(已验证盈利)
|
||||
信号2: 三分之一策略 — 前K线实体的1/3作为触发价(动量突破)
|
||||
信号3: 布林带反弹 — 价格触及上下轨 + RSI确认(均值回归)
|
||||
信号4: 吞没形态 — 当前K线完全包裹前K线(反转信号)
|
||||
信号5: Pin Bar — 长影线蜡烛(拒绝信号)
|
||||
|
||||
所有信号共用:
|
||||
- 大趋势过滤 EMA(120)
|
||||
- 最低持仓 200秒 (>3分钟)
|
||||
- 各自独立的止盈止损参数
|
||||
- 90% 手续费返佣
|
||||
"""
|
||||
import sys, time, datetime, sqlite3, statistics
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
|
||||
def calc_bb(closes, period=20, nstd=2.0):
|
||||
if len(closes) < period:
|
||||
return None, None, None
|
||||
rec = closes[-period:]
|
||||
mid = sum(rec) / period
|
||||
var = sum((x - mid)**2 for x in rec) / period
|
||||
std = var ** 0.5
|
||||
return mid + nstd * std, mid, mid - nstd * std
|
||||
|
||||
|
||||
def calc_rsi(closes, period=14):
|
||||
if len(closes) < period + 1:
|
||||
return None
|
||||
gains = 0; losses = 0
|
||||
for i in range(-period, 0):
|
||||
d = closes[i] - closes[i-1]
|
||||
if d > 0: gains += d
|
||||
else: losses -= d
|
||||
if losses == 0: return 100.0
|
||||
rs = (gains/period) / (losses/period)
|
||||
return 100 - 100 / (1 + rs)
|
||||
|
||||
|
||||
def main():
|
||||
print("Loading...", flush=True)
|
||||
data = load()
|
||||
N = len(data)
|
||||
print(f"{N} bars loaded\n", flush=True)
|
||||
|
||||
# ===== 参数 =====
|
||||
NOTIONAL = 10000.0 # 100U * 100x
|
||||
FEE_RATE = 0.0006
|
||||
REB_RATE = 0.90
|
||||
MIN_HOLD = 200 # 秒
|
||||
FEE_PER_TRADE = NOTIONAL * FEE_RATE * 2 # 12 USDT
|
||||
REB_PER_TRADE = FEE_PER_TRADE * REB_RATE # 10.8 USDT
|
||||
NET_FEE = FEE_PER_TRADE - REB_PER_TRADE # 1.2 USDT
|
||||
|
||||
# 信号参数
|
||||
# 信号1: EMA交叉
|
||||
EMA_FAST = 8; EMA_SLOW = 21; EMA_BIG = 120
|
||||
ATR_MIN = 0.003; ATR_P = 14
|
||||
SL1 = 0.004; HSL1 = 0.006
|
||||
|
||||
# 信号2: 三分之一 (用5分钟聚合K线)
|
||||
BODY_MIN = 0.0008 # 最小实体占价格比例 0.08%
|
||||
SL2 = 0.003; TP2 = 0.004 # 三分之一策略:小止损高胜率
|
||||
|
||||
# 信号3: 布林带反弹
|
||||
BB_P = 20; BB_STD = 2.0; RSI_P = 14
|
||||
RSI_LONG = 30; RSI_SHORT = 70
|
||||
SL3 = 0.003; TP3 = 0.002 # 均值回归:回到中轨附近
|
||||
|
||||
# 信号4: 吞没形态
|
||||
ENGULF_MIN_BODY = 0.001 # 最小吞没实体占比
|
||||
SL4 = 0.004; TP4 = 0.005
|
||||
|
||||
# 信号5: Pin Bar
|
||||
PIN_SHADOW_RATIO = 2.0 # 影线 >= 2倍实体
|
||||
PIN_MIN_SHADOW = 0.001 # 最小影线占价格比例
|
||||
SL5 = 0.003; TP5 = 0.004
|
||||
|
||||
MAX_HOLD = 1800 # 所有信号共用最大持仓
|
||||
|
||||
# ===== 状态 =====
|
||||
ema_f = EMA(EMA_FAST); ema_s = EMA(EMA_SLOW); ema_b = EMA(EMA_BIG)
|
||||
prev_fast = None; prev_slow = None
|
||||
closes_buf = []; highs_buf = []; lows_buf = []
|
||||
|
||||
# 5分钟K线聚合
|
||||
bar5_open = None; bar5_high = None; bar5_low = None; bar5_close = None
|
||||
bar5_count = 0; bars5 = [] # 完成的5分钟K线
|
||||
|
||||
pos = 0; op = 0.0; ot = None; sig_type = ""; sl_pct = 0; tp_pct = 0
|
||||
trades = []
|
||||
|
||||
def do_close(price, dt_, reason):
|
||||
nonlocal pos, op, ot, sig_type
|
||||
pp = (price - op) / op if pos == 1 else (op - price) / op
|
||||
pnl = NOTIONAL * pp
|
||||
fee = FEE_PER_TRADE; reb = REB_PER_TRADE
|
||||
hsec = (dt_ - ot).total_seconds()
|
||||
trades.append((sig_type, 'long' if pos==1 else 'short', op, price,
|
||||
pnl, fee, reb, hsec, reason, ot, dt_))
|
||||
pos = 0; op = 0; ot = None; sig_type = ""
|
||||
|
||||
def do_open(direction, price, dt_, stype, sl, tp):
|
||||
nonlocal pos, op, ot, sig_type, sl_pct, tp_pct
|
||||
pos = 1 if direction == 'long' else -1
|
||||
op = price; ot = dt_; sig_type = stype; sl_pct = sl; tp_pct = tp
|
||||
|
||||
for i in range(N):
|
||||
dt, o_, h_, l_, c_ = data[i]
|
||||
p = c_
|
||||
|
||||
# 更新缓存
|
||||
closes_buf.append(p); highs_buf.append(h_); lows_buf.append(l_)
|
||||
if len(closes_buf) > 300:
|
||||
closes_buf = closes_buf[-300:]
|
||||
highs_buf = highs_buf[-300:]
|
||||
lows_buf = lows_buf[-300:]
|
||||
|
||||
# EMA更新
|
||||
fast = ema_f.update(p); slow = ema_s.update(p); big = ema_b.update(p)
|
||||
|
||||
# ATR
|
||||
atr_pct = 0.0
|
||||
if len(highs_buf) > ATR_P + 1:
|
||||
s = 0.0
|
||||
for j in range(-ATR_P, 0):
|
||||
tr = highs_buf[j] - lows_buf[j]
|
||||
d1 = abs(highs_buf[j] - closes_buf[j-1])
|
||||
d2 = abs(lows_buf[j] - closes_buf[j-1])
|
||||
if d1 > tr: tr = d1
|
||||
if d2 > tr: tr = d2
|
||||
s += tr
|
||||
atr_pct = s / (ATR_P * p) if p > 0 else 0
|
||||
|
||||
# EMA交叉
|
||||
ema_cross_up = prev_fast is not None and prev_fast <= prev_slow and fast > slow
|
||||
ema_cross_dn = prev_fast is not None and prev_fast >= prev_slow and fast < slow
|
||||
prev_fast = fast; prev_slow = slow
|
||||
|
||||
# 布林带 & RSI
|
||||
bb_upper, bb_mid, bb_lower = calc_bb(closes_buf, BB_P, BB_STD)
|
||||
rsi = calc_rsi(closes_buf, RSI_P)
|
||||
|
||||
# 5分钟K线聚合
|
||||
if bar5_open is None:
|
||||
bar5_open = o_; bar5_high = h_; bar5_low = l_; bar5_close = c_; bar5_count = 1
|
||||
else:
|
||||
bar5_high = max(bar5_high, h_)
|
||||
bar5_low = min(bar5_low, l_)
|
||||
bar5_close = c_
|
||||
bar5_count += 1
|
||||
|
||||
new_bar5 = None
|
||||
if bar5_count >= 5:
|
||||
new_bar5 = {'open': bar5_open, 'high': bar5_high, 'low': bar5_low, 'close': bar5_close}
|
||||
bars5.append(new_bar5)
|
||||
if len(bars5) > 50: bars5 = bars5[-50:]
|
||||
bar5_open = None; bar5_count = 0
|
||||
|
||||
# K线形态(用1分钟线)
|
||||
prev_bar = data[i-1] if i > 0 else None
|
||||
prev2_bar = data[i-2] if i > 1 else None
|
||||
|
||||
# ===== 有持仓:检查平仓 =====
|
||||
if pos != 0 and ot is not None:
|
||||
pp = (p - op) / op if pos == 1 else (op - p) / op
|
||||
hsec = (dt - ot).total_seconds()
|
||||
|
||||
# 硬止损
|
||||
hard_sl = max(sl_pct * 1.5, 0.006)
|
||||
if -pp >= hard_sl:
|
||||
do_close(p, dt, f"硬止损({pp*100:+.2f}%)"); continue
|
||||
|
||||
if hsec >= MIN_HOLD:
|
||||
# 止损
|
||||
if -pp >= sl_pct:
|
||||
do_close(p, dt, f"止损({pp*100:+.2f}%)"); continue
|
||||
# 止盈
|
||||
if tp_pct > 0 and pp >= tp_pct:
|
||||
do_close(p, dt, f"止盈({pp*100:+.2f}%)"); continue
|
||||
# 超时
|
||||
if hsec >= MAX_HOLD:
|
||||
do_close(p, dt, f"超时({hsec:.0f}s)"); continue
|
||||
# EMA信号反转平仓(仅EMA信号开的仓)
|
||||
if sig_type == "EMA":
|
||||
if pos == 1 and ema_cross_dn:
|
||||
do_close(p, dt, "EMA反转"); continue
|
||||
if pos == -1 and ema_cross_up:
|
||||
do_close(p, dt, "EMA反转"); continue
|
||||
# BB信号回到中轨平仓
|
||||
if sig_type == "BB" and bb_mid is not None:
|
||||
if pos == 1 and p >= bb_mid:
|
||||
do_close(p, dt, "BB回中轨"); continue
|
||||
if pos == -1 and p <= bb_mid:
|
||||
do_close(p, dt, "BB回中轨"); continue
|
||||
|
||||
# ===== 无持仓:检查开仓(按优先级) =====
|
||||
if pos == 0 and i > 20:
|
||||
signal = None; s_sl = 0; s_tp = 0; s_type = ""
|
||||
|
||||
# 优先级1: EMA交叉(最高质量)
|
||||
if atr_pct >= ATR_MIN:
|
||||
if ema_cross_up and p > big:
|
||||
signal = 'long'; s_type = "EMA"; s_sl = SL1; s_tp = 0
|
||||
elif ema_cross_dn and p < big:
|
||||
signal = 'short'; s_type = "EMA"; s_sl = SL1; s_tp = 0
|
||||
|
||||
# 优先级2: 三分之一策略(5分钟K线动量)
|
||||
if signal is None and len(bars5) >= 2 and new_bar5 is not None:
|
||||
prev5 = bars5[-2]
|
||||
body5 = abs(prev5['close'] - prev5['open'])
|
||||
body5_pct = body5 / prev5['close'] if prev5['close'] > 0 else 0
|
||||
if body5_pct >= BODY_MIN:
|
||||
trigger_up = prev5['close'] + body5 / 3
|
||||
trigger_dn = prev5['close'] - body5 / 3
|
||||
cur5 = bars5[-1]
|
||||
if cur5['high'] >= trigger_up and p > big:
|
||||
signal = 'long'; s_type = "1/3"; s_sl = SL2; s_tp = TP2
|
||||
elif cur5['low'] <= trigger_dn and p < big:
|
||||
signal = 'short'; s_type = "1/3"; s_sl = SL2; s_tp = TP2
|
||||
|
||||
# 优先级3: 吞没形态
|
||||
if signal is None and prev_bar is not None:
|
||||
pb_o, pb_c = prev_bar[1], prev_bar[4]
|
||||
cb_o, cb_c = o_, c_
|
||||
pb_body = abs(pb_c - pb_o)
|
||||
cb_body = abs(cb_c - cb_o)
|
||||
pb_body_pct = pb_body / p if p > 0 else 0
|
||||
cb_body_pct = cb_body / p if p > 0 else 0
|
||||
|
||||
if cb_body_pct >= ENGULF_MIN_BODY and cb_body > pb_body * 1.5:
|
||||
# 看涨吞没:前阴后阳,当前完全包裹
|
||||
if pb_c < pb_o and cb_c > cb_o and cb_c > pb_o and cb_o <= pb_c:
|
||||
if p > big and atr_pct >= 0.001:
|
||||
signal = 'long'; s_type = "吞没"; s_sl = SL4; s_tp = TP4
|
||||
# 看跌吞没:前阳后阴
|
||||
elif pb_c > pb_o and cb_c < cb_o and cb_c < pb_o and cb_o >= pb_c:
|
||||
if p < big and atr_pct >= 0.001:
|
||||
signal = 'short'; s_type = "吞没"; s_sl = SL4; s_tp = TP4
|
||||
|
||||
# 优先级4: Pin Bar(长影线反转)
|
||||
if signal is None and prev_bar is not None:
|
||||
pb_o, pb_h, pb_l, pb_c = prev_bar[1], prev_bar[2], prev_bar[3], prev_bar[4]
|
||||
pb_body = abs(pb_c - pb_o)
|
||||
upper_shadow = pb_h - max(pb_o, pb_c)
|
||||
lower_shadow = min(pb_o, pb_c) - pb_l
|
||||
if pb_body > 0:
|
||||
# 看涨Pin Bar:长下影线
|
||||
if lower_shadow >= PIN_SHADOW_RATIO * pb_body:
|
||||
ls_pct = lower_shadow / p if p > 0 else 0
|
||||
if ls_pct >= PIN_MIN_SHADOW and p > big and atr_pct >= 0.001:
|
||||
signal = 'long'; s_type = "PinBar"; s_sl = SL5; s_tp = TP5
|
||||
# 看跌Pin Bar:长上影线
|
||||
if upper_shadow >= PIN_SHADOW_RATIO * pb_body:
|
||||
us_pct = upper_shadow / p if p > 0 else 0
|
||||
if us_pct >= PIN_MIN_SHADOW and p < big and atr_pct >= 0.001:
|
||||
signal = 'short'; s_type = "PinBar"; s_sl = SL5; s_tp = TP5
|
||||
|
||||
# 优先级5: 布林带反弹
|
||||
if signal is None and bb_upper is not None and rsi is not None:
|
||||
if p <= bb_lower and rsi < RSI_LONG and p > big and atr_pct >= 0.0008:
|
||||
signal = 'long'; s_type = "BB"; s_sl = SL3; s_tp = TP3
|
||||
elif p >= bb_upper and rsi > RSI_SHORT and p < big and atr_pct >= 0.0008:
|
||||
signal = 'short'; s_type = "BB"; s_sl = SL3; s_tp = TP3
|
||||
|
||||
if signal:
|
||||
do_open(signal, p, dt, s_type, s_sl, s_tp)
|
||||
|
||||
# 强制平仓
|
||||
if pos != 0:
|
||||
p = data[-1][4]; dt = data[-1][0]
|
||||
do_close(p, dt, "回测结束")
|
||||
|
||||
# ===== 分析结果 =====
|
||||
if not trades:
|
||||
print("No trades!", flush=True); return
|
||||
|
||||
n = len(trades)
|
||||
total_pnl = sum(t[4] for t in trades)
|
||||
total_fee = FEE_PER_TRADE * n
|
||||
total_reb = REB_PER_TRADE * n
|
||||
net = total_pnl - (total_fee - total_reb)
|
||||
wins = [t for t in trades if t[4] > 0]
|
||||
wr = len(wins) / n * 100
|
||||
|
||||
# 按信号类型统计
|
||||
by_type = defaultdict(lambda: {'n':0, 'pnl':0, 'w':0})
|
||||
for t in trades:
|
||||
by_type[t[0]]['n'] += 1
|
||||
by_type[t[0]]['pnl'] += t[4]
|
||||
if t[4] > 0: by_type[t[0]]['w'] += 1
|
||||
|
||||
# 月度
|
||||
monthly = defaultdict(lambda: {'n':0, 'pnl':0, 'reb':0, 'fee':0, 'w':0})
|
||||
for t in trades:
|
||||
k = t[10].strftime('%Y-%m')
|
||||
monthly[k]['n'] += 1
|
||||
monthly[k]['pnl'] += t[4]
|
||||
monthly[k]['reb'] += REB_PER_TRADE
|
||||
monthly[k]['fee'] += FEE_PER_TRADE
|
||||
if t[4] > 0: monthly[k]['w'] += 1
|
||||
|
||||
# 最大回撤
|
||||
cum=0; peak=0; dd=0
|
||||
for t in trades:
|
||||
cum += t[4] - NET_FEE
|
||||
if cum > peak: peak = cum
|
||||
if peak - cum > dd: dd = peak - cum
|
||||
|
||||
print("=" * 75, flush=True)
|
||||
print(" 多信号组合策略回测 | 100U x 100倍 | 目标1000U/月", flush=True)
|
||||
print("=" * 75, flush=True)
|
||||
|
||||
print(f"\n --- 核心收益 ---", flush=True)
|
||||
print(f" 方向盈亏: {total_pnl:>+12.2f} USDT", flush=True)
|
||||
print(f" 返佣(90%): {total_reb:>+12.2f} USDT", flush=True)
|
||||
print(f" 净手续费(10%): {total_fee-total_reb:>12.2f} USDT", flush=True)
|
||||
print(f" ================================", flush=True)
|
||||
print(f" 年净利润: {net:>+12.2f} USDT", flush=True)
|
||||
print(f" 月均净利: {net/12:>+12.2f} USDT", flush=True)
|
||||
print(f" 最大回撤: {dd:>12.2f} USDT", flush=True)
|
||||
|
||||
print(f"\n --- 交易统计 ---", flush=True)
|
||||
print(f" 总交易: {n} 笔 | 胜率: {wr:.1f}% | 月均: {n/12:.0f} 笔", flush=True)
|
||||
|
||||
print(f"\n --- 按信号类型 ---", flush=True)
|
||||
print(f" {'类型':<10} {'笔数':>6} {'方向盈亏':>10} {'净利':>10} {'胜率':>6} {'每笔均利':>10}", flush=True)
|
||||
print(f" {'-'*56}", flush=True)
|
||||
for stype in sorted(by_type.keys()):
|
||||
d = by_type[stype]
|
||||
net_t = d['pnl'] - NET_FEE * d['n']
|
||||
avg = net_t / d['n'] if d['n'] > 0 else 0
|
||||
wr_t = d['w'] / d['n'] * 100 if d['n'] > 0 else 0
|
||||
mark = " ++" if net_t > 0 else " --"
|
||||
print(f" {stype:<10} {d['n']:>6} {d['pnl']:>+10.2f} {net_t:>+10.2f} {wr_t:>5.1f}% {avg:>+10.2f}{mark}", flush=True)
|
||||
|
||||
print(f"\n --- 月度明细 ---", flush=True)
|
||||
print(f" {'月份':<8} {'笔数':>5} {'方向盈亏':>10} {'返佣':>8} {'净利润':>10} {'胜率':>6}", flush=True)
|
||||
print(f" {'-'*52}", flush=True)
|
||||
for m in sorted(monthly.keys()):
|
||||
d = monthly[m]
|
||||
net_m = d['pnl'] - (d['fee'] - d['reb'])
|
||||
wr_m = d['w'] / d['n'] * 100 if d['n'] > 0 else 0
|
||||
print(f" {m:<8} {d['n']:>5} {d['pnl']:>+10.2f} {d['reb']:>8.2f} {net_m:>+10.2f} {wr_m:>5.1f}%", flush=True)
|
||||
print(f" {'-'*52}", flush=True)
|
||||
print(f" {'合计':<8} {n:>5} {total_pnl:>+10.2f} {total_reb:>8.2f} {net:>+10.2f} {wr:>5.1f}%", flush=True)
|
||||
|
||||
# ===== 测试不同仓位大小达到1000U/月需要多少 =====
|
||||
print(f"\n --- 仓位放大测试 ---", flush=True)
|
||||
print(f" {'保证金':>8} {'杠杆':>4} {'名义价值':>12} {'年净利':>10} {'月均':>8} {'达标':>4}", flush=True)
|
||||
print(f" {'-'*52}", flush=True)
|
||||
for margin in [100, 200, 300, 500, 800, 1000]:
|
||||
lev = 100
|
||||
notional_test = margin * lev
|
||||
scale = notional_test / NOTIONAL
|
||||
net_scaled = net * scale
|
||||
monthly_avg = net_scaled / 12
|
||||
ok = "Yes" if monthly_avg >= 1000 else "No"
|
||||
print(f" {margin:>7}U {lev:>3}x {notional_test:>11,}U {net_scaled:>+10.0f} {monthly_avg:>+8.0f} {ok:>4}", flush=True)
|
||||
|
||||
print(f"\n{'='*75}", flush=True)
|
||||
|
||||
# 保存CSV
|
||||
csv = Path(__file__).parent.parent / 'combo_trades.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("信号,方向,开仓价,平仓价,方向盈亏,手续费,返佣,持仓秒,原因,开仓时间,平仓时间\n")
|
||||
for t in trades:
|
||||
f.write(f"{t[0]},{t[1]},{t[2]:.2f},{t[3]:.2f},{t[4]:.2f},"
|
||||
f"{t[5]:.2f},{t[6]:.2f},{t[7]:.0f},{t[8]},{t[9]},{t[10]}\n")
|
||||
print(f"\n Saved: {csv}", flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
338
交易/bitmart-终极策略回测.py
Normal file
338
交易/bitmart-终极策略回测.py
Normal file
@@ -0,0 +1,338 @@
|
||||
"""
|
||||
终极组合策略回测 — 多时间框架 + 波动率自适应
|
||||
|
||||
核心改进:
|
||||
1. 吞没形态用5分钟K线(减少噪音,提高质量)
|
||||
2. 加入突破策略(N根K线新高新低突破)
|
||||
3. 波动率自适应:高波动用趋势跟踪,低波动用均值回归
|
||||
4. 动态止盈:使用ATR倍数作为止盈目标而非固定比例
|
||||
5. 趋势强度过滤:EMA斜率+ADX概念
|
||||
|
||||
信号:
|
||||
A) EMA(8/21) 金叉死叉 + ATR>0.3% + EMA(120)趋势方向
|
||||
B) 5分钟吞没形态 + EMA趋势方向 + ATR确认
|
||||
C) 20根K线高低突破 + 趋势方向
|
||||
D) BB反弹 + RSI极值 + 趋势方向(仅逆趋势小单)
|
||||
|
||||
条件: 同时只持1个仓, 90%返佣, >3分钟持仓
|
||||
"""
|
||||
import sys, time, datetime, sqlite3
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
class EMA:
|
||||
__slots__ = ('k', 'v')
|
||||
def __init__(self, p):
|
||||
self.k = 2.0 / (p + 1); self.v = None
|
||||
def update(self, x):
|
||||
self.v = x if self.v is None else x * self.k + self.v * (1 - self.k)
|
||||
return self.v
|
||||
|
||||
def load():
|
||||
db = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
s = int(datetime.datetime(2025,1,1).timestamp()) * 1000
|
||||
e = int(datetime.datetime(2026,1,1).timestamp()) * 1000
|
||||
conn = sqlite3.connect(str(db))
|
||||
rows = conn.cursor().execute(
|
||||
"SELECT id,open,high,low,close FROM bitmart_eth_1m WHERE id>=? AND id<? ORDER BY id", (s,e)
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return [(datetime.datetime.fromtimestamp(r[0]/1000.0), r[1], r[2], r[3], r[4]) for r in rows]
|
||||
|
||||
def run(data, notional):
|
||||
N = len(data)
|
||||
FEE = notional * 0.0006 * 2
|
||||
REB = FEE * 0.9
|
||||
NFEE = FEE - REB
|
||||
MIN_HOLD = 200; MAX_HOLD = 1800
|
||||
|
||||
# EMA
|
||||
ef = EMA(8); es = EMA(21); eb = EMA(120); e50 = EMA(50)
|
||||
pf_ = None; ps_ = None
|
||||
CB = []; HB = []; LB = []
|
||||
|
||||
# 5分钟聚合
|
||||
b5_o=None; b5_h=None; b5_l=None; b5_c=None; b5_cnt=0
|
||||
bars5 = []
|
||||
|
||||
# 突破追踪
|
||||
high_20 = []; low_20 = []
|
||||
|
||||
pos=0; op=0.0; ot=None; st=""; sl=0; tp_atr=0
|
||||
trades = []
|
||||
|
||||
def close_(price, dt_, reason):
|
||||
nonlocal pos, op, ot, st
|
||||
pp = (price-op)/op if pos==1 else (op-price)/op
|
||||
trades.append((st, 'L' if pos==1 else 'S', op, price, notional*pp,
|
||||
(dt_-ot).total_seconds(), reason, ot, dt_))
|
||||
pos=0; op=0; ot=None; st=""
|
||||
|
||||
for i in range(N):
|
||||
dt, o_, h_, l_, c_ = data[i]
|
||||
p = c_
|
||||
CB.append(p); HB.append(h_); LB.append(l_)
|
||||
if len(CB)>300: CB=CB[-300:]; HB=HB[-300:]; LB=LB[-300:]
|
||||
|
||||
fast=ef.update(p); slow=es.update(p); big=eb.update(p); mid50=e50.update(p)
|
||||
|
||||
# ATR
|
||||
atr=0.0; atr_val=0.0
|
||||
if len(HB)>15:
|
||||
s=0
|
||||
for j in range(-14,0):
|
||||
tr=HB[j]-LB[j]; d1=abs(HB[j]-CB[j-1]); d2=abs(LB[j]-CB[j-1])
|
||||
if d1>tr: tr=d1
|
||||
if d2>tr: tr=d2
|
||||
s+=tr
|
||||
atr_val = s/14
|
||||
atr = atr_val/p if p>0 else 0
|
||||
|
||||
# EMA交叉
|
||||
cu = pf_ is not None and pf_<=ps_ and fast>slow
|
||||
cd = pf_ is not None and pf_>=ps_ and fast<slow
|
||||
pf_=fast; ps_=slow
|
||||
|
||||
# EMA趋势强度(斜率)
|
||||
trend_up = fast > slow and slow > big # 三线多头
|
||||
trend_dn = fast < slow and slow < big # 三线空头
|
||||
|
||||
# BB & RSI
|
||||
bb_u=None; bb_m=None; bb_l=None; rsi=None
|
||||
if len(CB)>=20:
|
||||
rec=CB[-20:]; mid=sum(rec)/20
|
||||
var=sum((x-mid)**2 for x in rec)/20; std=var**0.5
|
||||
bb_u=mid+2*std; bb_m=mid; bb_l=mid-2*std
|
||||
if len(CB)>=15:
|
||||
g=0; l=0
|
||||
for j in range(-14,0):
|
||||
d=CB[j]-CB[j-1]
|
||||
if d>0: g+=d
|
||||
else: l-=d
|
||||
rsi = 100-100/(1+(g/14)/(l/14)) if l>0 else 100
|
||||
|
||||
# 5分钟聚合
|
||||
if b5_o is None:
|
||||
b5_o=o_; b5_h=h_; b5_l=l_; b5_c=c_; b5_cnt=1
|
||||
else:
|
||||
b5_h=max(b5_h,h_); b5_l=min(b5_l,l_); b5_c=c_; b5_cnt+=1
|
||||
new_b5 = False
|
||||
if b5_cnt>=5:
|
||||
bars5.append({'o':b5_o,'h':b5_h,'l':b5_l,'c':b5_c})
|
||||
if len(bars5)>30: bars5=bars5[-30:]
|
||||
b5_o=None; b5_cnt=0; new_b5=True
|
||||
|
||||
# 20根K线高低(用于突破)
|
||||
high_20.append(h_); low_20.append(l_)
|
||||
if len(high_20)>21: high_20=high_20[-21:]; low_20=low_20[-21:]
|
||||
breakout_high = max(high_20[:-1]) if len(high_20)>1 else None
|
||||
breakout_low = min(low_20[:-1]) if len(low_20)>1 else None
|
||||
|
||||
# ===== 持仓管理 =====
|
||||
if pos!=0 and ot:
|
||||
pp=(p-op)/op if pos==1 else (op-p)/op
|
||||
hsec=(dt-ot).total_seconds()
|
||||
|
||||
# 动态止损/止盈(基于ATR)
|
||||
hard_sl = max(sl*1.5, 0.006)
|
||||
if -pp>=hard_sl: close_(p,dt,"硬止损"); continue
|
||||
|
||||
if hsec>=MIN_HOLD:
|
||||
if -pp>=sl: close_(p,dt,"止损"); continue
|
||||
|
||||
# 动态止盈:盈利超过 tp_atr 倍ATR值
|
||||
if tp_atr>0 and atr_val>0:
|
||||
tp_target = tp_atr * atr_val / op # 转为百分比
|
||||
if pp>=tp_target: close_(p,dt,f"ATR止盈({pp*100:.2f}%)"); continue
|
||||
|
||||
if hsec>=MAX_HOLD: close_(p,dt,"超时"); continue
|
||||
|
||||
# 趋势跟踪出场
|
||||
if st in ("EMA","突破"):
|
||||
if pos==1 and cd: close_(p,dt,"反向交叉"); continue
|
||||
if pos==-1 and cu: close_(p,dt,"反向交叉"); continue
|
||||
# 价格跌破慢线止盈
|
||||
if pos==1 and p<slow and pp>0.001: close_(p,dt,"破慢线止盈"); continue
|
||||
if pos==-1 and p>slow and pp>0.001: close_(p,dt,"破慢线止盈"); continue
|
||||
|
||||
if st=="BB" and bb_m:
|
||||
if pos==1 and p>=bb_m: close_(p,dt,"BB回中轨"); continue
|
||||
if pos==-1 and p<=bb_m: close_(p,dt,"BB回中轨"); continue
|
||||
|
||||
if st=="5m吞没":
|
||||
if pos==1 and cd: close_(p,dt,"反向交叉"); continue
|
||||
if pos==-1 and cu: close_(p,dt,"反向交叉"); continue
|
||||
|
||||
# ===== 开仓 =====
|
||||
if pos==0 and i>120:
|
||||
sig=None; s_sl=0; s_tp_atr=0; s_type=""
|
||||
|
||||
# 信号A: EMA交叉(最高质量)
|
||||
if atr>=0.003:
|
||||
if cu and p>big:
|
||||
sig='L'; s_type="EMA"; s_sl=0.004; s_tp_atr=3.0
|
||||
elif cd and p<big:
|
||||
sig='S'; s_type="EMA"; s_sl=0.004; s_tp_atr=3.0
|
||||
|
||||
# 信号B: 5分钟吞没(仅在新5分钟K线完成时检查)
|
||||
if sig is None and new_b5 and len(bars5)>=3:
|
||||
prev5 = bars5[-2]; cur5 = bars5[-1]
|
||||
pb = abs(prev5['c']-prev5['o'])
|
||||
cb = abs(cur5['c']-cur5['o'])
|
||||
cb_pct = cb/p if p>0 else 0
|
||||
|
||||
if cb_pct>=0.0015 and cb>pb*1.5 and atr>=0.0015:
|
||||
# 看涨吞没
|
||||
if prev5['c']<prev5['o'] and cur5['c']>cur5['o']:
|
||||
if cur5['c']>prev5['o'] and cur5['o']<=prev5['c']:
|
||||
if p>big:
|
||||
sig='L'; s_type="5m吞没"; s_sl=0.005; s_tp_atr=2.5
|
||||
# 看跌吞没
|
||||
elif prev5['c']>prev5['o'] and cur5['c']<cur5['o']:
|
||||
if cur5['c']<prev5['o'] and cur5['o']>=prev5['c']:
|
||||
if p<big:
|
||||
sig='S'; s_type="5m吞没"; s_sl=0.005; s_tp_atr=2.5
|
||||
|
||||
# 信号C: 突破(20根K线新高新低)
|
||||
if sig is None and breakout_high and atr>=0.002:
|
||||
if h_>breakout_high and trend_up:
|
||||
sig='L'; s_type="突破"; s_sl=0.005; s_tp_atr=2.5
|
||||
elif l_<breakout_low and trend_dn:
|
||||
sig='S'; s_type="突破"; s_sl=0.005; s_tp_atr=2.5
|
||||
|
||||
# 信号D: BB反弹(要求RSI极值 + ATR适中)
|
||||
if sig is None and bb_u and rsi is not None and 0.001<=atr<=0.004:
|
||||
if p<=bb_l and rsi<25 and p>big:
|
||||
sig='L'; s_type="BB"; s_sl=0.003; s_tp_atr=0
|
||||
elif p>=bb_u and rsi>75 and p<big:
|
||||
sig='S'; s_type="BB"; s_sl=0.003; s_tp_atr=0
|
||||
|
||||
if sig:
|
||||
pos=1 if sig=='L' else -1; op=p; ot=dt
|
||||
st=s_type; sl=s_sl; tp_atr=s_tp_atr
|
||||
|
||||
if pos!=0: close_(data[-1][4], data[-1][0], "结束")
|
||||
return trades
|
||||
|
||||
def report(trades, notional, label):
|
||||
if not trades: print(f" [{label}] No trades"); return 0,{}
|
||||
n=len(trades)
|
||||
FEE=notional*0.0006*2; REB=FEE*0.9; NFEE=FEE-REB
|
||||
total_pnl=sum(t[4] for t in trades)
|
||||
net=total_pnl-NFEE*n; total_reb=REB*n
|
||||
wins=len([t for t in trades if t[4]>0]); wr=wins/n*100
|
||||
|
||||
by_type=defaultdict(lambda:{'n':0,'pnl':0,'w':0})
|
||||
for t in trades:
|
||||
by_type[t[0]]['n']+=1; by_type[t[0]]['pnl']+=t[4]
|
||||
if t[4]>0: by_type[t[0]]['w']+=1
|
||||
|
||||
monthly=defaultdict(lambda:{'n':0,'net':0,'w':0})
|
||||
for t in trades:
|
||||
k=t[8].strftime('%Y-%m')
|
||||
monthly[k]['n']+=1; monthly[k]['net']+=t[4]-NFEE
|
||||
if t[4]>0: monthly[k]['w']+=1
|
||||
|
||||
cum=0;peak=0;dd=0
|
||||
for t in trades:
|
||||
cum+=t[4]-NFEE
|
||||
if cum>peak:peak=cum
|
||||
if peak-cum>dd:dd=peak-cum
|
||||
|
||||
pm=len([m for m in monthly.values() if m['net']>0])
|
||||
min_m=min(monthly.values(),key=lambda x:x['net'])['net']
|
||||
max_m=max(monthly.values(),key=lambda x:x['net'])['net']
|
||||
|
||||
print(f"\n{'='*75}", flush=True)
|
||||
print(f" {label} | 名义值={notional:,.0f}U", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
print(f" 年净利: {net:>+10.0f} | 月均: {net/12:>+8.0f} | 交易: {n}笔 | 胜率: {wr:.1f}%", flush=True)
|
||||
print(f" 返佣: {total_reb:>.0f} | 回撤: {dd:>.0f} | 盈利月: {pm}/12", flush=True)
|
||||
print(f" 最佳月: {max_m:>+.0f} | 最差月: {min_m:>+.0f}", flush=True)
|
||||
|
||||
print(f"\n 信号:", flush=True)
|
||||
for st in sorted(by_type.keys()):
|
||||
d=by_type[st]; nt=d['pnl']-NFEE*d['n']
|
||||
wt=d['w']/d['n']*100 if d['n']>0 else 0
|
||||
avg=nt/d['n'] if d['n']>0 else 0
|
||||
mk="+" if nt>0 else "-"
|
||||
print(f" {st:<8} {d['n']:>5}笔 净{nt:>+8.0f} 胜{wt:.0f}% 均{avg:>+.1f}/笔 {mk}", flush=True)
|
||||
|
||||
print(f"\n 月度:", flush=True)
|
||||
for m in sorted(monthly.keys()):
|
||||
d=monthly[m]; wr_m=d['w']/d['n']*100 if d['n']>0 else 0
|
||||
bar = "+" * max(0, int(d['net']/200)) + "-" * max(0, int(-d['net']/200))
|
||||
print(f" {m} {d['n']:>4}笔 {d['net']:>+8.0f} {wr_m:>4.0f}% {bar}", flush=True)
|
||||
print(f" {'合计':>7} {n:>4}笔 {net:>+8.0f}", flush=True)
|
||||
print(f"{'='*75}", flush=True)
|
||||
return net, monthly
|
||||
|
||||
def main():
|
||||
print("Loading...", flush=True)
|
||||
data = load()
|
||||
print(f"{len(data)} bars\n", flush=True)
|
||||
|
||||
# 测试不同仓位大小
|
||||
margins = [100, 200, 300, 500, 800, 1000]
|
||||
|
||||
print("="*80, flush=True)
|
||||
print(" 不同保证金下的收益 (100x杠杆)", flush=True)
|
||||
print("="*80, flush=True)
|
||||
|
||||
all_results = []
|
||||
for margin in margins:
|
||||
notional = margin * 100
|
||||
trades = run(data, notional)
|
||||
net, monthly = report(trades, notional, f"{margin}U保证金")
|
||||
all_results.append((margin, notional, len(trades), net, monthly))
|
||||
|
||||
# 总览
|
||||
print(f"\n\n{'='*80}", flush=True)
|
||||
print(f" 总览 — 目标: 每月 1000 USDT", flush=True)
|
||||
print(f"{'='*80}", flush=True)
|
||||
print(f" {'保证金':>6} {'杠杆':>4} {'名义值':>10} {'交易':>5} {'年净利':>10} {'月均':>8} {'达标':>4}", flush=True)
|
||||
print(f" {'-'*52}", flush=True)
|
||||
for margin, notional, n, net, monthly in all_results:
|
||||
mavg = net/12
|
||||
ok = "YES" if mavg>=1000 else "no"
|
||||
print(f" {margin:>5}U {100:>3}x {notional:>9,}U {n:>5} {net:>+10.0f} {mavg:>+8.0f} {ok:>4}", flush=True)
|
||||
|
||||
# 找到达标的最小保证金
|
||||
print(f"\n 结论:", flush=True)
|
||||
for margin, notional, n, net, monthly in all_results:
|
||||
if net/12 >= 1000:
|
||||
print(f" >>> {margin}U 保证金即可达到月均 {net/12:.0f} USDT <<<", flush=True)
|
||||
# 打印该配置的月度
|
||||
print(f"\n {margin}U配置月度明细:", flush=True)
|
||||
pm = 0
|
||||
for m in sorted(monthly.keys()):
|
||||
d = monthly[m]
|
||||
status = "盈" if d['net']>0 else "亏"
|
||||
print(f" {m}: {d['net']:>+8.0f} USDT ({d['n']}笔) [{status}]", flush=True)
|
||||
if d['net'] > 0: pm += 1
|
||||
print(f" 盈利月份: {pm}/12", flush=True)
|
||||
break
|
||||
else:
|
||||
# 没有达标的,计算需要多少
|
||||
base_net = all_results[0][3] # 100U的净利
|
||||
if base_net > 0:
|
||||
needed = int(12000 / base_net * 100) + 1
|
||||
print(f" 100U净利={base_net:.0f}/年 → 达标需约 {needed}U 保证金", flush=True)
|
||||
else:
|
||||
print(f" 策略本身不盈利,需要继续优化信号质量", flush=True)
|
||||
|
||||
print(f"{'='*80}", flush=True)
|
||||
|
||||
# 保存最佳配置的交易记录
|
||||
best = max(all_results, key=lambda x: x[3])
|
||||
margin, notional = best[0], best[1]
|
||||
trades = run(data, notional)
|
||||
csv = Path(__file__).parent.parent / 'final_trades.csv'
|
||||
with open(csv, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("信号,方向,开仓价,平仓价,盈亏,持仓秒,原因,开仓时间,平仓时间\n")
|
||||
for t in trades:
|
||||
f.write(f"{t[0]},{t[1]},{t[2]:.2f},{t[3]:.2f},{t[4]:.2f},{t[5]:.0f},{t[6]},{t[7]},{t[8]}\n")
|
||||
print(f"\n 交易记录: {csv}", flush=True)
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
542
交易/bitmart-返佣策略-回测.py
Normal file
542
交易/bitmart-返佣策略-回测.py
Normal file
@@ -0,0 +1,542 @@
|
||||
"""
|
||||
BitMart 布林带均值回归返佣策略 — 回测脚本
|
||||
|
||||
从 SQLite 数据库读取 2025 年全年 1 分钟 K 线数据,模拟策略运行。
|
||||
输出:交易次数、胜率、盈亏、返佣收入、净收益、最大回撤、月度统计等。
|
||||
|
||||
用法:
|
||||
python 交易/bitmart-返佣策略-回测.py
|
||||
"""
|
||||
|
||||
import time
|
||||
import datetime
|
||||
import statistics
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
# 简易 logger,避免依赖 loguru
|
||||
class _Logger:
|
||||
@staticmethod
|
||||
def info(msg): print(f"[INFO] {msg}")
|
||||
@staticmethod
|
||||
def success(msg): print(f"[OK] {msg}")
|
||||
@staticmethod
|
||||
def warning(msg): print(f"[WARN] {msg}")
|
||||
@staticmethod
|
||||
def error(msg): print(f"[ERR] {msg}")
|
||||
|
||||
logger = _Logger()
|
||||
|
||||
|
||||
# ========================= 交易记录 =========================
|
||||
|
||||
@dataclass
|
||||
class Trade:
|
||||
"""单笔交易记录"""
|
||||
open_time: datetime.datetime # 开仓时间
|
||||
close_time: datetime.datetime # 平仓时间
|
||||
direction: str # 'long' / 'short'
|
||||
open_price: float # 开仓价
|
||||
close_price: float # 平仓价
|
||||
size: float # 仓位大小 (USDT)
|
||||
pnl: float # 盈亏 (USDT)
|
||||
pnl_pct: float # 盈亏百分比
|
||||
fee: float # 手续费
|
||||
rebate: float # 返佣
|
||||
hold_seconds: float # 持仓时间(秒)
|
||||
close_reason: str # 平仓原因
|
||||
|
||||
|
||||
# ========================= 回测引擎 =========================
|
||||
|
||||
class RebateBacktest:
|
||||
def __init__(
|
||||
self,
|
||||
# 布林带参数
|
||||
bb_period: int = 20,
|
||||
bb_std: float = 2.0,
|
||||
rsi_period: int = 14,
|
||||
rsi_long_threshold: float = 35,
|
||||
rsi_short_threshold: float = 65,
|
||||
# 持仓管理
|
||||
min_hold_seconds: int = 200,
|
||||
max_hold_seconds: int = 900,
|
||||
stop_loss_pct: float = 0.003,
|
||||
hard_stop_pct: float = 0.0045,
|
||||
take_profit_pct: float = 0.0002,
|
||||
# 仓位 & 费用
|
||||
initial_balance: float = 1000.0,
|
||||
leverage: int = 50,
|
||||
risk_percent: float = 0.005,
|
||||
taker_fee_rate: float = 0.0006,
|
||||
rebate_rate: float = 0.90,
|
||||
# 时间范围
|
||||
start_date: str = '2025-01-01',
|
||||
end_date: str = '2025-12-31',
|
||||
):
|
||||
# 策略参数
|
||||
self.bb_period = bb_period
|
||||
self.bb_std = bb_std
|
||||
self.rsi_period = rsi_period
|
||||
self.rsi_long_threshold = rsi_long_threshold
|
||||
self.rsi_short_threshold = rsi_short_threshold
|
||||
|
||||
self.min_hold_seconds = min_hold_seconds
|
||||
self.max_hold_seconds = max_hold_seconds
|
||||
self.stop_loss_pct = stop_loss_pct
|
||||
self.hard_stop_pct = hard_stop_pct
|
||||
self.take_profit_pct = take_profit_pct
|
||||
|
||||
self.initial_balance = initial_balance
|
||||
self.leverage = leverage
|
||||
self.risk_percent = risk_percent
|
||||
self.taker_fee_rate = taker_fee_rate
|
||||
self.rebate_rate = rebate_rate
|
||||
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
|
||||
# 状态
|
||||
self.balance = initial_balance
|
||||
self.position = 0 # -1 空, 0 无, 1 多
|
||||
self.open_price = 0.0
|
||||
self.open_time = None # datetime
|
||||
self.position_size = 0.0 # 开仓金额 (USDT)
|
||||
|
||||
# 结果
|
||||
self.trades: List[Trade] = []
|
||||
self.equity_curve: List[dict] = [] # [{datetime, equity}]
|
||||
self.peak_equity = initial_balance
|
||||
self.max_drawdown = 0.0
|
||||
self.max_drawdown_pct = 0.0
|
||||
|
||||
# ========================= 技术指标 =========================
|
||||
|
||||
@staticmethod
|
||||
def calc_bb(closes: list, period: int, num_std: float):
|
||||
"""布林带"""
|
||||
if len(closes) < period:
|
||||
return None, None, None
|
||||
recent = closes[-period:]
|
||||
mid = statistics.mean(recent)
|
||||
std = statistics.stdev(recent)
|
||||
return mid + num_std * std, mid, mid - num_std * std
|
||||
|
||||
@staticmethod
|
||||
def calc_rsi(closes: list, period: int):
|
||||
"""RSI"""
|
||||
if len(closes) < period + 1:
|
||||
return None
|
||||
gains, losses = [], []
|
||||
for i in range(-period, 0):
|
||||
change = closes[i] - closes[i - 1]
|
||||
gains.append(max(change, 0))
|
||||
losses.append(max(-change, 0))
|
||||
avg_gain = sum(gains) / period
|
||||
avg_loss = sum(losses) / period
|
||||
if avg_loss == 0:
|
||||
return 100.0
|
||||
rs = avg_gain / avg_loss
|
||||
return 100 - (100 / (1 + rs))
|
||||
|
||||
# ========================= 数据加载 =========================
|
||||
|
||||
def load_data(self) -> list:
|
||||
"""从 SQLite 读取 1 分钟 K 线"""
|
||||
db_path = Path(__file__).parent.parent / 'models' / 'database.db'
|
||||
if not db_path.exists():
|
||||
raise FileNotFoundError(f"数据库不存在: {db_path}")
|
||||
|
||||
start_dt = datetime.datetime.strptime(self.start_date, '%Y-%m-%d')
|
||||
end_dt = datetime.datetime.strptime(self.end_date, '%Y-%m-%d') + datetime.timedelta(days=1)
|
||||
start_ms = int(start_dt.timestamp()) * 1000
|
||||
end_ms = int(end_dt.timestamp()) * 1000
|
||||
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT id, open, high, low, close FROM bitmart_eth_1m "
|
||||
"WHERE id >= ? AND id < ? ORDER BY id",
|
||||
(start_ms, end_ms)
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
ts_ms = row[0]
|
||||
ts_sec = ts_ms / 1000.0
|
||||
dt = datetime.datetime.fromtimestamp(ts_sec)
|
||||
data.append({
|
||||
'datetime': dt,
|
||||
'timestamp': ts_sec,
|
||||
'open': row[1],
|
||||
'high': row[2],
|
||||
'low': row[3],
|
||||
'close': row[4],
|
||||
})
|
||||
|
||||
logger.info(f"加载数据: {len(data)} 条 1分钟K线 ({self.start_date} ~ {self.end_date})")
|
||||
if data:
|
||||
logger.info(f" 起始: {data[0]['datetime']} | 结束: {data[-1]['datetime']}")
|
||||
return data
|
||||
|
||||
# ========================= 开平仓模拟 =========================
|
||||
|
||||
def _calc_size(self):
|
||||
"""计算开仓金额"""
|
||||
return self.balance * self.risk_percent * self.leverage
|
||||
|
||||
def _open(self, direction: str, price: float, dt: datetime.datetime):
|
||||
"""模拟开仓"""
|
||||
self.position_size = self._calc_size()
|
||||
if self.position_size < 1:
|
||||
return False
|
||||
|
||||
# 扣手续费
|
||||
fee = self.position_size * self.taker_fee_rate
|
||||
self.balance -= fee
|
||||
|
||||
self.position = 1 if direction == 'long' else -1
|
||||
self.open_price = price
|
||||
self.open_time = dt
|
||||
return True
|
||||
|
||||
def _close(self, price: float, dt: datetime.datetime, reason: str):
|
||||
"""模拟平仓,返回 Trade"""
|
||||
if self.position == 0:
|
||||
return None
|
||||
|
||||
# 计算盈亏
|
||||
if self.position == 1:
|
||||
pnl_pct = (price - self.open_price) / self.open_price
|
||||
else:
|
||||
pnl_pct = (self.open_price - price) / self.open_price
|
||||
|
||||
pnl = self.position_size * pnl_pct
|
||||
|
||||
# 平仓手续费
|
||||
close_value = self.position_size * (1 + pnl_pct)
|
||||
fee = close_value * self.taker_fee_rate
|
||||
|
||||
# 总手续费(开+平)
|
||||
open_fee = self.position_size * self.taker_fee_rate
|
||||
total_fee = open_fee + fee
|
||||
|
||||
# 返佣
|
||||
rebate = total_fee * self.rebate_rate
|
||||
|
||||
# 更新余额:加上盈亏 - 平仓手续费 + 返佣
|
||||
self.balance += pnl - fee + rebate
|
||||
|
||||
hold_seconds = (dt - self.open_time).total_seconds()
|
||||
|
||||
trade = Trade(
|
||||
open_time=self.open_time,
|
||||
close_time=dt,
|
||||
direction='long' if self.position == 1 else 'short',
|
||||
open_price=self.open_price,
|
||||
close_price=price,
|
||||
size=self.position_size,
|
||||
pnl=pnl,
|
||||
pnl_pct=pnl_pct,
|
||||
fee=total_fee,
|
||||
rebate=rebate,
|
||||
hold_seconds=hold_seconds,
|
||||
close_reason=reason,
|
||||
)
|
||||
self.trades.append(trade)
|
||||
|
||||
# 重置持仓
|
||||
self.position = 0
|
||||
self.open_price = 0.0
|
||||
self.open_time = None
|
||||
self.position_size = 0.0
|
||||
|
||||
return trade
|
||||
|
||||
# ========================= 回测主循环 =========================
|
||||
|
||||
def run(self):
|
||||
"""运行回测"""
|
||||
data = self.load_data()
|
||||
if len(data) < self.bb_period + self.rsi_period + 1:
|
||||
logger.error("数据不足,无法回测")
|
||||
return
|
||||
|
||||
closes = []
|
||||
total_bars = len(data)
|
||||
log_interval = total_bars // 20 # 打印 20 次进度
|
||||
|
||||
logger.info(f"开始回测... 共 {total_bars} 根 K 线")
|
||||
t0 = time.time()
|
||||
|
||||
for i, bar in enumerate(data):
|
||||
price = bar['close']
|
||||
dt = bar['datetime']
|
||||
closes.append(price)
|
||||
|
||||
# 需要足够数据才能计算指标
|
||||
if len(closes) < max(self.bb_period, self.rsi_period + 1) + 1:
|
||||
continue
|
||||
|
||||
# 计算指标
|
||||
upper, middle, lower = self.calc_bb(closes, self.bb_period, self.bb_std)
|
||||
rsi = self.calc_rsi(closes, self.rsi_period)
|
||||
if upper is None or rsi is None:
|
||||
continue
|
||||
|
||||
# —— 有持仓:检查平仓 ——
|
||||
if self.position != 0 and self.open_time:
|
||||
hold_sec = (dt - self.open_time).total_seconds()
|
||||
|
||||
# 计算当前浮动盈亏百分比
|
||||
if self.position == 1:
|
||||
cur_pnl_pct = (price - self.open_price) / self.open_price
|
||||
else:
|
||||
cur_pnl_pct = (self.open_price - price) / self.open_price
|
||||
|
||||
# ① 硬止损(不受持仓时间限制)
|
||||
if -cur_pnl_pct >= self.hard_stop_pct:
|
||||
self._close(price, dt, f"硬止损 ({cur_pnl_pct*100:+.3f}%)")
|
||||
continue
|
||||
|
||||
# ② 满足最低持仓时间后的平仓条件
|
||||
if hold_sec >= self.min_hold_seconds:
|
||||
# 止盈:回归中轨
|
||||
if self.position == 1 and price >= middle * (1 - self.take_profit_pct):
|
||||
self._close(price, dt, "止盈回归中轨")
|
||||
continue
|
||||
if self.position == -1 and price <= middle * (1 + self.take_profit_pct):
|
||||
self._close(price, dt, "止盈回归中轨")
|
||||
continue
|
||||
|
||||
# 止损
|
||||
if -cur_pnl_pct >= self.stop_loss_pct:
|
||||
self._close(price, dt, f"止损 ({cur_pnl_pct*100:+.3f}%)")
|
||||
continue
|
||||
|
||||
# 超时
|
||||
if hold_sec >= self.max_hold_seconds:
|
||||
self._close(price, dt, f"超时 ({hold_sec:.0f}s)")
|
||||
continue
|
||||
|
||||
# —— 无持仓:检查开仓 ——
|
||||
if self.position == 0:
|
||||
if price <= lower and rsi < self.rsi_long_threshold:
|
||||
self._open('long', price, dt)
|
||||
elif price >= upper and rsi > self.rsi_short_threshold:
|
||||
self._open('short', price, dt)
|
||||
|
||||
# 记录权益曲线(每小时记录一次)
|
||||
if i % 60 == 0:
|
||||
equity = self.balance
|
||||
if self.position != 0 and self.open_price > 0:
|
||||
if self.position == 1:
|
||||
unrealized = self.position_size * (price - self.open_price) / self.open_price
|
||||
else:
|
||||
unrealized = self.position_size * (self.open_price - price) / self.open_price
|
||||
equity += unrealized
|
||||
self.equity_curve.append({'datetime': dt, 'equity': equity})
|
||||
|
||||
# 最大回撤
|
||||
if equity > self.peak_equity:
|
||||
self.peak_equity = equity
|
||||
dd = (self.peak_equity - equity) / self.peak_equity
|
||||
if dd > self.max_drawdown_pct:
|
||||
self.max_drawdown_pct = dd
|
||||
self.max_drawdown = self.peak_equity - equity
|
||||
|
||||
# 进度
|
||||
if log_interval > 0 and i % log_interval == 0 and i > 0:
|
||||
pct = i / total_bars * 100
|
||||
logger.info(f" 进度 {pct:.0f}% | 余额 {self.balance:.2f} | 交易 {len(self.trades)} 笔")
|
||||
|
||||
elapsed = time.time() - t0
|
||||
logger.info(f"回测完成,耗时 {elapsed:.1f}s")
|
||||
|
||||
# 如果还有持仓,强制平仓
|
||||
if self.position != 0:
|
||||
last_bar = data[-1]
|
||||
self._close(last_bar['close'], last_bar['datetime'], "回测结束强制平仓")
|
||||
|
||||
self.print_results()
|
||||
|
||||
# ========================= 结果输出 =========================
|
||||
|
||||
def print_results(self):
|
||||
"""打印详细回测结果"""
|
||||
trades = self.trades
|
||||
if not trades:
|
||||
logger.warning("无交易记录")
|
||||
return
|
||||
|
||||
# 基础统计
|
||||
total_trades = len(trades)
|
||||
wins = [t for t in trades if t.pnl > 0]
|
||||
losses = [t for t in trades if t.pnl <= 0]
|
||||
win_rate = len(wins) / total_trades * 100
|
||||
|
||||
total_pnl = sum(t.pnl for t in trades)
|
||||
total_fee = sum(t.fee for t in trades)
|
||||
total_rebate = sum(t.rebate for t in trades)
|
||||
net_profit = self.balance - self.initial_balance
|
||||
|
||||
avg_pnl = total_pnl / total_trades
|
||||
avg_win = statistics.mean([t.pnl for t in wins]) if wins else 0
|
||||
avg_loss = statistics.mean([t.pnl for t in losses]) if losses else 0
|
||||
|
||||
avg_hold = statistics.mean([t.hold_seconds for t in trades])
|
||||
total_volume = sum(t.size for t in trades) * 2 # 开+平
|
||||
|
||||
# 盈亏比
|
||||
profit_factor = (sum(t.pnl for t in wins) / abs(sum(t.pnl for t in losses))) if losses and sum(t.pnl for t in losses) != 0 else float('inf')
|
||||
|
||||
# 连续亏损
|
||||
max_consecutive_loss = 0
|
||||
current_loss_streak = 0
|
||||
for t in trades:
|
||||
if t.pnl <= 0:
|
||||
current_loss_streak += 1
|
||||
max_consecutive_loss = max(max_consecutive_loss, current_loss_streak)
|
||||
else:
|
||||
current_loss_streak = 0
|
||||
|
||||
# 长/空统计
|
||||
long_trades = [t for t in trades if t.direction == 'long']
|
||||
short_trades = [t for t in trades if t.direction == 'short']
|
||||
long_wins = len([t for t in long_trades if t.pnl > 0])
|
||||
short_wins = len([t for t in short_trades if t.pnl > 0])
|
||||
|
||||
# 平仓原因统计
|
||||
close_reasons = {}
|
||||
for t in trades:
|
||||
r = t.close_reason.split(' (')[0] # 去掉括号部分
|
||||
close_reasons[r] = close_reasons.get(r, 0) + 1
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f" 布林带均值回归返佣策略 — 回测报告")
|
||||
print(f" 回测区间: {self.start_date} ~ {self.end_date}")
|
||||
print("=" * 70)
|
||||
|
||||
print(f"\n{'─'*35} 账户 {'─'*35}")
|
||||
print(f" 初始资金: {self.initial_balance:>12.2f} USDT")
|
||||
print(f" 最终余额: {self.balance:>12.2f} USDT")
|
||||
print(f" 净收益: {net_profit:>+12.2f} USDT ({net_profit/self.initial_balance*100:+.2f}%)")
|
||||
print(f" 最大回撤: {self.max_drawdown:>12.2f} USDT ({self.max_drawdown_pct*100:.2f}%)")
|
||||
|
||||
print(f"\n{'─'*35} 交易 {'─'*35}")
|
||||
print(f" 总交易次数: {total_trades:>8}")
|
||||
print(f" 盈利次数: {len(wins):>8} ({win_rate:.1f}%)")
|
||||
print(f" 亏损次数: {len(losses):>8} ({100-win_rate:.1f}%)")
|
||||
print(f" 做多交易: {len(long_trades):>8} (胜率 {long_wins/len(long_trades)*100:.1f}%)" if long_trades else "")
|
||||
print(f" 做空交易: {len(short_trades):>8} (胜率 {short_wins/len(short_trades)*100:.1f}%)" if short_trades else "")
|
||||
print(f" 盈亏比: {profit_factor:>8.2f}")
|
||||
print(f" 最大连续亏损: {max_consecutive_loss:>8} 笔")
|
||||
|
||||
print(f"\n{'─'*35} 盈亏 {'─'*35}")
|
||||
print(f" 交易总盈亏: {total_pnl:>+12.4f} USDT")
|
||||
print(f" 平均每笔盈亏: {avg_pnl:>+12.4f} USDT")
|
||||
print(f" 平均盈利: {avg_win:>+12.4f} USDT")
|
||||
print(f" 平均亏损: {avg_loss:>+12.4f} USDT")
|
||||
print(f" 最大单笔盈利: {max(t.pnl for t in trades):>+12.4f} USDT")
|
||||
print(f" 最大单笔亏损: {min(t.pnl for t in trades):>+12.4f} USDT")
|
||||
|
||||
print(f"\n{'─'*35} 手续费 & 返佣 {'─'*28}")
|
||||
print(f" 交易总额: {total_volume:>12.2f} USDT")
|
||||
print(f" 总手续费: {total_fee:>12.4f} USDT")
|
||||
print(f" 总返佣 (90%): {total_rebate:>+12.4f} USDT")
|
||||
print(f" 净手续费成本: {total_fee - total_rebate:>12.4f} USDT")
|
||||
print(f" 返佣占净收益: {total_rebate/net_profit*100:.1f}%" if net_profit != 0 else " 返佣占净收益: N/A")
|
||||
|
||||
print(f"\n{'─'*35} 持仓 {'─'*35}")
|
||||
print(f" 平均持仓时间: {avg_hold:>8.0f} 秒 ({avg_hold/60:.1f} 分钟)")
|
||||
print(f" 最短持仓: {min(t.hold_seconds for t in trades):>8.0f} 秒")
|
||||
print(f" 最长持仓: {max(t.hold_seconds for t in trades):>8.0f} 秒")
|
||||
# 持仓<3分钟的交易数(应该只有硬止损的)
|
||||
under_3min = len([t for t in trades if t.hold_seconds < 180])
|
||||
print(f" 持仓<3分钟: {under_3min:>8} 笔 (仅硬止损触发)")
|
||||
|
||||
print(f"\n{'─'*35} 平仓原因 {'─'*31}")
|
||||
for reason, count in sorted(close_reasons.items(), key=lambda x: -x[1]):
|
||||
print(f" {reason:<20} {count:>6} 笔 ({count/total_trades*100:.1f}%)")
|
||||
|
||||
# ========================= 月度统计 =========================
|
||||
print(f"\n{'─'*35} 月度统计 {'─'*31}")
|
||||
print(f" {'月份':<10} {'交易数':>6} {'盈利':>10} {'返佣':>10} {'净收益':>10} {'胜率':>6}")
|
||||
print(f" {'─'*56}")
|
||||
|
||||
monthly = {}
|
||||
for t in trades:
|
||||
key = t.close_time.strftime('%Y-%m')
|
||||
if key not in monthly:
|
||||
monthly[key] = {'trades': 0, 'pnl': 0, 'rebate': 0, 'wins': 0}
|
||||
monthly[key]['trades'] += 1
|
||||
monthly[key]['pnl'] += t.pnl
|
||||
monthly[key]['rebate'] += t.rebate
|
||||
if t.pnl > 0:
|
||||
monthly[key]['wins'] += 1
|
||||
|
||||
for month in sorted(monthly.keys()):
|
||||
m = monthly[month]
|
||||
net = m['pnl'] + m['rebate'] - (sum(t.fee for t in trades if t.close_time.strftime('%Y-%m') == month) * (1 - self.rebate_rate))
|
||||
wr = m['wins'] / m['trades'] * 100 if m['trades'] > 0 else 0
|
||||
print(f" {month:<10} {m['trades']:>6} {m['pnl']:>+10.2f} {m['rebate']:>10.2f} {m['pnl']+m['rebate']:>+10.2f} {wr:>5.1f}%")
|
||||
|
||||
print("=" * 70)
|
||||
|
||||
# ========================= 保存交易记录到 CSV =========================
|
||||
csv_path = Path(__file__).parent.parent / '回测结果.csv'
|
||||
try:
|
||||
with open(csv_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("开仓时间,平仓时间,方向,开仓价,平仓价,仓位(USDT),盈亏(USDT),盈亏%,手续费,返佣,持仓秒数,平仓原因\n")
|
||||
for t in trades:
|
||||
f.write(
|
||||
f"{t.open_time},{t.close_time},{t.direction},"
|
||||
f"{t.open_price:.2f},{t.close_price:.2f},{t.size:.2f},"
|
||||
f"{t.pnl:.4f},{t.pnl_pct*100:.4f}%,{t.fee:.4f},{t.rebate:.4f},"
|
||||
f"{t.hold_seconds:.0f},{t.close_reason}\n"
|
||||
)
|
||||
logger.info(f"交易记录已保存到: {csv_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存 CSV 失败: {e}")
|
||||
|
||||
# ========================= 保存权益曲线 =========================
|
||||
equity_path = Path(__file__).parent.parent / '权益曲线.csv'
|
||||
try:
|
||||
with open(equity_path, 'w', encoding='utf-8-sig') as f:
|
||||
f.write("时间,权益\n")
|
||||
for e in self.equity_curve:
|
||||
f.write(f"{e['datetime']},{e['equity']:.2f}\n")
|
||||
logger.info(f"权益曲线已保存到: {equity_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"保存权益曲线失败: {e}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bt = RebateBacktest(
|
||||
# 布林带参数
|
||||
bb_period=20,
|
||||
bb_std=2.0,
|
||||
rsi_period=14,
|
||||
rsi_long_threshold=35,
|
||||
rsi_short_threshold=65,
|
||||
# 持仓管理
|
||||
min_hold_seconds=200, # >3分钟
|
||||
max_hold_seconds=900, # 15分钟
|
||||
stop_loss_pct=0.003, # 0.3% 止损
|
||||
hard_stop_pct=0.0045, # 0.45% 硬止损
|
||||
take_profit_pct=0.0002, # 中轨容差
|
||||
# 仓位 & 费用
|
||||
initial_balance=1000.0, # 初始 1000 USDT
|
||||
leverage=50,
|
||||
risk_percent=0.005, # 0.5%
|
||||
taker_fee_rate=0.0006, # 0.06%
|
||||
rebate_rate=0.90, # 90% 返佣
|
||||
# 时间范围
|
||||
start_date='2025-01-01',
|
||||
end_date='2025-12-31',
|
||||
)
|
||||
bt.run()
|
||||
763
交易/bitmart-返佣策略.py
Normal file
763
交易/bitmart-返佣策略.py
Normal file
@@ -0,0 +1,763 @@
|
||||
"""
|
||||
BitMart EMA趋势跟随返佣策略
|
||||
|
||||
核心思路:
|
||||
你有交易所 90% 手续费返佣,因此每笔交易的"真实成本"极低(0.012%/round trip)。
|
||||
策略使用 EMA 金叉/死叉 捕捉 1 分钟级别的微趋势,配合大级别 EMA 趋势过滤
|
||||
和 ATR 波动率过滤,只在高波动顺势环境中开仓。
|
||||
持仓必须 >= 3 分钟,避免被判定为刷量。
|
||||
|
||||
回测表现(2025全年 1 分钟 K 线):
|
||||
- 参数:EMA(8/21/120) ATR>0.3% SL=0.4% MaxHold=1800s
|
||||
- Risk=2% → 年化 +10.11%,最大回撤 10.92%
|
||||
- Risk=3% → 年化 +13.66%,最大回撤 15.96%
|
||||
- 全年约 227 笔交易,平均 1.6 天 1 笔
|
||||
- 胜率 ~29%,盈亏比 ~2.7:1(低胜率高赔率模式)
|
||||
|
||||
策略规则:
|
||||
1. 使用 1 分钟 K 线计算 EMA(8)快线、EMA(21)慢线、EMA(120)大趋势线
|
||||
2. 计算 ATR(14) 波动率,仅在 ATR > 0.3% 时交易(过滤低波动区间)
|
||||
3. 开仓信号:
|
||||
- 做多:EMA(8) 上穿 EMA(21) 且 价格 > EMA(120)(顺势金叉)
|
||||
- 做空:EMA(8) 下穿 EMA(21) 且 价格 < EMA(120)(顺势死叉)
|
||||
4. 平仓条件:
|
||||
a) 反向交叉信号(满足最低持仓后,可同时反手开仓)
|
||||
b) 止损:浮亏 >= 0.4%(硬止损 0.6%,不受持仓时间限制)
|
||||
c) 超时:持仓 >= 30 分钟强制平仓
|
||||
5. 平仓后若有反向信号且满足 ATR 过滤,立即反手开仓
|
||||
"""
|
||||
|
||||
import time
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from bitmart.api_contract import APIContract
|
||||
from bitmart.lib.cloud_exceptions import APIException
|
||||
|
||||
from 交易.tools import send_dingtalk_message
|
||||
|
||||
|
||||
# ========================= EMA 计算器 =========================
|
||||
class EMACalculator:
|
||||
"""指数移动平均线,增量式更新"""
|
||||
def __init__(self, period: int):
|
||||
self.period = period
|
||||
self.k = 2.0 / (period + 1)
|
||||
self.value = None
|
||||
|
||||
def update(self, price: float) -> float:
|
||||
if self.value is None:
|
||||
self.value = price
|
||||
else:
|
||||
self.value = price * self.k + self.value * (1 - self.k)
|
||||
return self.value
|
||||
|
||||
def reset(self):
|
||||
self.value = None
|
||||
|
||||
|
||||
class BitmartRebateStrategy:
|
||||
def __init__(self):
|
||||
self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8"
|
||||
self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5"
|
||||
self.memo = "合约交易"
|
||||
|
||||
self.contract_symbol = "ETHUSDT"
|
||||
self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15))
|
||||
|
||||
# ========================= 持仓状态 =========================
|
||||
self.start = 0 # -1 空, 0 无, 1 多
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.position_cross = None
|
||||
self.open_time = None # 开仓时间戳(用于计算持仓时长)
|
||||
|
||||
# ========================= 杠杆 & 仓位 =========================
|
||||
self.leverage = "50" # 杠杆倍数
|
||||
self.open_type = "cross" # 全仓模式
|
||||
self.risk_percent = 0.02 # 每次开仓使用可用余额的 2%(回测最优)
|
||||
|
||||
# ========================= EMA 参数(回测最优) =========================
|
||||
self.ema_fast_period = 8 # 快线 EMA 周期
|
||||
self.ema_slow_period = 21 # 慢线 EMA 周期
|
||||
self.ema_big_period = 120 # 大趋势 EMA 周期(2小时)
|
||||
self.atr_period = 14 # ATR 周期
|
||||
self.atr_min_pct = 0.003 # ATR 最低阈值 0.3%(过滤低波动)
|
||||
|
||||
# ========================= 持仓管理参数(回测最优) =========================
|
||||
self.min_hold_seconds = 200 # 最低持仓时间(秒),>3分钟
|
||||
self.max_hold_seconds = 1800 # 最长持仓时间(秒),30分钟
|
||||
self.stop_loss_pct = 0.004 # 止损百分比 0.4%
|
||||
self.hard_stop_pct = 0.006 # 硬止损 0.6%(不受时间限制)
|
||||
|
||||
# ========================= EMA 状态 =========================
|
||||
self.ema_fast = EMACalculator(self.ema_fast_period)
|
||||
self.ema_slow = EMACalculator(self.ema_slow_period)
|
||||
self.ema_big = EMACalculator(self.ema_big_period)
|
||||
self.prev_fast = None # 上一根K线的快线值
|
||||
self.prev_slow = None # 上一根K线的慢线值
|
||||
self.pending_signal = None # 等待最低持仓后执行的延迟信号
|
||||
|
||||
# ========================= K线缓存(用于 ATR 计算) =========================
|
||||
self.highs = []
|
||||
self.lows = []
|
||||
self.closes = []
|
||||
self.last_kline_time = None # 最新处理过的K线时间戳
|
||||
|
||||
# ========================= 运行控制 =========================
|
||||
self.check_interval = 5 # 主循环检测间隔(秒)
|
||||
|
||||
# ========================= 统计 =========================
|
||||
self.trade_count = 0 # 当日交易次数
|
||||
self.total_pnl = 0.0 # 当日累计盈亏
|
||||
self.total_volume = 0.0 # 当日累计交易额(用于估算返佣)
|
||||
self.start_time = time.time() # 程序启动时间
|
||||
|
||||
# ========================= 技术指标计算 =========================
|
||||
|
||||
def calculate_atr_pct(self, current_price):
|
||||
"""计算 ATR 占当前价格的百分比"""
|
||||
if len(self.highs) < self.atr_period + 1:
|
||||
return 0.0
|
||||
|
||||
trs = []
|
||||
for i in range(-self.atr_period, 0):
|
||||
h = self.highs[i]
|
||||
l = self.lows[i]
|
||||
pc = self.closes[i - 1]
|
||||
tr = max(h - l, abs(h - pc), abs(l - pc))
|
||||
trs.append(tr)
|
||||
|
||||
atr = sum(trs) / self.atr_period
|
||||
return atr / current_price if current_price > 0 else 0.0
|
||||
|
||||
def detect_cross(self, fast_val, slow_val):
|
||||
"""
|
||||
检测 EMA 金叉/死叉
|
||||
返回: 'golden' (金叉) / 'death' (死叉) / None
|
||||
"""
|
||||
if self.prev_fast is None or self.prev_slow is None:
|
||||
return None
|
||||
|
||||
# 金叉:快线从下方穿越慢线
|
||||
if self.prev_fast <= self.prev_slow and fast_val > slow_val:
|
||||
return "golden"
|
||||
|
||||
# 死叉:快线从上方穿越慢线
|
||||
if self.prev_fast >= self.prev_slow and fast_val < slow_val:
|
||||
return "death"
|
||||
|
||||
return None
|
||||
|
||||
def process_new_kline(self, kline):
|
||||
"""
|
||||
处理新的 1 分钟 K 线,更新所有指标
|
||||
返回: (cross_signal, atr_pct, fast, slow, big)
|
||||
"""
|
||||
close = kline['close']
|
||||
high = kline['high']
|
||||
low = kline['low']
|
||||
|
||||
# 缓存 K 线数据
|
||||
self.highs.append(high)
|
||||
self.lows.append(low)
|
||||
self.closes.append(close)
|
||||
|
||||
# 只保留最近 200 根(节省内存)
|
||||
if len(self.highs) > 200:
|
||||
self.highs = self.highs[-200:]
|
||||
self.lows = self.lows[-200:]
|
||||
self.closes = self.closes[-200:]
|
||||
|
||||
# 更新 EMA
|
||||
fast_val = self.ema_fast.update(close)
|
||||
slow_val = self.ema_slow.update(close)
|
||||
big_val = self.ema_big.update(close)
|
||||
|
||||
# 检测交叉
|
||||
cross = self.detect_cross(fast_val, slow_val)
|
||||
|
||||
# 保存当前值供下次对比
|
||||
self.prev_fast = fast_val
|
||||
self.prev_slow = slow_val
|
||||
|
||||
# 计算 ATR
|
||||
atr_pct = self.calculate_atr_pct(close)
|
||||
|
||||
return cross, atr_pct, fast_val, slow_val, big_val
|
||||
|
||||
# ========================= 数据获取 =========================
|
||||
|
||||
def get_1min_klines(self, count=150):
|
||||
"""获取最近 N 根 1 分钟 K 线"""
|
||||
try:
|
||||
end_time = int(time.time())
|
||||
start_time = end_time - 60 * count * 2 # 多取一些保证够用
|
||||
response = self.contractAPI.get_kline(
|
||||
contract_symbol=self.contract_symbol,
|
||||
step=1, # 1 分钟
|
||||
start_time=start_time,
|
||||
end_time=end_time
|
||||
)[0]
|
||||
|
||||
if response['code'] != 1000:
|
||||
logger.error(f"获取K线失败: {response}")
|
||||
return None
|
||||
|
||||
formatted = []
|
||||
for k in response['data']:
|
||||
formatted.append({
|
||||
'timestamp': int(k["timestamp"]),
|
||||
'open': float(k["open_price"]),
|
||||
'high': float(k["high_price"]),
|
||||
'low': float(k["low_price"]),
|
||||
'close': float(k["close_price"]),
|
||||
})
|
||||
formatted.sort(key=lambda x: x['timestamp'])
|
||||
|
||||
# 只保留最近 count 根
|
||||
return formatted[-count:]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取1分钟K线异常: {e}")
|
||||
return None
|
||||
|
||||
def get_current_price(self):
|
||||
"""获取当前最新价格"""
|
||||
try:
|
||||
end_time = int(time.time())
|
||||
response = self.contractAPI.get_kline(
|
||||
contract_symbol=self.contract_symbol,
|
||||
step=1,
|
||||
start_time=end_time - 300,
|
||||
end_time=end_time
|
||||
)[0]
|
||||
if response['code'] == 1000 and response['data']:
|
||||
return float(response['data'][-1]["close_price"])
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"获取价格异常: {e}")
|
||||
return None
|
||||
|
||||
def get_available_balance(self):
|
||||
"""获取合约账户可用 USDT 余额"""
|
||||
try:
|
||||
response = self.contractAPI.get_assets_detail()[0]
|
||||
if response['code'] == 1000:
|
||||
data = response['data']
|
||||
if isinstance(data, dict):
|
||||
return float(data.get('available_balance', 0))
|
||||
elif isinstance(data, list):
|
||||
for asset in data:
|
||||
if asset.get('currency') == 'USDT':
|
||||
return float(asset.get('available_balance', 0))
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"余额查询异常: {e}")
|
||||
return None
|
||||
|
||||
def get_position_status(self):
|
||||
"""获取当前持仓状态"""
|
||||
try:
|
||||
response = self.contractAPI.get_position(contract_symbol=self.contract_symbol)[0]
|
||||
if response['code'] == 1000:
|
||||
positions = response['data']
|
||||
if not positions:
|
||||
self.start = 0
|
||||
self.open_avg_price = None
|
||||
self.current_amount = None
|
||||
self.position_cross = None
|
||||
return True
|
||||
self.start = 1 if positions[0]['position_type'] == 1 else -1
|
||||
self.open_avg_price = float(positions[0]['open_avg_price'])
|
||||
self.current_amount = float(positions[0]['current_amount'])
|
||||
self.position_cross = positions[0].get("position_cross")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"持仓查询异常: {e}")
|
||||
return False
|
||||
|
||||
# ========================= 交易执行 =========================
|
||||
|
||||
def set_leverage(self):
|
||||
"""设置全仓 + 杠杆"""
|
||||
try:
|
||||
response = self.contractAPI.post_submit_leverage(
|
||||
contract_symbol=self.contract_symbol,
|
||||
leverage=self.leverage,
|
||||
open_type=self.open_type
|
||||
)[0]
|
||||
if response['code'] == 1000:
|
||||
logger.success(f"全仓模式 + {self.leverage}x 杠杆设置成功")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"杠杆设置失败: {response}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"设置杠杆异常: {e}")
|
||||
return False
|
||||
|
||||
def calculate_size(self, price):
|
||||
"""计算开仓张数"""
|
||||
balance = self.get_available_balance()
|
||||
if not balance or balance < 10:
|
||||
logger.warning(f"余额不足: {balance}")
|
||||
return 0
|
||||
|
||||
leverage = int(self.leverage)
|
||||
margin = balance * self.risk_percent
|
||||
# ETHUSDT 1张 ≈ 0.001 ETH
|
||||
size = int((margin * leverage) / (price * 0.001))
|
||||
size = max(1, size)
|
||||
|
||||
logger.info(f"余额 {balance:.2f} USDT → 保证金 {margin:.2f} USDT → 开仓 {size} 张 (价格≈{price:.2f})")
|
||||
return size
|
||||
|
||||
def place_market_order(self, side: int, size: int):
|
||||
"""
|
||||
下市价单
|
||||
side: 1=开多, 2=平空, 3=平多, 4=开空
|
||||
"""
|
||||
if size <= 0:
|
||||
return False
|
||||
|
||||
client_order_id = f"rebate_{int(time.time())}_{uuid.uuid4().hex[:8]}"
|
||||
side_names = {1: "开多", 2: "平空", 3: "平多", 4: "开空"}
|
||||
|
||||
try:
|
||||
response = self.contractAPI.post_submit_order(
|
||||
contract_symbol=self.contract_symbol,
|
||||
client_order_id=client_order_id,
|
||||
side=side,
|
||||
mode=1,
|
||||
type='market',
|
||||
leverage=self.leverage,
|
||||
open_type=self.open_type,
|
||||
size=size
|
||||
)[0]
|
||||
|
||||
if response['code'] == 1000:
|
||||
logger.success(f"下单成功: {side_names.get(side, side)} {size} 张")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"下单失败: {response}")
|
||||
return False
|
||||
except APIException as e:
|
||||
logger.error(f"API下单异常: {e}")
|
||||
return False
|
||||
|
||||
def open_position(self, direction: str, price: float):
|
||||
"""开仓"""
|
||||
size = self.calculate_size(price)
|
||||
if size == 0:
|
||||
return False
|
||||
|
||||
if direction == "long":
|
||||
if self.place_market_order(1, size):
|
||||
self.start = 1
|
||||
self.open_avg_price = price
|
||||
self.open_time = time.time()
|
||||
self.current_amount = size
|
||||
self.pending_signal = None
|
||||
|
||||
# 统计交易额
|
||||
volume = size * 0.001 * price
|
||||
self.total_volume += volume
|
||||
self.trade_count += 1
|
||||
|
||||
logger.success(f"开多 {size} 张 @ {price:.2f}")
|
||||
self.ding(f"开多 {size} 张 @ {price:.2f}")
|
||||
return True
|
||||
|
||||
elif direction == "short":
|
||||
if self.place_market_order(4, size):
|
||||
self.start = -1
|
||||
self.open_avg_price = price
|
||||
self.open_time = time.time()
|
||||
self.current_amount = size
|
||||
self.pending_signal = None
|
||||
|
||||
volume = size * 0.001 * price
|
||||
self.total_volume += volume
|
||||
self.trade_count += 1
|
||||
|
||||
logger.success(f"开空 {size} 张 @ {price:.2f}")
|
||||
self.ding(f"开空 {size} 张 @ {price:.2f}")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def close_position(self, reason: str, current_price: float):
|
||||
"""平仓"""
|
||||
if self.start == 0:
|
||||
return False
|
||||
|
||||
close_side = 3 if self.start == 1 else 2 # 3=平多, 2=平空
|
||||
direction_str = "多" if self.start == 1 else "空"
|
||||
|
||||
if self.place_market_order(close_side, 999999):
|
||||
# 计算本次盈亏
|
||||
if self.open_avg_price and self.current_amount:
|
||||
if self.start == 1:
|
||||
pnl = self.current_amount * 0.001 * (current_price - self.open_avg_price)
|
||||
else:
|
||||
pnl = self.current_amount * 0.001 * (self.open_avg_price - current_price)
|
||||
self.total_pnl += pnl
|
||||
|
||||
# 统计平仓交易额
|
||||
volume = self.current_amount * 0.001 * current_price
|
||||
self.total_volume += volume
|
||||
|
||||
hold_seconds = time.time() - self.open_time if self.open_time else 0
|
||||
logger.success(
|
||||
f"平{direction_str} @ {current_price:.2f} | "
|
||||
f"原因: {reason} | 持仓 {hold_seconds:.0f}s | "
|
||||
f"本次盈亏: {pnl:+.4f} USDT"
|
||||
)
|
||||
self.ding(
|
||||
f"平{direction_str} @ {current_price:.2f}\n"
|
||||
f"原因: {reason}\n持仓 {hold_seconds:.0f}s\n"
|
||||
f"本次盈亏: {pnl:+.4f} USDT"
|
||||
)
|
||||
|
||||
self.start = 0
|
||||
self.open_avg_price = None
|
||||
self.open_time = None
|
||||
self.current_amount = None
|
||||
self.pending_signal = None
|
||||
self.trade_count += 1
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# ========================= 信号检测 =========================
|
||||
|
||||
def check_open_signal(self, current_price, cross, atr_pct, big_val):
|
||||
"""
|
||||
检查开仓信号
|
||||
返回: 'long' / 'short' / None
|
||||
"""
|
||||
# ATR 过滤:波动率不足时不开仓
|
||||
if atr_pct < self.atr_min_pct:
|
||||
return None
|
||||
|
||||
# 金叉 + 价格在大EMA上方 → 做多
|
||||
if cross == "golden" and current_price > big_val:
|
||||
logger.info(
|
||||
f"做多信号 | 金叉 + 价格 {current_price:.2f} > EMA120 {big_val:.2f} | ATR {atr_pct*100:.3f}%"
|
||||
)
|
||||
return "long"
|
||||
|
||||
# 死叉 + 价格在大EMA下方 → 做空
|
||||
if cross == "death" and current_price < big_val:
|
||||
logger.info(
|
||||
f"做空信号 | 死叉 + 价格 {current_price:.2f} < EMA120 {big_val:.2f} | ATR {atr_pct*100:.3f}%"
|
||||
)
|
||||
return "short"
|
||||
|
||||
return None
|
||||
|
||||
def check_close_signal(self, current_price, cross, atr_pct, fast_val, slow_val, big_val):
|
||||
"""
|
||||
检查平仓信号
|
||||
返回: (should_close: bool, reason: str, reverse_direction: str or None)
|
||||
reverse_direction: 平仓后是否反手 ('long'/'short'/None)
|
||||
"""
|
||||
if self.start == 0 or not self.open_avg_price or not self.open_time:
|
||||
return False, "", None
|
||||
|
||||
hold_seconds = time.time() - self.open_time
|
||||
|
||||
# 计算浮动盈亏
|
||||
if self.start == 1:
|
||||
loss_pct = (self.open_avg_price - current_price) / self.open_avg_price
|
||||
else:
|
||||
loss_pct = (current_price - self.open_avg_price) / self.open_avg_price
|
||||
|
||||
# ① 硬止损:不受持仓时间限制
|
||||
if loss_pct >= self.hard_stop_pct:
|
||||
return True, f"硬止损 (亏损 {loss_pct*100:.3f}%)", None
|
||||
|
||||
# ② 未满足最低持仓时间
|
||||
if hold_seconds < self.min_hold_seconds:
|
||||
# 记录延迟信号(等持仓时间到了再处理)
|
||||
if self.start == 1 and cross == "death":
|
||||
self.pending_signal = "close_long"
|
||||
logger.info(f"检测到死叉但持仓时间不足,记录延迟信号 | 还需 {self.min_hold_seconds - hold_seconds:.0f}s")
|
||||
elif self.start == -1 and cross == "golden":
|
||||
self.pending_signal = "close_short"
|
||||
logger.info(f"检测到金叉但持仓时间不足,记录延迟信号 | 还需 {self.min_hold_seconds - hold_seconds:.0f}s")
|
||||
|
||||
remaining = self.min_hold_seconds - hold_seconds
|
||||
if int(hold_seconds) % 30 == 0:
|
||||
logger.info(f"持仓中... 还需等待 {remaining:.0f}s")
|
||||
return False, "", None
|
||||
|
||||
# ③ 满足持仓时间后的平仓检查
|
||||
reverse = None
|
||||
|
||||
# 止损
|
||||
if loss_pct >= self.stop_loss_pct:
|
||||
return True, f"止损 (亏损 {loss_pct*100:.3f}%)", None
|
||||
|
||||
# 超时
|
||||
if hold_seconds >= self.max_hold_seconds:
|
||||
return True, f"超时平仓 (持仓 {hold_seconds:.0f}s)", None
|
||||
|
||||
# 反向交叉 → 平仓 + 可能反手
|
||||
if self.start == 1 and cross == "death":
|
||||
# 判断是否满足反手条件
|
||||
if current_price < big_val and atr_pct >= self.atr_min_pct:
|
||||
reverse = "short"
|
||||
return True, "EMA死叉反转", reverse
|
||||
|
||||
if self.start == -1 and cross == "golden":
|
||||
if current_price > big_val and atr_pct >= self.atr_min_pct:
|
||||
reverse = "long"
|
||||
return True, "EMA金叉反转", reverse
|
||||
|
||||
# 处理延迟信号(之前因持仓时间不足未执行)
|
||||
if self.pending_signal == "close_long" and self.start == 1:
|
||||
if fast_val < slow_val and current_price < big_val and atr_pct >= self.atr_min_pct:
|
||||
reverse = "short"
|
||||
self.pending_signal = None
|
||||
return True, "延迟死叉平仓", reverse
|
||||
|
||||
if self.pending_signal == "close_short" and self.start == -1:
|
||||
if fast_val > slow_val and current_price > big_val and atr_pct >= self.atr_min_pct:
|
||||
reverse = "long"
|
||||
self.pending_signal = None
|
||||
return True, "延迟金叉平仓", reverse
|
||||
|
||||
return False, "", None
|
||||
|
||||
# ========================= 通知 =========================
|
||||
|
||||
def ding(self, msg, error=False):
|
||||
"""发送通知"""
|
||||
prefix = "返佣策略(ERR): " if error else "返佣策略: "
|
||||
try:
|
||||
if error:
|
||||
for _ in range(3):
|
||||
send_dingtalk_message(f"{prefix}{msg}")
|
||||
else:
|
||||
send_dingtalk_message(f"{prefix}{msg}")
|
||||
except Exception as e:
|
||||
logger.warning(f"通知发送失败: {e}")
|
||||
|
||||
def print_daily_stats(self):
|
||||
"""打印当日统计"""
|
||||
elapsed = time.time() - self.start_time
|
||||
hours = elapsed / 3600
|
||||
|
||||
# 预估返佣(90% 的手续费)
|
||||
fee_rate = 0.0006 # taker 0.06%
|
||||
total_fee = self.total_volume * fee_rate
|
||||
rebate = total_fee * 0.9 # 90% 返佣
|
||||
|
||||
now = datetime.datetime.now().strftime("%H:%M:%S")
|
||||
stats = (
|
||||
f"\n{'='*50}\n"
|
||||
f"[{now}] EMA趋势返佣策略统计\n"
|
||||
f"运行时长: {hours:.1f} 小时\n"
|
||||
f"交易次数: {self.trade_count} 次\n"
|
||||
f"交易总额: {self.total_volume:.2f} USDT\n"
|
||||
f"交易盈亏: {self.total_pnl:+.4f} USDT\n"
|
||||
f"预估手续费: {total_fee:.4f} USDT\n"
|
||||
f"预估返佣收入: {rebate:.4f} USDT\n"
|
||||
f"预估净收益: {self.total_pnl + rebate:.4f} USDT\n"
|
||||
f"{'='*50}"
|
||||
)
|
||||
logger.info(stats)
|
||||
|
||||
# ========================= 初始化指标 =========================
|
||||
|
||||
def init_indicators(self):
|
||||
"""用历史K线初始化 EMA 和 ATR,避免冷启动"""
|
||||
logger.info("正在加载历史K线初始化指标...")
|
||||
klines = self.get_1min_klines(count=150)
|
||||
if not klines or len(klines) < self.ema_big_period:
|
||||
logger.warning(f"历史K线不足 {self.ema_big_period} 根,指标将在运行中逐步初始化")
|
||||
return False
|
||||
|
||||
# 除最后一根(当前未完成的K线)外,全部用于初始化
|
||||
for kline in klines[:-1]:
|
||||
self.process_new_kline(kline)
|
||||
self.last_kline_time = kline['timestamp']
|
||||
|
||||
logger.info(
|
||||
f"指标初始化完成 | {len(klines)-1} 根K线 | "
|
||||
f"EMA8={self.ema_fast.value:.2f} EMA21={self.ema_slow.value:.2f} "
|
||||
f"EMA120={self.ema_big.value:.2f}"
|
||||
)
|
||||
return True
|
||||
|
||||
# ========================= 主循环 =========================
|
||||
|
||||
def action(self):
|
||||
"""主循环"""
|
||||
# 设置杠杆
|
||||
if not self.set_leverage():
|
||||
logger.error("杠杆设置失败,退出")
|
||||
self.ding("杠杆设置失败", error=True)
|
||||
return
|
||||
|
||||
# 启动时获取持仓状态
|
||||
if not self.get_position_status():
|
||||
logger.warning("初始持仓状态获取失败,假设无仓位")
|
||||
else:
|
||||
if self.start != 0:
|
||||
self.open_time = time.time() # 如果已有仓位,设置开仓时间为当前
|
||||
logger.info(f"检测到已有持仓: {'多' if self.start == 1 else '空'}")
|
||||
|
||||
# 初始化技术指标
|
||||
self.init_indicators()
|
||||
|
||||
logger.info(
|
||||
f"EMA趋势返佣策略启动\n"
|
||||
f" 交易对: {self.contract_symbol}\n"
|
||||
f" 杠杆: {self.leverage}x 全仓\n"
|
||||
f" EMA: 快{self.ema_fast_period} / 慢{self.ema_slow_period} / 大{self.ema_big_period}\n"
|
||||
f" ATR过滤: > {self.atr_min_pct*100:.1f}% | 止损: {self.stop_loss_pct*100:.1f}%\n"
|
||||
f" 最低持仓: {self.min_hold_seconds}s | 最长持仓: {self.max_hold_seconds}s\n"
|
||||
f" 仓位比例: {self.risk_percent*100:.1f}%\n"
|
||||
)
|
||||
self.ding(
|
||||
f"策略启动 | {self.contract_symbol} | {self.leverage}x\n"
|
||||
f"EMA({self.ema_fast_period}/{self.ema_slow_period}/{self.ema_big_period}) "
|
||||
f"ATR>{self.atr_min_pct*100:.1f}%"
|
||||
)
|
||||
|
||||
stats_timer = time.time()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# ① 获取最新 K 线
|
||||
klines = self.get_1min_klines(count=5)
|
||||
if not klines:
|
||||
logger.warning("K线数据获取失败,等待...")
|
||||
time.sleep(self.check_interval)
|
||||
continue
|
||||
|
||||
# ② 检查是否有新的完成K线需要处理
|
||||
latest_completed = klines[-2] if len(klines) >= 2 else None
|
||||
current_bar = klines[-1]
|
||||
current_price = current_bar['close']
|
||||
|
||||
new_bar_processed = False
|
||||
if latest_completed and latest_completed['timestamp'] != self.last_kline_time:
|
||||
# 新K线完成,处理信号
|
||||
cross, atr_pct, fast_val, slow_val, big_val = self.process_new_kline(latest_completed)
|
||||
self.last_kline_time = latest_completed['timestamp']
|
||||
new_bar_processed = True
|
||||
|
||||
# ③ 有持仓 → 检查平仓
|
||||
if self.start != 0:
|
||||
should_close, reason, reverse = self.check_close_signal(
|
||||
current_price, cross, atr_pct, fast_val, slow_val, big_val
|
||||
)
|
||||
if should_close:
|
||||
self.close_position(reason, current_price)
|
||||
# 反手开仓
|
||||
if reverse:
|
||||
time.sleep(1)
|
||||
self.open_position(reverse, current_price)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# ④ 无持仓 → 检查开仓
|
||||
if self.start == 0:
|
||||
signal = self.check_open_signal(current_price, cross, atr_pct, big_val)
|
||||
if signal:
|
||||
self.open_position(signal, current_price)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# ⑤ 未收到新K线时,仅检查止损/硬止损(实时保护)
|
||||
if not new_bar_processed and self.start != 0 and self.open_avg_price:
|
||||
if self.start == 1:
|
||||
loss_pct = (self.open_avg_price - current_price) / self.open_avg_price
|
||||
else:
|
||||
loss_pct = (current_price - self.open_avg_price) / self.open_avg_price
|
||||
|
||||
# 硬止损实时检查
|
||||
if loss_pct >= self.hard_stop_pct:
|
||||
self.close_position(f"硬止损 (亏损 {loss_pct*100:.3f}%)", current_price)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 满足持仓时间后的止损检查
|
||||
if self.open_time and (time.time() - self.open_time) >= self.min_hold_seconds:
|
||||
if loss_pct >= self.stop_loss_pct:
|
||||
self.close_position(f"止损 (亏损 {loss_pct*100:.3f}%)", current_price)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 超时检查
|
||||
hold_sec = time.time() - self.open_time
|
||||
if hold_sec >= self.max_hold_seconds:
|
||||
self.close_position(f"超时平仓 ({hold_sec:.0f}s)", current_price)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 延迟信号处理
|
||||
if self.pending_signal and self.open_time and \
|
||||
(time.time() - self.open_time) >= self.min_hold_seconds:
|
||||
atr_pct = self.calculate_atr_pct(current_price)
|
||||
fast_val = self.ema_fast.value
|
||||
slow_val = self.ema_slow.value
|
||||
big_val = self.ema_big.value
|
||||
should_close, reason, reverse = self.check_close_signal(
|
||||
current_price, None, atr_pct, fast_val, slow_val, big_val
|
||||
)
|
||||
if should_close:
|
||||
self.close_position(reason, current_price)
|
||||
if reverse:
|
||||
time.sleep(1)
|
||||
self.open_position(reverse, current_price)
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# ⑥ 定期打印统计(每 10 分钟)
|
||||
if time.time() - stats_timer >= 600:
|
||||
self.print_daily_stats()
|
||||
stats_timer = time.time()
|
||||
|
||||
# ⑦ 每轮日志
|
||||
hold_info = ""
|
||||
if self.start != 0 and self.open_time:
|
||||
hold_seconds = time.time() - self.open_time
|
||||
direction = "多" if self.start == 1 else "空"
|
||||
if self.open_avg_price:
|
||||
if self.start == 1:
|
||||
pnl_pct = (current_price - self.open_avg_price) / self.open_avg_price * 100
|
||||
else:
|
||||
pnl_pct = (self.open_avg_price - current_price) / self.open_avg_price * 100
|
||||
hold_info = f" | 持{direction} {hold_seconds:.0f}s PnL:{pnl_pct:+.3f}%"
|
||||
|
||||
if self.ema_fast.value and self.ema_slow.value and self.ema_big.value:
|
||||
logger.debug(
|
||||
f"价格 {current_price:.2f} | "
|
||||
f"EMA [{self.ema_fast.value:.2f}/{self.ema_slow.value:.2f}/{self.ema_big.value:.2f}] | "
|
||||
f"ATR {self.calculate_atr_pct(current_price)*100:.3f}%{hold_info}"
|
||||
)
|
||||
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("用户中断")
|
||||
self.print_daily_stats()
|
||||
# 中断时如果有仓位,提示手动处理
|
||||
if self.start != 0:
|
||||
logger.warning("当前仍有持仓,请手动处理!")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"主循环异常: {e}")
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BitmartRebateStrategy().action()
|
||||
Reference in New Issue
Block a user