pivot improvement
This commit is contained in:
parent
249ec6f5a7
commit
d870f74d0c
1
.cursorignore
Normal file
1
.cursorignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
|
@ -1,749 +1,358 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"trade_id": 1,
|
"trade_id": 1,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:46:48.566670+00:00",
|
"entry_time": "2025-05-30T17:25:58.643819+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:11.830306+00:00",
|
"exit_time": "2025-05-30T17:26:39.729472+00:00",
|
||||||
"entry_price": 2604.21,
|
"entry_price": 2550.7,
|
||||||
"exit_price": 2604.4,
|
"exit_price": 2546.59,
|
||||||
"size": 0.003576,
|
"size": 0.003724,
|
||||||
"gross_pnl": 0.0006794400000001952,
|
"gross_pnl": 0.015305639999998781,
|
||||||
"fees": 0.009312994680000002,
|
"fees": 0.00949115398,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.008633554679999806,
|
"net_pnl": 0.005814486019998783,
|
||||||
"duration": "0:00:23.263636",
|
"duration": "0:00:41.085653",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 2,
|
"trade_id": 2,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:47:11.830306+00:00",
|
"entry_time": "2025-05-30T17:26:39.729472+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:16.736449+00:00",
|
"exit_time": "2025-05-30T17:26:40.742643+00:00",
|
||||||
"entry_price": 2604.4,
|
"entry_price": 2546.59,
|
||||||
"exit_price": 2605.29,
|
"exit_price": 2546.58,
|
||||||
"size": 0.002833,
|
"size": 0.003456,
|
||||||
"gross_pnl": -0.0025213699999996394,
|
"gross_pnl": -3.456000000075437e-05,
|
||||||
"fees": 0.007379525885,
|
"fees": 0.008800997759999998,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.00990089588499964,
|
"net_pnl": -0.008835557760000754,
|
||||||
"duration": "0:00:04.906143",
|
"duration": "0:00:01.013171",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 3,
|
"trade_id": 3,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:47:16.736449+00:00",
|
"entry_time": "2025-05-30T17:26:40.742643+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:33.874932+00:00",
|
"exit_time": "2025-05-30T17:26:44.783909+00:00",
|
||||||
"entry_price": 2605.29,
|
"entry_price": 2546.58,
|
||||||
"exit_price": 2605.1,
|
"exit_price": 2546.69,
|
||||||
"size": 0.002799,
|
"size": 0.003155,
|
||||||
"gross_pnl": -0.0005318100000001527,
|
"gross_pnl": -0.0003470500000004017,
|
||||||
"fees": 0.007291940804999999,
|
"fees": 0.008034633425,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.007823750805000152,
|
"net_pnl": -0.008381683425000402,
|
||||||
"duration": "0:00:17.138483",
|
"duration": "0:00:04.041266",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": true
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 4,
|
"trade_id": 4,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:47:33.874932+00:00",
|
"entry_time": "2025-05-30T17:26:44.783909+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:36.898270+00:00",
|
"exit_time": "2025-05-30T17:26:56.903098+00:00",
|
||||||
"entry_price": 2605.1,
|
"entry_price": 2546.69,
|
||||||
"exit_price": 2605.1,
|
"exit_price": 2546.9,
|
||||||
"size": 0.003048,
|
"size": 0.003374,
|
||||||
"gross_pnl": 0.0,
|
"gross_pnl": 0.0007085400000001227,
|
||||||
"fees": 0.007940344799999999,
|
"fees": 0.00859288633,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.007940344799999999,
|
"net_pnl": -0.007884346329999877,
|
||||||
"duration": "0:00:03.023338",
|
"duration": "0:00:12.119189",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 5,
|
"trade_id": 5,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:47:36.898270+00:00",
|
"entry_time": "2025-05-30T17:26:56.903098+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:37.897486+00:00",
|
"exit_time": "2025-05-30T17:27:03.971971+00:00",
|
||||||
"entry_price": 2605.1,
|
"entry_price": 2546.9,
|
||||||
"exit_price": 2604.7,
|
"exit_price": 2547.78,
|
||||||
"size": 0.003562,
|
"size": 0.003309,
|
||||||
"gross_pnl": -0.001424800000000324,
|
"gross_pnl": -0.002911920000000361,
|
||||||
"fees": 0.0092786538,
|
"fees": 0.00842914806,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.010703453800000325,
|
"net_pnl": -0.011341068060000362,
|
||||||
"duration": "0:00:00.999216",
|
"duration": "0:00:07.068873",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 6,
|
"trade_id": 6,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:47:37.897486+00:00",
|
"entry_time": "2025-05-30T17:27:03.971971+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:48.957013+00:00",
|
"exit_time": "2025-05-30T17:27:24.185714+00:00",
|
||||||
"entry_price": 2604.7,
|
"entry_price": 2547.78,
|
||||||
"exit_price": 2604.8,
|
"exit_price": 2548.0,
|
||||||
"size": 0.002685,
|
"size": 0.003704,
|
||||||
"gross_pnl": -0.0002685000000009768,
|
"gross_pnl": 0.0008148799999992589,
|
||||||
"fees": 0.00699375375,
|
"fees": 0.009437384560000001,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.007262253750000976,
|
"net_pnl": -0.008622504560000742,
|
||||||
"duration": "0:00:11.059527",
|
"duration": "0:00:20.213743",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 7,
|
"trade_id": 7,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:47:48.957013+00:00",
|
"entry_time": "2025-05-30T17:27:24.185714+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:51.986365+00:00",
|
"exit_time": "2025-05-30T17:27:35.315014+00:00",
|
||||||
"entry_price": 2604.8,
|
"entry_price": 2548.0,
|
||||||
"exit_price": 2604.3,
|
"exit_price": 2547.67,
|
||||||
"size": 0.003647,
|
"size": 0.003304,
|
||||||
"gross_pnl": -0.0018235,
|
"gross_pnl": 0.0010903199999997596,
|
||||||
"fees": 0.00949879385,
|
"fees": 0.008418046840000002,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.011322293850000002,
|
"net_pnl": -0.007327726840000242,
|
||||||
"duration": "0:00:03.029352",
|
"duration": "0:00:11.129300",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 8,
|
"trade_id": 8,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:47:51.986365+00:00",
|
"entry_time": "2025-05-30T17:27:35.315014+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:52.946304+00:00",
|
"exit_time": "2025-05-30T17:27:48.488282+00:00",
|
||||||
"entry_price": 2604.3,
|
"entry_price": 2547.67,
|
||||||
"exit_price": 2604.3,
|
"exit_price": 2547.5,
|
||||||
"size": 0.002838,
|
"size": 0.003442,
|
||||||
"gross_pnl": 0.0,
|
"gross_pnl": -0.0005851400000002505,
|
||||||
"fees": 0.0073910034,
|
"fees": 0.00876878757,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.0073910034,
|
"net_pnl": -0.00935392757000025,
|
||||||
"duration": "0:00:00.959939",
|
"duration": "0:00:13.173268",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 9,
|
"trade_id": 9,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:47:52.946304+00:00",
|
"entry_time": "2025-05-30T17:27:48.488282+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:54.208771+00:00",
|
"exit_time": "2025-05-30T17:28:09.641167+00:00",
|
||||||
"entry_price": 2604.3,
|
"entry_price": 2547.5,
|
||||||
"exit_price": 2604.3,
|
"exit_price": 2547.2,
|
||||||
"size": 0.003537,
|
"size": 0.003729,
|
||||||
"gross_pnl": 0.0,
|
"gross_pnl": 0.0011187000000006783,
|
||||||
"fees": 0.009211409100000002,
|
"fees": 0.009499068150000001,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.009211409100000002,
|
"net_pnl": -0.008380368149999321,
|
||||||
"duration": "0:00:01.262467",
|
"duration": "0:00:21.152885",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 10,
|
"trade_id": 10,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:47:54.208771+00:00",
|
"entry_time": "2025-05-30T17:28:09.641167+00:00",
|
||||||
"exit_time": "2025-05-30T15:47:57.069714+00:00",
|
"exit_time": "2025-05-30T17:29:03.116674+00:00",
|
||||||
"entry_price": 2604.3,
|
"entry_price": 2547.2,
|
||||||
"exit_price": 2604.39,
|
"exit_price": 2549.4,
|
||||||
"size": 0.00349,
|
"size": 0.0034,
|
||||||
"gross_pnl": -0.0003140999999989208,
|
"gross_pnl": 0.007480000000000927,
|
||||||
"fees": 0.009089164050000001,
|
"fees": 0.00866422,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.00940326404999892,
|
"net_pnl": -0.0011842199999990725,
|
||||||
"duration": "0:00:02.860943",
|
"duration": "0:00:53.475507",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 11,
|
"trade_id": 11,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:47:57.069714+00:00",
|
"entry_time": "2025-05-30T17:29:03.116674+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:34.556088+00:00",
|
"exit_time": "2025-05-30T17:29:10.180571+00:00",
|
||||||
"entry_price": 2604.39,
|
"entry_price": 2549.4,
|
||||||
"exit_price": 2605.5,
|
"exit_price": 2549.79,
|
||||||
"size": 0.003648,
|
"size": 0.003408,
|
||||||
"gross_pnl": 0.004049280000000465,
|
"gross_pnl": -0.0013291199999995661,
|
||||||
"fees": 0.009502839360000001,
|
"fees": 0.00868901976,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.005453559359999536,
|
"net_pnl": -0.010018139759999566,
|
||||||
"duration": "0:00:37.486374",
|
"duration": "0:00:07.063897",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 12,
|
"trade_id": 12,
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:48:34.556088+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:48:36.554840+00:00",
|
|
||||||
"entry_price": 2605.5,
|
|
||||||
"exit_price": 2605.6,
|
|
||||||
"size": 0.002613,
|
|
||||||
"gross_pnl": -0.00026129999999976235,
|
|
||||||
"fees": 0.00680830215,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.007069602149999762,
|
|
||||||
"duration": "0:00:01.998752",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 13,
|
|
||||||
"side": "LONG",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:48:36.554840+00:00",
|
"entry_time": "2025-05-30T17:29:10.180571+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:37.522249+00:00",
|
"exit_time": "2025-05-30T17:29:19.404003+00:00",
|
||||||
"entry_price": 2605.6,
|
"entry_price": 2549.79,
|
||||||
"exit_price": 2605.7,
|
"exit_price": 2548.9,
|
||||||
"size": 0.003435,
|
"size": 0.003552,
|
||||||
"gross_pnl": 0.0003434999999996876,
|
"gross_pnl": -0.003161279999999548,
|
||||||
"fees": 0.00895040775,
|
"fees": 0.00905527344,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.008606907750000312,
|
"net_pnl": -0.012216553439999549,
|
||||||
"duration": "0:00:00.967409",
|
"duration": "0:00:09.223432",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 14,
|
"trade_id": 13,
|
||||||
"side": "SHORT",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:48:37.522249+00:00",
|
"entry_time": "2025-05-30T17:29:19.404003+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:39.531230+00:00",
|
"exit_time": "2025-05-30T17:29:40.434581+00:00",
|
||||||
"entry_price": 2605.7,
|
"entry_price": 2548.9,
|
||||||
"exit_price": 2606.69,
|
"exit_price": 2547.8,
|
||||||
"size": 0.003062,
|
"size": 0.003692,
|
||||||
"gross_pnl": -0.003031380000000724,
|
"gross_pnl": 0.004061199999999664,
|
||||||
"fees": 0.00798016909,
|
"fees": 0.0094085082,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.011011549090000725,
|
"net_pnl": -0.005347308200000336,
|
||||||
"duration": "0:00:02.008981",
|
"duration": "0:00:21.030578",
|
||||||
|
"symbol": "ETH/USDC",
|
||||||
|
"mexc_executed": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trade_id": 14,
|
||||||
|
"side": "LONG",
|
||||||
|
"entry_time": "2025-05-30T17:29:40.434581+00:00",
|
||||||
|
"exit_time": "2025-05-30T17:29:41.445058+00:00",
|
||||||
|
"entry_price": 2547.8,
|
||||||
|
"exit_price": 2547.8,
|
||||||
|
"size": 0.003729,
|
||||||
|
"gross_pnl": 0.0,
|
||||||
|
"fees": 0.009500746200000002,
|
||||||
|
"fee_type": "taker",
|
||||||
|
"fee_rate": 0.0005,
|
||||||
|
"net_pnl": -0.009500746200000002,
|
||||||
|
"duration": "0:00:01.010477",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 15,
|
"trade_id": 15,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:48:39.531230+00:00",
|
"entry_time": "2025-05-30T17:29:41.445058+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:47.597191+00:00",
|
"exit_time": "2025-05-30T17:29:45.488994+00:00",
|
||||||
"entry_price": 2606.69,
|
"entry_price": 2547.8,
|
||||||
"exit_price": 2605.4,
|
"exit_price": 2547.88,
|
||||||
"size": 0.003069,
|
"size": 0.003215,
|
||||||
"gross_pnl": -0.003959009999999889,
|
"gross_pnl": -0.0002571999999997661,
|
||||||
"fees": 0.007997952105000001,
|
"fees": 0.0081913056,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.01195696210499989,
|
"net_pnl": -0.008448505599999765,
|
||||||
"duration": "0:00:08.065961",
|
"duration": "0:00:04.043936",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 16,
|
"trade_id": 16,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:48:47.597191+00:00",
|
"entry_time": "2025-05-30T17:29:45.488994+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:55.696686+00:00",
|
"exit_time": "2025-05-30T17:30:11.732339+00:00",
|
||||||
"entry_price": 2605.4,
|
"entry_price": 2547.88,
|
||||||
"exit_price": 2605.0,
|
"exit_price": 2549.3,
|
||||||
"size": 0.003267,
|
"size": 0.003189,
|
||||||
"gross_pnl": 0.0013068000000002972,
|
"gross_pnl": 0.004528380000000232,
|
||||||
"fees": 0.008511188400000001,
|
"fees": 0.00812745351,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.0072043883999997034,
|
"net_pnl": -0.0035990735099997685,
|
||||||
"duration": "0:00:08.099495",
|
"duration": "0:00:26.243345",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 17,
|
"trade_id": 17,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:48:55.696686+00:00",
|
"entry_time": "2025-05-30T17:30:11.732339+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:56.673544+00:00",
|
"exit_time": "2025-05-30T17:30:25.893383+00:00",
|
||||||
"entry_price": 2605.0,
|
"entry_price": 2549.3,
|
||||||
"exit_price": 2605.09,
|
"exit_price": 2548.76,
|
||||||
"size": 0.003647,
|
"size": 0.003013,
|
||||||
"gross_pnl": 0.0003282300000005307,
|
"gross_pnl": 0.0016270199999998904,
|
||||||
"fees": 0.009500599115000001,
|
"fees": 0.007680227390000001,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.00917236911499947,
|
"net_pnl": -0.00605320739000011,
|
||||||
"duration": "0:00:00.976858",
|
"duration": "0:00:14.161044",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 18,
|
"trade_id": 18,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:48:56.673544+00:00",
|
"entry_time": "2025-05-30T17:30:25.893383+00:00",
|
||||||
"exit_time": "2025-05-30T15:48:59.683812+00:00",
|
"exit_time": "2025-05-30T17:30:40.053758+00:00",
|
||||||
"entry_price": 2605.09,
|
"entry_price": 2548.76,
|
||||||
"exit_price": 2605.2,
|
"exit_price": 2549.4,
|
||||||
"size": 0.00307,
|
"size": 0.002905,
|
||||||
"gross_pnl": -0.0003376999999989948,
|
"gross_pnl": 0.0018591999999996302,
|
||||||
"fees": 0.00799779515,
|
"fees": 0.007405077400000001,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.008335495149998994,
|
"net_pnl": -0.005545877400000371,
|
||||||
"duration": "0:00:03.010268",
|
"duration": "0:00:14.160375",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 19,
|
"trade_id": 19,
|
||||||
"side": "LONG",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:48:59.683812+00:00",
|
"entry_time": "2025-05-30T17:30:40.053758+00:00",
|
||||||
"exit_time": "2025-05-30T15:49:09.266816+00:00",
|
"exit_time": "2025-05-30T17:30:46.111367+00:00",
|
||||||
"entry_price": 2605.2,
|
"entry_price": 2549.4,
|
||||||
"exit_price": 2604.77,
|
"exit_price": 2549.8,
|
||||||
"size": 0.003379,
|
"size": 0.003726,
|
||||||
"gross_pnl": -0.0014529699999994469,
|
"gross_pnl": -0.001490400000000339,
|
||||||
"fees": 0.008802244314999999,
|
"fees": 0.0094998096,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.010255214314999445,
|
"net_pnl": -0.01099020960000034,
|
||||||
"duration": "0:00:09.583004",
|
"duration": "0:00:06.057609",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": true
|
"mexc_executed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 20,
|
"trade_id": 20,
|
||||||
"side": "SHORT",
|
"side": "LONG",
|
||||||
"entry_time": "2025-05-30T15:49:09.266816+00:00",
|
"entry_time": "2025-05-30T17:30:46.111367+00:00",
|
||||||
"exit_time": "2025-05-30T15:49:11.161782+00:00",
|
"exit_time": "2025-05-30T17:30:48.166894+00:00",
|
||||||
"entry_price": 2604.77,
|
"entry_price": 2549.8,
|
||||||
"exit_price": 2604.31,
|
"exit_price": 2549.21,
|
||||||
"size": 0.002557,
|
"size": 0.003652,
|
||||||
"gross_pnl": 0.001176220000000093,
|
"gross_pnl": -0.0021546800000005312,
|
||||||
"fees": 0.00665980878,
|
"fees": 0.009310792259999999,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.005483588779999907,
|
"net_pnl": -0.011465472260000532,
|
||||||
"duration": "0:00:01.894966",
|
"duration": "0:00:02.055527",
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"trade_id": 21,
|
"trade_id": 21,
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:49:11.161782+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:12.298999+00:00",
|
|
||||||
"entry_price": 2604.31,
|
|
||||||
"exit_price": 2603.92,
|
|
||||||
"size": 0.003603,
|
|
||||||
"gross_pnl": -0.0014051699999995412,
|
|
||||||
"fees": 0.009382626344999999,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.01078779634499954,
|
|
||||||
"duration": "0:00:01.137217",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 22,
|
|
||||||
"side": "SHORT",
|
"side": "SHORT",
|
||||||
"entry_time": "2025-05-30T15:49:12.298999+00:00",
|
"entry_time": "2025-05-30T17:30:48.166894+00:00",
|
||||||
"exit_time": "2025-05-30T15:49:24.339209+00:00",
|
"exit_time": "2025-05-30T17:31:12.387130+00:00",
|
||||||
"entry_price": 2603.92,
|
"entry_price": 2549.21,
|
||||||
"exit_price": 2604.03,
|
"exit_price": 2547.77,
|
||||||
"size": 0.003234,
|
"size": 0.003313,
|
||||||
"gross_pnl": -0.0003557400000004118,
|
"gross_pnl": 0.00477072000000018,
|
||||||
"fees": 0.008421255150000001,
|
"fees": 0.008443147370000001,
|
||||||
"fee_type": "taker",
|
"fee_type": "taker",
|
||||||
"fee_rate": 0.0005,
|
"fee_rate": 0.0005,
|
||||||
"net_pnl": -0.008776995150000412,
|
"net_pnl": -0.0036724273699998197,
|
||||||
"duration": "0:00:12.040210",
|
"duration": "0:00:24.220236",
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 23,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:49:24.339209+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:25.364806+00:00",
|
|
||||||
"entry_price": 2604.03,
|
|
||||||
"exit_price": 2604.0,
|
|
||||||
"size": 0.003211,
|
|
||||||
"gross_pnl": -9.633000000064248e-05,
|
|
||||||
"fees": 0.008361492165000001,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.008457822165000642,
|
|
||||||
"duration": "0:00:01.025597",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 24,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:49:25.364806+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:26.274504+00:00",
|
|
||||||
"entry_price": 2604.0,
|
|
||||||
"exit_price": 2604.0,
|
|
||||||
"size": 0.003067,
|
|
||||||
"gross_pnl": 0.0,
|
|
||||||
"fees": 0.007986468,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.007986468,
|
|
||||||
"duration": "0:00:00.909698",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 25,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:49:26.274504+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:33.355299+00:00",
|
|
||||||
"entry_price": 2604.0,
|
|
||||||
"exit_price": 2603.61,
|
|
||||||
"size": 0.003566,
|
|
||||||
"gross_pnl": -0.001390739999999546,
|
|
||||||
"fees": 0.00928516863,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.010675908629999547,
|
|
||||||
"duration": "0:00:07.080795",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 26,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:49:33.355299+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:36.415411+00:00",
|
|
||||||
"entry_price": 2603.61,
|
|
||||||
"exit_price": 2603.6,
|
|
||||||
"size": 0.00328,
|
|
||||||
"gross_pnl": 3.280000000071595e-05,
|
|
||||||
"fees": 0.0085398244,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.008507024399999284,
|
|
||||||
"duration": "0:00:03.060112",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 27,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:49:36.415411+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:38.429512+00:00",
|
|
||||||
"entry_price": 2603.6,
|
|
||||||
"exit_price": 2602.53,
|
|
||||||
"size": 0.00364,
|
|
||||||
"gross_pnl": -0.0038947999999989404,
|
|
||||||
"fees": 0.0094751566,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.013369956599998942,
|
|
||||||
"duration": "0:00:02.014101",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 28,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:49:38.429512+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:49:47.285835+00:00",
|
|
||||||
"entry_price": 2602.53,
|
|
||||||
"exit_price": 2602.56,
|
|
||||||
"size": 0.00365,
|
|
||||||
"gross_pnl": -0.00010949999999907049,
|
|
||||||
"fees": 0.009499289250000001,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.009608789249999071,
|
|
||||||
"duration": "0:00:08.856323",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 29,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:49:47.285835+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:50:36.918488+00:00",
|
|
||||||
"entry_price": 2602.56,
|
|
||||||
"exit_price": 2605.1,
|
|
||||||
"size": 0.003291,
|
|
||||||
"gross_pnl": 0.008359139999999881,
|
|
||||||
"fees": 0.008569204530000001,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.00021006453000011957,
|
|
||||||
"duration": "0:00:49.632653",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 30,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:50:36.918488+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:50:48.718534+00:00",
|
|
||||||
"entry_price": 2605.1,
|
|
||||||
"exit_price": 2604.41,
|
|
||||||
"size": 0.003411,
|
|
||||||
"gross_pnl": 0.002353590000000186,
|
|
||||||
"fees": 0.008884819305,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.006531229304999813,
|
|
||||||
"duration": "0:00:11.800046",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 31,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:50:48.718534+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:50:51.034097+00:00",
|
|
||||||
"entry_price": 2604.41,
|
|
||||||
"exit_price": 2603.93,
|
|
||||||
"size": 0.00337,
|
|
||||||
"gross_pnl": -0.0016176000000000614,
|
|
||||||
"fees": 0.008776052900000001,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.010393652900000062,
|
|
||||||
"duration": "0:00:02.315563",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 32,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:50:51.034097+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:50:53.833190+00:00",
|
|
||||||
"entry_price": 2603.93,
|
|
||||||
"exit_price": 2604.2,
|
|
||||||
"size": 0.003184,
|
|
||||||
"gross_pnl": -0.0008596799999999421,
|
|
||||||
"fees": 0.008291342960000002,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.009151022959999942,
|
|
||||||
"duration": "0:00:02.799093",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 33,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:50:53.833190+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:51:12.337656+00:00",
|
|
||||||
"entry_price": 2604.2,
|
|
||||||
"exit_price": 2604.49,
|
|
||||||
"size": 0.003578,
|
|
||||||
"gross_pnl": 0.0010376199999998698,
|
|
||||||
"fees": 0.009318346410000001,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.00828072641000013,
|
|
||||||
"duration": "0:00:18.504466",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 34,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:51:12.337656+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:51:18.768780+00:00",
|
|
||||||
"entry_price": 2604.49,
|
|
||||||
"exit_price": 2604.3,
|
|
||||||
"size": 0.002971,
|
|
||||||
"gross_pnl": 0.0005644899999988111,
|
|
||||||
"fees": 0.007737657545,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.007173167545001189,
|
|
||||||
"duration": "0:00:06.431124",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 35,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:51:18.768780+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:51:41.211077+00:00",
|
|
||||||
"entry_price": 2604.3,
|
|
||||||
"exit_price": 2603.58,
|
|
||||||
"size": 0.003587,
|
|
||||||
"gross_pnl": -0.0025826400000009135,
|
|
||||||
"fees": 0.00934033278,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.011922972780000913,
|
|
||||||
"duration": "0:00:22.442297",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 36,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:51:41.211077+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:51:44.328012+00:00",
|
|
||||||
"entry_price": 2603.58,
|
|
||||||
"exit_price": 2603.8,
|
|
||||||
"size": 0.00313,
|
|
||||||
"gross_pnl": -0.000688600000000797,
|
|
||||||
"fees": 0.0081495497,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.008838149700000797,
|
|
||||||
"duration": "0:00:03.116935",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 37,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:51:44.328012+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:52:09.755402+00:00",
|
|
||||||
"entry_price": 2603.8,
|
|
||||||
"exit_price": 2605.17,
|
|
||||||
"size": 0.003105,
|
|
||||||
"gross_pnl": 0.004253849999999662,
|
|
||||||
"fees": 0.008086925925,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.0038330759250003385,
|
|
||||||
"duration": "0:00:25.427390",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 38,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:52:09.755402+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:52:28.457757+00:00",
|
|
||||||
"entry_price": 2605.17,
|
|
||||||
"exit_price": 2604.7,
|
|
||||||
"size": 0.003439,
|
|
||||||
"gross_pnl": 0.0016163300000008758,
|
|
||||||
"fees": 0.008958371465,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.007342041464999125,
|
|
||||||
"duration": "0:00:18.702355",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 39,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:52:28.457757+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:53:03.648655+00:00",
|
|
||||||
"entry_price": 2604.7,
|
|
||||||
"exit_price": 2605.51,
|
|
||||||
"size": 0.003285,
|
|
||||||
"gross_pnl": 0.0026608500000013147,
|
|
||||||
"fees": 0.008557769925,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.005896919924998686,
|
|
||||||
"duration": "0:00:35.190898",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 40,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:53:03.648655+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:53:17.399923+00:00",
|
|
||||||
"entry_price": 2605.51,
|
|
||||||
"exit_price": 2605.18,
|
|
||||||
"size": 0.003646,
|
|
||||||
"gross_pnl": 0.0012031800000013926,
|
|
||||||
"fees": 0.009499087869999999,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.008295907869998606,
|
|
||||||
"duration": "0:00:13.751268",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 41,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:53:17.399923+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:53:26.556819+00:00",
|
|
||||||
"entry_price": 2605.18,
|
|
||||||
"exit_price": 2605.06,
|
|
||||||
"size": 0.003546,
|
|
||||||
"gross_pnl": -0.000425519999999613,
|
|
||||||
"fees": 0.009237755520000002,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.009663275519999613,
|
|
||||||
"duration": "0:00:09.156896",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 42,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:53:26.556819+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:53:52.936931+00:00",
|
|
||||||
"entry_price": 2605.06,
|
|
||||||
"exit_price": 2601.4,
|
|
||||||
"size": 0.00318,
|
|
||||||
"gross_pnl": 0.011638799999999538,
|
|
||||||
"fees": 0.0082782714,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": 0.0033605285999995377,
|
|
||||||
"duration": "0:00:26.380112",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 43,
|
|
||||||
"side": "LONG",
|
|
||||||
"entry_time": "2025-05-30T15:53:52.936931+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:53:56.578000+00:00",
|
|
||||||
"entry_price": 2601.4,
|
|
||||||
"exit_price": 2600.78,
|
|
||||||
"size": 0.003544,
|
|
||||||
"gross_pnl": -0.002197279999999613,
|
|
||||||
"fees": 0.009218262960000001,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.011415542959999614,
|
|
||||||
"duration": "0:00:03.641069",
|
|
||||||
"symbol": "ETH/USDC",
|
|
||||||
"mexc_executed": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trade_id": 44,
|
|
||||||
"side": "SHORT",
|
|
||||||
"entry_time": "2025-05-30T15:53:56.578000+00:00",
|
|
||||||
"exit_time": "2025-05-30T15:54:26.021540+00:00",
|
|
||||||
"entry_price": 2600.78,
|
|
||||||
"exit_price": 2601.2,
|
|
||||||
"size": 0.00335,
|
|
||||||
"gross_pnl": -0.0014069999999987205,
|
|
||||||
"fees": 0.0087133165,
|
|
||||||
"fee_type": "taker",
|
|
||||||
"fee_rate": 0.0005,
|
|
||||||
"net_pnl": -0.01012031649999872,
|
|
||||||
"duration": "0:00:29.443540",
|
|
||||||
"symbol": "ETH/USDC",
|
"symbol": "ETH/USDC",
|
||||||
"mexc_executed": false
|
"mexc_executed": false
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ from .extrema_trainer import ExtremaTrainer
|
|||||||
from .trading_action import TradingAction
|
from .trading_action import TradingAction
|
||||||
from .negative_case_trainer import NegativeCaseTrainer
|
from .negative_case_trainer import NegativeCaseTrainer
|
||||||
from .trading_executor import TradingExecutor
|
from .trading_executor import TradingExecutor
|
||||||
|
from training.enhanced_pivot_rl_trainer import EnhancedPivotRLTrainer, create_enhanced_pivot_trainer
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -157,12 +158,22 @@ class EnhancedTradingOrchestrator:
|
|||||||
self.current_positions = {} # symbol -> {'side': 'LONG'|'SHORT'|'FLAT', 'entry_price': float, 'timestamp': datetime}
|
self.current_positions = {} # symbol -> {'side': 'LONG'|'SHORT'|'FLAT', 'entry_price': float, 'timestamp': datetime}
|
||||||
self.last_signals = {} # symbol -> {'action': 'BUY'|'SELL', 'timestamp': datetime, 'confidence': float}
|
self.last_signals = {} # symbol -> {'action': 'BUY'|'SELL', 'timestamp': datetime, 'confidence': float}
|
||||||
|
|
||||||
# Different thresholds for entry vs exit
|
# Initialize Enhanced Pivot RL Trainer
|
||||||
self.entry_threshold = self.config.orchestrator.get('entry_threshold', 0.75) # Higher threshold for entries
|
self.pivot_rl_trainer = create_enhanced_pivot_trainer(
|
||||||
self.exit_threshold = self.config.orchestrator.get('exit_threshold', 0.35) # Lower threshold for exits
|
data_provider=self.data_provider,
|
||||||
|
orchestrator=self
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"Entry threshold: {self.entry_threshold:.3f} (more certain)")
|
# Get dynamic thresholds from pivot trainer
|
||||||
logger.info(f"Exit threshold: {self.exit_threshold:.3f} (easier to exit)")
|
thresholds = self.pivot_rl_trainer.get_current_thresholds()
|
||||||
|
self.entry_threshold = thresholds['entry_threshold'] # Higher threshold for entries
|
||||||
|
self.exit_threshold = thresholds['exit_threshold'] # Lower threshold for exits
|
||||||
|
self.uninvested_threshold = thresholds['uninvested_threshold'] # Stay out threshold
|
||||||
|
|
||||||
|
logger.info(f"Dynamic Pivot-Based Thresholds:")
|
||||||
|
logger.info(f" Entry threshold: {self.entry_threshold:.3f} (more certain)")
|
||||||
|
logger.info(f" Exit threshold: {self.exit_threshold:.3f} (easier to exit)")
|
||||||
|
logger.info(f" Uninvested threshold: {self.uninvested_threshold:.3f} (stay out when uncertain)")
|
||||||
|
|
||||||
# Initialize universal data adapter
|
# Initialize universal data adapter
|
||||||
self.universal_adapter = UniversalDataAdapter(self.data_provider)
|
self.universal_adapter = UniversalDataAdapter(self.data_provider)
|
||||||
@ -2046,29 +2057,33 @@ class EnhancedTradingOrchestrator:
|
|||||||
|
|
||||||
def _make_2_action_decision(self, symbol: str, predictions: List[EnhancedPrediction],
|
def _make_2_action_decision(self, symbol: str, predictions: List[EnhancedPrediction],
|
||||||
market_state: MarketState) -> Optional[TradingAction]:
|
market_state: MarketState) -> Optional[TradingAction]:
|
||||||
"""
|
"""Enhanced 2-action decision making with pivot analysis and CNN predictions"""
|
||||||
Make trading decision using strict 2-action system (BUY/SELL only)
|
try:
|
||||||
|
|
||||||
STRICT Logic:
|
|
||||||
- When FLAT: BUY signal -> go LONG, SELL signal -> go SHORT
|
|
||||||
- When LONG: SELL signal -> close LONG immediately (and optionally enter SHORT if no other positions)
|
|
||||||
- When SHORT: BUY signal -> close SHORT immediately (and optionally enter LONG if no other positions)
|
|
||||||
- ALWAYS close opposite positions first before opening new ones
|
|
||||||
"""
|
|
||||||
if not predictions:
|
if not predictions:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
# Get the best prediction
|
||||||
# Get best prediction
|
best_pred = max(predictions, key=lambda p: p.confidence)
|
||||||
best_pred = max(predictions, key=lambda p: p.overall_confidence)
|
confidence = best_pred.confidence
|
||||||
raw_action = best_pred.overall_action
|
raw_action = best_pred.action
|
||||||
confidence = best_pred.overall_confidence
|
|
||||||
|
|
||||||
# Get current position for this symbol
|
# Update dynamic thresholds periodically
|
||||||
current_position = self.current_positions.get(symbol, {'side': 'FLAT'})
|
if hasattr(self, '_last_threshold_update'):
|
||||||
position_side = current_position['side']
|
if (datetime.now() - self._last_threshold_update).total_seconds() > 3600: # Every hour
|
||||||
|
self.update_dynamic_thresholds()
|
||||||
|
self._last_threshold_update = datetime.now()
|
||||||
|
else:
|
||||||
|
self._last_threshold_update = datetime.now()
|
||||||
|
|
||||||
# STRICT LOGIC: Determine action type
|
# Check if we should stay uninvested due to low confidence
|
||||||
|
if confidence < self.uninvested_threshold:
|
||||||
|
logger.info(f"[{symbol}] Staying uninvested - confidence {confidence:.3f} below threshold {self.uninvested_threshold:.3f}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get current position
|
||||||
|
position_side = self._get_current_position_side(symbol)
|
||||||
|
|
||||||
|
# Determine if this is entry or exit
|
||||||
is_entry = False
|
is_entry = False
|
||||||
is_exit = False
|
is_exit = False
|
||||||
final_action = raw_action
|
final_action = raw_action
|
||||||
@ -2098,10 +2113,29 @@ class EnhancedTradingOrchestrator:
|
|||||||
logger.info(f"[{symbol}] SHORT position - SELL signal ignored (already short)")
|
logger.info(f"[{symbol}] SHORT position - SELL signal ignored (already short)")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Apply appropriate threshold
|
# Apply appropriate threshold with CNN enhancement
|
||||||
if is_entry:
|
if is_entry:
|
||||||
threshold = self.entry_threshold
|
threshold = self.entry_threshold
|
||||||
threshold_type = "ENTRY"
|
threshold_type = "ENTRY"
|
||||||
|
|
||||||
|
# For entries, check if CNN predicts favorable pivot
|
||||||
|
if hasattr(self.pivot_rl_trainer, 'williams') and self.pivot_rl_trainer.williams.cnn_model:
|
||||||
|
try:
|
||||||
|
# Get market data for CNN analysis
|
||||||
|
current_price = market_state.prices.get(self.timeframes[0], 0)
|
||||||
|
|
||||||
|
# CNN prediction could lower entry threshold if it predicts favorable pivot
|
||||||
|
# This allows earlier entry before pivot is confirmed
|
||||||
|
cnn_adjustment = self._get_cnn_threshold_adjustment(symbol, raw_action, market_state)
|
||||||
|
adjusted_threshold = max(threshold - cnn_adjustment, threshold * 0.8) # Max 20% reduction
|
||||||
|
|
||||||
|
if cnn_adjustment > 0:
|
||||||
|
logger.info(f"[{symbol}] CNN predicts favorable pivot - adjusted entry threshold: {threshold:.3f} -> {adjusted_threshold:.3f}")
|
||||||
|
threshold = adjusted_threshold
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error getting CNN threshold adjustment: {e}")
|
||||||
|
|
||||||
elif is_exit:
|
elif is_exit:
|
||||||
threshold = self.exit_threshold
|
threshold = self.exit_threshold
|
||||||
threshold_type = "EXIT"
|
threshold_type = "EXIT"
|
||||||
@ -2130,7 +2164,8 @@ class EnhancedTradingOrchestrator:
|
|||||||
'position_before': position_side,
|
'position_before': position_side,
|
||||||
'action_type': threshold_type,
|
'action_type': threshold_type,
|
||||||
'threshold_used': threshold,
|
'threshold_used': threshold,
|
||||||
'strict_mode': True,
|
'pivot_enhanced': True,
|
||||||
|
'cnn_integrated': hasattr(self.pivot_rl_trainer, 'williams') and self.pivot_rl_trainer.williams.cnn_model is not None,
|
||||||
'timeframe_breakdown': [(tf.timeframe, tf.action, tf.confidence)
|
'timeframe_breakdown': [(tf.timeframe, tf.action, tf.confidence)
|
||||||
for tf in best_pred.timeframe_predictions],
|
for tf in best_pred.timeframe_predictions],
|
||||||
'market_regime': market_state.market_regime
|
'market_regime': market_state.market_regime
|
||||||
@ -2148,14 +2183,72 @@ class EnhancedTradingOrchestrator:
|
|||||||
'confidence': confidence
|
'confidence': confidence
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"[{symbol}] STRICT {threshold_type} Decision: {final_action} (conf: {confidence:.3f}, threshold: {threshold:.3f})")
|
logger.info(f"[{symbol}] ENHANCED {threshold_type} Decision: {final_action} (conf: {confidence:.3f}, threshold: {threshold:.3f})")
|
||||||
|
|
||||||
return action
|
return action
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error making strict 2-action decision for {symbol}: {e}")
|
logger.error(f"Error making enhanced 2-action decision for {symbol}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_cnn_threshold_adjustment(self, symbol: str, action: str, market_state: MarketState) -> float:
|
||||||
|
"""Get threshold adjustment based on CNN pivot predictions"""
|
||||||
|
try:
|
||||||
|
# This would analyze CNN predictions to determine if we should lower entry threshold
|
||||||
|
# For example, if CNN predicts a swing low and we want to BUY, we can be more aggressive
|
||||||
|
|
||||||
|
# Placeholder implementation - in real scenario, this would:
|
||||||
|
# 1. Get recent market data
|
||||||
|
# 2. Run CNN prediction through Williams structure
|
||||||
|
# 3. Check if predicted pivot aligns with our intended action
|
||||||
|
# 4. Return threshold adjustment (0.0 to 0.1 typically)
|
||||||
|
|
||||||
|
# For now, return small adjustment to demonstrate concept
|
||||||
|
if hasattr(self.pivot_rl_trainer.williams, 'cnn_model') and self.pivot_rl_trainer.williams.cnn_model:
|
||||||
|
# CNN is available, could provide small threshold reduction for better entries
|
||||||
|
return 0.05 # 5% threshold reduction when CNN available
|
||||||
|
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting CNN threshold adjustment: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def update_dynamic_thresholds(self):
|
||||||
|
"""Update thresholds based on recent performance"""
|
||||||
|
try:
|
||||||
|
# Update thresholds in pivot trainer
|
||||||
|
self.pivot_rl_trainer.update_thresholds_based_on_performance()
|
||||||
|
|
||||||
|
# Get updated thresholds
|
||||||
|
thresholds = self.pivot_rl_trainer.get_current_thresholds()
|
||||||
|
old_entry = self.entry_threshold
|
||||||
|
old_exit = self.exit_threshold
|
||||||
|
|
||||||
|
self.entry_threshold = thresholds['entry_threshold']
|
||||||
|
self.exit_threshold = thresholds['exit_threshold']
|
||||||
|
self.uninvested_threshold = thresholds['uninvested_threshold']
|
||||||
|
|
||||||
|
# Log changes if significant
|
||||||
|
if abs(old_entry - self.entry_threshold) > 0.01 or abs(old_exit - self.exit_threshold) > 0.01:
|
||||||
|
logger.info(f"Threshold Update - Entry: {old_entry:.3f} -> {self.entry_threshold:.3f}, "
|
||||||
|
f"Exit: {old_exit:.3f} -> {self.exit_threshold:.3f}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating dynamic thresholds: {e}")
|
||||||
|
|
||||||
|
def calculate_enhanced_pivot_reward(self, trade_decision: Dict[str, Any],
|
||||||
|
market_data: pd.DataFrame,
|
||||||
|
trade_outcome: Dict[str, Any]) -> float:
|
||||||
|
"""Calculate reward using the enhanced pivot-based system"""
|
||||||
|
try:
|
||||||
|
return self.pivot_rl_trainer.calculate_pivot_based_reward(
|
||||||
|
trade_decision, market_data, trade_outcome
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating enhanced pivot reward: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
def _update_2_action_position(self, symbol: str, action: TradingAction):
|
def _update_2_action_position(self, symbol: str, action: TradingAction):
|
||||||
"""Update position tracking for strict 2-action system"""
|
"""Update position tracking for strict 2-action system"""
|
||||||
try:
|
try:
|
||||||
|
@ -54,7 +54,7 @@ run cnn training fron the dashboard as well - on each pivot point we inference a
|
|||||||
|
|
||||||
well, we have sell signals. don't we sell at the exact moment when we have long position and execute a sell signal? I see now we're totaly invested. change the model outputs too include cash signal (or learn to make decision to not enter position when we're not certain about where the market will go. this way we will only enter when the price move is clearly visible and most probable) learn to not be so certain when we made a bad trade (replay both entering and exiting position) we can do that by storing the models input data when we make a decision and then train with the known output. This is why we wanted to have a central data probider class which will be preparing the data for all the models er inference and train.
|
well, we have sell signals. don't we sell at the exact moment when we have long position and execute a sell signal? I see now we're totaly invested. change the model outputs too include cash signal (or learn to make decision to not enter position when we're not certain about where the market will go. this way we will only enter when the price move is clearly visible and most probable) learn to not be so certain when we made a bad trade (replay both entering and exiting position) we can do that by storing the models input data when we make a decision and then train with the known output. This is why we wanted to have a central data probider class which will be preparing the data for all the models er inference and train.
|
||||||
|
|
||||||
I see we're always invested.adjust the training, reward functions and possibly model outputs to include CASH signal where we sell our positions but we keep off the market. or use the orchestrator to learn to make that decison when gets uncertain signals from the expert models.mods hould learn to effectively spot setups in the market which are with high risk/reward level and act on theese
|
I see we're always invested. adjust the training, reward functions and possibly model outputs to include CASH signal where we sell our positions but we keep off the market. or use the orchestrator to learn to make that decison when gets uncertain signals from the expert models.mods hould learn to effectively spot setups in the market which are with high risk/reward level and act on theese
|
||||||
|
|
||||||
|
|
||||||
also, implement risk management (stop loss)
|
also, implement risk management (stop loss)
|
||||||
@ -63,3 +63,7 @@ make all dashboard processes run on the server without need of dashboard page to
|
|||||||
if that does not work I think we can make it simpler and easier to train if we have just 2 model actions buy/sell. we don't need hold signal, as until we have action we hold. And when we are long and we get a sell signal - we close. and enter short on consequtive sell signal. also, we will have different thresholds for entering and exiting. learning to enter when we are more certain
|
if that does not work I think we can make it simpler and easier to train if we have just 2 model actions buy/sell. we don't need hold signal, as until we have action we hold. And when we are long and we get a sell signal - we close. and enter short on consequtive sell signal. also, we will have different thresholds for entering and exiting. learning to enter when we are more certain
|
||||||
this will also help us simplify the training and our codebase to keep it easy to develop.
|
this will also help us simplify the training and our codebase to keep it easy to develop.
|
||||||
as our models are chained, it does not make sense anymore to train them separately. so remove all modes from main_clean and all referenced code. we use only web mode wherehe flow is: we collect data, calculate indicators and pivot points -> CNN -> RL => orchestrator -> broker/web
|
as our models are chained, it does not make sense anymore to train them separately. so remove all modes from main_clean and all referenced code. we use only web mode wherehe flow is: we collect data, calculate indicators and pivot points -> CNN -> RL => orchestrator -> broker/web
|
||||||
|
|
||||||
|
|
||||||
|
# DASH
|
||||||
|
add a row with small charts showing all the data we feed to the models: the 1m 1h 1d and reference (btc) ohlcv on the dashboard
|
320
test_enhanced_pivot_rl_system.py
Normal file
320
test_enhanced_pivot_rl_system.py
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
"""
|
||||||
|
Test Enhanced Pivot-Based RL System
|
||||||
|
|
||||||
|
Tests the new system with:
|
||||||
|
- Different thresholds for entry vs exit
|
||||||
|
- Pivot-based rewards
|
||||||
|
- CNN predictions for early pivot detection
|
||||||
|
- Uninvested rewards
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
|
||||||
|
stream=sys.stdout
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Add project root to Python path
|
||||||
|
import os
|
||||||
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from core.data_provider import DataProvider
|
||||||
|
from core.enhanced_orchestrator import EnhancedTradingOrchestrator
|
||||||
|
from training.enhanced_pivot_rl_trainer import EnhancedPivotRLTrainer, create_enhanced_pivot_trainer
|
||||||
|
|
||||||
|
def test_enhanced_pivot_thresholds():
|
||||||
|
"""Test the enhanced pivot-based threshold system"""
|
||||||
|
logger.info("=== Testing Enhanced Pivot-Based Thresholds ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create components
|
||||||
|
data_provider = DataProvider()
|
||||||
|
orchestrator = EnhancedTradingOrchestrator(
|
||||||
|
data_provider=data_provider,
|
||||||
|
enhanced_rl_training=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test threshold initialization
|
||||||
|
thresholds = orchestrator.pivot_rl_trainer.get_current_thresholds()
|
||||||
|
logger.info(f"Initial thresholds:")
|
||||||
|
logger.info(f" Entry: {thresholds['entry_threshold']:.3f}")
|
||||||
|
logger.info(f" Exit: {thresholds['exit_threshold']:.3f}")
|
||||||
|
logger.info(f" Uninvested: {thresholds['uninvested_threshold']:.3f}")
|
||||||
|
|
||||||
|
# Verify entry threshold is higher than exit threshold
|
||||||
|
assert thresholds['entry_threshold'] > thresholds['exit_threshold'], "Entry threshold should be higher than exit"
|
||||||
|
logger.info("✅ Entry threshold correctly higher than exit threshold")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error testing thresholds: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_pivot_reward_calculation():
|
||||||
|
"""Test the pivot-based reward calculation"""
|
||||||
|
logger.info("=== Testing Pivot-Based Reward Calculation ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create enhanced pivot trainer
|
||||||
|
data_provider = DataProvider()
|
||||||
|
pivot_trainer = create_enhanced_pivot_trainer(data_provider)
|
||||||
|
|
||||||
|
# Create mock trade decision and outcome
|
||||||
|
trade_decision = {
|
||||||
|
'action': 'BUY',
|
||||||
|
'confidence': 0.75,
|
||||||
|
'price': 2500.0,
|
||||||
|
'timestamp': datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
trade_outcome = {
|
||||||
|
'net_pnl': 15.50, # Profitable trade
|
||||||
|
'exit_price': 2515.0,
|
||||||
|
'duration': timedelta(minutes=45)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create mock market data
|
||||||
|
market_data = pd.DataFrame({
|
||||||
|
'open': np.random.normal(2500, 10, 100),
|
||||||
|
'high': np.random.normal(2510, 10, 100),
|
||||||
|
'low': np.random.normal(2490, 10, 100),
|
||||||
|
'close': np.random.normal(2500, 10, 100),
|
||||||
|
'volume': np.random.normal(1000, 100, 100)
|
||||||
|
})
|
||||||
|
market_data.index = pd.date_range(start=datetime.now() - timedelta(hours=2), periods=100, freq='1min')
|
||||||
|
|
||||||
|
# Calculate reward
|
||||||
|
reward = pivot_trainer.calculate_pivot_based_reward(
|
||||||
|
trade_decision, market_data, trade_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Calculated pivot-based reward: {reward:.3f}")
|
||||||
|
|
||||||
|
# Test should return a reasonable reward for profitable trade
|
||||||
|
assert -15.0 <= reward <= 10.0, f"Reward {reward} outside expected range"
|
||||||
|
logger.info("✅ Pivot-based reward calculation working")
|
||||||
|
|
||||||
|
# Test uninvested reward
|
||||||
|
low_conf_decision = {
|
||||||
|
'action': 'HOLD',
|
||||||
|
'confidence': 0.35, # Below uninvested threshold
|
||||||
|
'price': 2500.0,
|
||||||
|
'timestamp': datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
uninvested_reward = pivot_trainer._calculate_uninvested_rewards(low_conf_decision, 0.35)
|
||||||
|
logger.info(f"Uninvested reward for low confidence: {uninvested_reward:.3f}")
|
||||||
|
|
||||||
|
assert uninvested_reward > 0, "Should get positive reward for staying uninvested with low confidence"
|
||||||
|
logger.info("✅ Uninvested rewards working correctly")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error testing pivot rewards: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_confidence_adjustment():
|
||||||
|
"""Test confidence-based reward adjustments"""
|
||||||
|
logger.info("=== Testing Confidence-Based Adjustments ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pivot_trainer = create_enhanced_pivot_trainer()
|
||||||
|
|
||||||
|
# Test overconfidence penalty on loss
|
||||||
|
high_conf_loss = {
|
||||||
|
'action': 'BUY',
|
||||||
|
'confidence': 0.85, # High confidence
|
||||||
|
'price': 2500.0,
|
||||||
|
'timestamp': datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
loss_outcome = {
|
||||||
|
'net_pnl': -25.0, # Loss
|
||||||
|
'exit_price': 2475.0,
|
||||||
|
'duration': timedelta(hours=3)
|
||||||
|
}
|
||||||
|
|
||||||
|
confidence_adjustment = pivot_trainer._calculate_confidence_adjustment(
|
||||||
|
high_conf_loss, loss_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Confidence adjustment for overconfident loss: {confidence_adjustment:.3f}")
|
||||||
|
assert confidence_adjustment < 0, "Should penalize overconfidence on losses"
|
||||||
|
|
||||||
|
# Test underconfidence penalty on win
|
||||||
|
low_conf_win = {
|
||||||
|
'action': 'BUY',
|
||||||
|
'confidence': 0.35, # Low confidence
|
||||||
|
'price': 2500.0,
|
||||||
|
'timestamp': datetime.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
win_outcome = {
|
||||||
|
'net_pnl': 20.0, # Profit
|
||||||
|
'exit_price': 2520.0,
|
||||||
|
'duration': timedelta(minutes=30)
|
||||||
|
}
|
||||||
|
|
||||||
|
confidence_adjustment_2 = pivot_trainer._calculate_confidence_adjustment(
|
||||||
|
low_conf_win, win_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Confidence adjustment for underconfident win: {confidence_adjustment_2:.3f}")
|
||||||
|
# Should be small penalty or zero
|
||||||
|
|
||||||
|
logger.info("✅ Confidence adjustments working correctly")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error testing confidence adjustments: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_dynamic_threshold_updates():
|
||||||
|
"""Test dynamic threshold updating based on performance"""
|
||||||
|
logger.info("=== Testing Dynamic Threshold Updates ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pivot_trainer = create_enhanced_pivot_trainer()
|
||||||
|
|
||||||
|
# Get initial thresholds
|
||||||
|
initial_thresholds = pivot_trainer.get_current_thresholds()
|
||||||
|
logger.info(f"Initial thresholds: {initial_thresholds}")
|
||||||
|
|
||||||
|
# Simulate some poor performance (low win rate)
|
||||||
|
for i in range(25):
|
||||||
|
outcome = {
|
||||||
|
'timestamp': datetime.now(),
|
||||||
|
'action': 'BUY',
|
||||||
|
'confidence': 0.6,
|
||||||
|
'net_pnl': -5.0 if i < 20 else 10.0, # 20% win rate
|
||||||
|
'reward': -1.0 if i < 20 else 2.0,
|
||||||
|
'duration': timedelta(hours=2)
|
||||||
|
}
|
||||||
|
pivot_trainer.trade_outcomes.append(outcome)
|
||||||
|
|
||||||
|
# Update thresholds
|
||||||
|
pivot_trainer.update_thresholds_based_on_performance()
|
||||||
|
|
||||||
|
# Get updated thresholds
|
||||||
|
updated_thresholds = pivot_trainer.get_current_thresholds()
|
||||||
|
logger.info(f"Updated thresholds after poor performance: {updated_thresholds}")
|
||||||
|
|
||||||
|
# Entry threshold should increase (more selective) after poor performance
|
||||||
|
assert updated_thresholds['entry_threshold'] >= initial_thresholds['entry_threshold'], \
|
||||||
|
"Entry threshold should increase after poor performance"
|
||||||
|
|
||||||
|
logger.info("✅ Dynamic threshold updates working correctly")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error testing dynamic thresholds: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_cnn_integration():
|
||||||
|
"""Test CNN integration for pivot predictions"""
|
||||||
|
logger.info("=== Testing CNN Integration ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
data_provider = DataProvider()
|
||||||
|
orchestrator = EnhancedTradingOrchestrator(
|
||||||
|
data_provider=data_provider,
|
||||||
|
enhanced_rl_training=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if Williams structure is initialized with CNN
|
||||||
|
williams = orchestrator.pivot_rl_trainer.williams
|
||||||
|
logger.info(f"Williams CNN enabled: {williams.enable_cnn_feature}")
|
||||||
|
logger.info(f"Williams CNN model available: {williams.cnn_model is not None}")
|
||||||
|
|
||||||
|
# Test CNN threshold adjustment
|
||||||
|
from core.enhanced_orchestrator import MarketState
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
mock_market_state = MarketState(
|
||||||
|
symbol='ETH/USDT',
|
||||||
|
timestamp=datetime.now(),
|
||||||
|
prices={'1s': 2500.0},
|
||||||
|
features={'1s': np.array([])},
|
||||||
|
volatility=0.02,
|
||||||
|
volume=1000.0,
|
||||||
|
trend_strength=0.5,
|
||||||
|
market_regime='normal',
|
||||||
|
universal_data=None
|
||||||
|
)
|
||||||
|
|
||||||
|
cnn_adjustment = orchestrator._get_cnn_threshold_adjustment(
|
||||||
|
'ETH/USDT', 'BUY', mock_market_state
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"CNN threshold adjustment: {cnn_adjustment:.3f}")
|
||||||
|
assert 0.0 <= cnn_adjustment <= 0.1, "CNN adjustment should be reasonable"
|
||||||
|
|
||||||
|
logger.info("✅ CNN integration working correctly")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error testing CNN integration: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run_all_tests():
|
||||||
|
"""Run all enhanced pivot RL system tests"""
|
||||||
|
logger.info("🚀 Starting Enhanced Pivot RL System Tests")
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
test_enhanced_pivot_thresholds,
|
||||||
|
test_pivot_reward_calculation,
|
||||||
|
test_confidence_adjustment,
|
||||||
|
test_dynamic_threshold_updates,
|
||||||
|
test_cnn_integration
|
||||||
|
]
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
total = len(tests)
|
||||||
|
|
||||||
|
for test_func in tests:
|
||||||
|
try:
|
||||||
|
if test_func():
|
||||||
|
passed += 1
|
||||||
|
logger.info(f"✅ {test_func.__name__} PASSED")
|
||||||
|
else:
|
||||||
|
logger.error(f"❌ {test_func.__name__} FAILED")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ {test_func.__name__} ERROR: {e}")
|
||||||
|
|
||||||
|
logger.info(f"\n📊 Test Results: {passed}/{total} tests passed")
|
||||||
|
|
||||||
|
if passed == total:
|
||||||
|
logger.info("🎉 All Enhanced Pivot RL System tests PASSED!")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"⚠️ {total - passed} tests FAILED")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success = run_all_tests()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info("\n🔥 Enhanced Pivot RL System is ready for deployment!")
|
||||||
|
logger.info("Key improvements:")
|
||||||
|
logger.info(" ✅ Higher entry threshold than exit threshold")
|
||||||
|
logger.info(" ✅ Pivot-based reward calculation")
|
||||||
|
logger.info(" ✅ CNN predictions for early pivot detection")
|
||||||
|
logger.info(" ✅ Rewards for staying uninvested when uncertain")
|
||||||
|
logger.info(" ✅ Confidence-based reward adjustments")
|
||||||
|
logger.info(" ✅ Dynamic threshold learning from performance")
|
||||||
|
else:
|
||||||
|
logger.error("\n❌ Enhanced Pivot RL System has issues that need fixing")
|
||||||
|
|
||||||
|
sys.exit(0 if success else 1)
|
584
training/enhanced_pivot_rl_trainer.py
Normal file
584
training/enhanced_pivot_rl_trainer.py
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
"""
|
||||||
|
Enhanced Pivot-Based RL Trainer
|
||||||
|
|
||||||
|
Integrates Williams Market Structure pivot points with CNN predictions
|
||||||
|
for improved trading decisions and training rewards.
|
||||||
|
|
||||||
|
Key Features:
|
||||||
|
- Train RL model to buy/sell at local pivot points
|
||||||
|
- CNN predicts next pivot to avoid late signals
|
||||||
|
- Different thresholds for entry vs exit
|
||||||
|
- Rewards for staying uninvested when uncertain
|
||||||
|
- Uncertainty-based confidence adjustment
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from collections import deque, namedtuple
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Dict, List, Optional, Tuple, Any, Union, TYPE_CHECKING
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from core.config import get_config
|
||||||
|
from core.data_provider import DataProvider
|
||||||
|
from training.williams_market_structure import WilliamsMarketStructure, SwingType, SwingPoint
|
||||||
|
|
||||||
|
# Use TYPE_CHECKING to avoid circular import
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.enhanced_orchestrator import EnhancedTradingOrchestrator
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class PivotReward:
|
||||||
|
"""Reward structure for pivot-based trading decisions"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Pivot-based reward weights
|
||||||
|
self.pivot_hit_bonus = 2.0 # Bonus for trading at actual pivot points
|
||||||
|
self.pivot_anticipation_bonus = 1.5 # Bonus for trading before pivot (CNN prediction)
|
||||||
|
self.wrong_direction_penalty = -1.0 # Penalty for trading opposite to pivot direction
|
||||||
|
self.late_entry_penalty = -0.5 # Penalty for entering after pivot is confirmed
|
||||||
|
|
||||||
|
# Stay uninvested rewards
|
||||||
|
self.uninvested_reward = 0.1 # Small positive reward for staying out of poor setups
|
||||||
|
self.avoid_false_signal_bonus = 0.5 # Bonus for avoiding false signals
|
||||||
|
|
||||||
|
# Uncertainty penalties
|
||||||
|
self.overconfidence_penalty = -0.3 # Penalty for being overconfident on losses
|
||||||
|
self.underconfidence_penalty = -0.1 # Small penalty for being underconfident on wins
|
||||||
|
|
||||||
|
class EnhancedPivotRLTrainer:
|
||||||
|
"""Enhanced RL trainer focused on Williams pivot points and CNN predictions"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
data_provider: DataProvider = None,
|
||||||
|
orchestrator: Optional["EnhancedTradingOrchestrator"] = None):
|
||||||
|
|
||||||
|
self.config = get_config()
|
||||||
|
self.data_provider = data_provider or DataProvider()
|
||||||
|
self.orchestrator = orchestrator
|
||||||
|
|
||||||
|
# Initialize Williams Market Structure with CNN
|
||||||
|
self.williams = WilliamsMarketStructure(
|
||||||
|
swing_strengths=[2, 4, 6, 8, 10], # Multiple strengths for better detection
|
||||||
|
enable_cnn_feature=True,
|
||||||
|
training_data_provider=data_provider
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pivot tracking
|
||||||
|
self.recent_pivots = deque(maxlen=50)
|
||||||
|
self.pivot_predictions = deque(maxlen=20)
|
||||||
|
self.trade_outcomes = deque(maxlen=100)
|
||||||
|
|
||||||
|
# Threshold management - different for entry vs exit
|
||||||
|
self.entry_threshold = 0.65 # Higher threshold for entering positions
|
||||||
|
self.exit_threshold = 0.35 # Lower threshold for exiting positions
|
||||||
|
self.max_uninvested_reward_threshold = 0.60 # Stay out if confidence below this
|
||||||
|
|
||||||
|
# Confidence learning parameters
|
||||||
|
self.confidence_history = deque(maxlen=200)
|
||||||
|
self.mistake_severity_tracker = deque(maxlen=50)
|
||||||
|
|
||||||
|
# Reward calculator
|
||||||
|
self.pivot_reward = PivotReward()
|
||||||
|
|
||||||
|
logger.info("Enhanced Pivot RL Trainer initialized")
|
||||||
|
logger.info(f"Entry threshold: {self.entry_threshold:.2%}")
|
||||||
|
logger.info(f"Exit threshold: {self.exit_threshold:.2%}")
|
||||||
|
logger.info(f"Uninvested reward threshold: {self.max_uninvested_reward_threshold:.2%}")
|
||||||
|
|
||||||
|
def calculate_pivot_based_reward(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
market_data: pd.DataFrame,
|
||||||
|
trade_outcome: Dict[str, Any]) -> float:
|
||||||
|
"""
|
||||||
|
Calculate enhanced reward based on pivot points and CNN predictions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
trade_decision: The trading decision made by the model
|
||||||
|
market_data: Market data context
|
||||||
|
trade_outcome: Actual trade outcome
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Enhanced reward score
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
base_pnl = trade_outcome.get('net_pnl', 0.0)
|
||||||
|
confidence = trade_decision.get('confidence', 0.5)
|
||||||
|
action = trade_decision.get('action', 'HOLD')
|
||||||
|
entry_price = trade_decision.get('price', 0.0)
|
||||||
|
exit_price = trade_outcome.get('exit_price', entry_price)
|
||||||
|
duration = trade_outcome.get('duration', timedelta(0))
|
||||||
|
|
||||||
|
# Base PnL reward
|
||||||
|
base_reward = base_pnl / 5.0
|
||||||
|
|
||||||
|
# 1. Pivot Point Analysis Rewards
|
||||||
|
pivot_reward = self._calculate_pivot_rewards(
|
||||||
|
trade_decision, market_data, trade_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. CNN Prediction Accuracy Rewards
|
||||||
|
cnn_reward = self._calculate_cnn_prediction_rewards(
|
||||||
|
trade_decision, market_data, trade_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Uninvested Period Rewards
|
||||||
|
uninvested_reward = self._calculate_uninvested_rewards(
|
||||||
|
trade_decision, confidence
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Uncertainty-based Confidence Adjustment
|
||||||
|
confidence_adjustment = self._calculate_confidence_adjustment(
|
||||||
|
trade_decision, trade_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5. Time efficiency with pivot context
|
||||||
|
time_reward = self._calculate_time_efficiency_reward(
|
||||||
|
duration, base_pnl, market_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combine all rewards
|
||||||
|
total_reward = (
|
||||||
|
base_reward +
|
||||||
|
pivot_reward +
|
||||||
|
cnn_reward +
|
||||||
|
uninvested_reward +
|
||||||
|
confidence_adjustment +
|
||||||
|
time_reward
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log detailed reward breakdown
|
||||||
|
self._log_reward_breakdown(
|
||||||
|
trade_decision, trade_outcome, {
|
||||||
|
'base': base_reward,
|
||||||
|
'pivot': pivot_reward,
|
||||||
|
'cnn': cnn_reward,
|
||||||
|
'uninvested': uninvested_reward,
|
||||||
|
'confidence': confidence_adjustment,
|
||||||
|
'time': time_reward,
|
||||||
|
'total': total_reward
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Track for learning
|
||||||
|
self._track_reward_outcome(trade_decision, trade_outcome, total_reward)
|
||||||
|
|
||||||
|
return np.clip(total_reward, -15.0, 10.0)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating pivot-based reward: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _calculate_pivot_rewards(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
market_data: pd.DataFrame,
|
||||||
|
trade_outcome: Dict[str, Any]) -> float:
|
||||||
|
"""Calculate rewards based on proximity to pivot points"""
|
||||||
|
try:
|
||||||
|
entry_price = trade_decision.get('price', 0.0)
|
||||||
|
action = trade_decision.get('action', 'HOLD')
|
||||||
|
entry_time = trade_decision.get('timestamp', datetime.now())
|
||||||
|
net_pnl = trade_outcome.get('net_pnl', 0.0)
|
||||||
|
|
||||||
|
# Find recent pivot points from Williams analysis
|
||||||
|
ohlcv_array = self._convert_dataframe_to_ohlcv_array(market_data)
|
||||||
|
if ohlcv_array is None or len(ohlcv_array) < 20:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Get pivot points from Williams structure
|
||||||
|
structure_levels = self.williams.calculate_recursive_pivot_points(ohlcv_array)
|
||||||
|
if not structure_levels or 'level_0' not in structure_levels:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
level_0_pivots = structure_levels['level_0'].swing_points
|
||||||
|
if not level_0_pivots:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Find closest pivot to entry
|
||||||
|
closest_pivot = self._find_closest_pivot(entry_price, entry_time, level_0_pivots)
|
||||||
|
if not closest_pivot:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Calculate distance to pivot (price and time)
|
||||||
|
price_distance = abs(entry_price - closest_pivot.price) / closest_pivot.price
|
||||||
|
time_distance = abs((entry_time - closest_pivot.timestamp).total_seconds()) / 3600.0 # hours
|
||||||
|
|
||||||
|
pivot_reward = 0.0
|
||||||
|
|
||||||
|
# Reward trading at or near pivot points
|
||||||
|
if price_distance < 0.005: # Within 0.5% of pivot
|
||||||
|
if time_distance < 0.5: # Within 30 minutes
|
||||||
|
pivot_reward += self.pivot_reward.pivot_hit_bonus
|
||||||
|
logger.debug(f"PIVOT HIT BONUS: {self.pivot_reward.pivot_hit_bonus:.2f}")
|
||||||
|
|
||||||
|
# Check if trade direction aligns with pivot
|
||||||
|
if self._trade_aligns_with_pivot(action, closest_pivot, net_pnl):
|
||||||
|
pivot_reward += self.pivot_reward.pivot_anticipation_bonus
|
||||||
|
logger.debug(f"PIVOT DIRECTION BONUS: {self.pivot_reward.pivot_anticipation_bonus:.2f}")
|
||||||
|
else:
|
||||||
|
pivot_reward += self.pivot_reward.wrong_direction_penalty
|
||||||
|
logger.debug(f"WRONG DIRECTION PENALTY: {self.pivot_reward.wrong_direction_penalty:.2f}")
|
||||||
|
|
||||||
|
# Penalty for late entry after pivot confirmation
|
||||||
|
if time_distance > 2.0: # More than 2 hours after pivot
|
||||||
|
pivot_reward += self.pivot_reward.late_entry_penalty
|
||||||
|
logger.debug(f"LATE ENTRY PENALTY: {self.pivot_reward.late_entry_penalty:.2f}")
|
||||||
|
|
||||||
|
return pivot_reward
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating pivot rewards: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _calculate_cnn_prediction_rewards(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
market_data: pd.DataFrame,
|
||||||
|
trade_outcome: Dict[str, Any]) -> float:
|
||||||
|
"""Calculate rewards based on CNN pivot predictions"""
|
||||||
|
try:
|
||||||
|
# Check if we have CNN predictions available
|
||||||
|
if not hasattr(self.williams, 'cnn_model') or not self.williams.cnn_model:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
action = trade_decision.get('action', 'HOLD')
|
||||||
|
confidence = trade_decision.get('confidence', 0.5)
|
||||||
|
net_pnl = trade_outcome.get('net_pnl', 0.0)
|
||||||
|
|
||||||
|
# Get latest CNN prediction if available
|
||||||
|
# This would be the prediction made before the trade
|
||||||
|
cnn_prediction = self._get_latest_cnn_prediction()
|
||||||
|
if not cnn_prediction:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
cnn_reward = 0.0
|
||||||
|
|
||||||
|
# Reward for following CNN predictions that turn out correct
|
||||||
|
predicted_direction = self._interpret_cnn_prediction(cnn_prediction)
|
||||||
|
|
||||||
|
if predicted_direction == action and net_pnl > 0:
|
||||||
|
# CNN prediction was correct and we followed it
|
||||||
|
cnn_reward += 1.0 * confidence # Scale by confidence
|
||||||
|
logger.debug(f"CNN CORRECT FOLLOW: +{1.0 * confidence:.2f}")
|
||||||
|
|
||||||
|
elif predicted_direction != action and net_pnl < 0:
|
||||||
|
# We didn't follow CNN and it was right (we were wrong)
|
||||||
|
cnn_reward -= 0.5
|
||||||
|
logger.debug(f"CNN IGNORE PENALTY: -0.5")
|
||||||
|
|
||||||
|
elif predicted_direction == action and net_pnl < 0:
|
||||||
|
# We followed CNN but it was wrong
|
||||||
|
cnn_reward -= 0.2 # Small penalty, CNN predictions can be wrong
|
||||||
|
logger.debug(f"CNN WRONG FOLLOW: -0.2")
|
||||||
|
|
||||||
|
return cnn_reward
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating CNN prediction rewards: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _calculate_uninvested_rewards(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
confidence: float) -> float:
|
||||||
|
"""Calculate rewards for staying uninvested when uncertain"""
|
||||||
|
try:
|
||||||
|
action = trade_decision.get('action', 'HOLD')
|
||||||
|
|
||||||
|
# Reward staying out when confidence is low
|
||||||
|
if action == 'HOLD' and confidence < self.max_uninvested_reward_threshold:
|
||||||
|
uninvested_reward = self.pivot_reward.uninvested_reward
|
||||||
|
|
||||||
|
# Bonus for avoiding very uncertain setups
|
||||||
|
if confidence < 0.4:
|
||||||
|
uninvested_reward += self.pivot_reward.avoid_false_signal_bonus
|
||||||
|
logger.debug(f"AVOID FALSE SIGNAL BONUS: +{self.pivot_reward.avoid_false_signal_bonus:.2f}")
|
||||||
|
|
||||||
|
logger.debug(f"UNINVESTED REWARD: +{uninvested_reward:.2f}")
|
||||||
|
return uninvested_reward
|
||||||
|
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating uninvested rewards: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _calculate_confidence_adjustment(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
trade_outcome: Dict[str, Any]) -> float:
|
||||||
|
"""Adjust rewards based on confidence vs outcome to reduce overconfidence"""
|
||||||
|
try:
|
||||||
|
confidence = trade_decision.get('confidence', 0.5)
|
||||||
|
net_pnl = trade_outcome.get('net_pnl', 0.0)
|
||||||
|
|
||||||
|
confidence_adjustment = 0.0
|
||||||
|
|
||||||
|
# Track mistake severity
|
||||||
|
mistake_severity = abs(net_pnl) if net_pnl < 0 else 0.0
|
||||||
|
self.mistake_severity_tracker.append(mistake_severity)
|
||||||
|
|
||||||
|
# Penalize overconfidence on losses
|
||||||
|
if net_pnl < 0 and confidence > 0.7:
|
||||||
|
# High confidence but loss - penalize overconfidence
|
||||||
|
overconfidence_factor = (confidence - 0.7) / 0.3 # 0-1 scale
|
||||||
|
severity_factor = min(mistake_severity / 2.0, 1.0) # Scale by loss size
|
||||||
|
|
||||||
|
penalty = self.pivot_reward.overconfidence_penalty * overconfidence_factor * severity_factor
|
||||||
|
confidence_adjustment += penalty
|
||||||
|
|
||||||
|
logger.debug(f"OVERCONFIDENCE PENALTY: {penalty:.2f} (conf: {confidence:.2f}, loss: ${net_pnl:.2f})")
|
||||||
|
|
||||||
|
# Small penalty for underconfidence on wins
|
||||||
|
elif net_pnl > 0 and confidence < 0.4:
|
||||||
|
underconfidence_factor = (0.4 - confidence) / 0.4 # 0-1 scale
|
||||||
|
penalty = self.pivot_reward.underconfidence_penalty * underconfidence_factor
|
||||||
|
confidence_adjustment += penalty
|
||||||
|
|
||||||
|
logger.debug(f"UNDERCONFIDENCE PENALTY: {penalty:.2f} (conf: {confidence:.2f}, profit: ${net_pnl:.2f})")
|
||||||
|
|
||||||
|
# Update confidence learning
|
||||||
|
self._update_confidence_learning(confidence, net_pnl, mistake_severity)
|
||||||
|
|
||||||
|
return confidence_adjustment
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating confidence adjustment: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def _calculate_time_efficiency_reward(self,
|
||||||
|
duration: timedelta,
|
||||||
|
net_pnl: float,
|
||||||
|
market_data: pd.DataFrame) -> float:
|
||||||
|
"""Calculate time-based rewards considering market context"""
|
||||||
|
try:
|
||||||
|
duration_hours = duration.total_seconds() / 3600.0
|
||||||
|
|
||||||
|
# Quick profitable trades get bonus
|
||||||
|
if net_pnl > 0 and duration_hours < 0.5: # Less than 30 minutes
|
||||||
|
return 0.3
|
||||||
|
|
||||||
|
# Holding losses too long gets penalty
|
||||||
|
elif net_pnl < 0 and duration_hours > 2.0: # More than 2 hours
|
||||||
|
return -0.5
|
||||||
|
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating time efficiency reward: {e}")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def update_thresholds_based_on_performance(self):
|
||||||
|
"""Dynamically adjust entry/exit thresholds based on recent performance"""
|
||||||
|
try:
|
||||||
|
if len(self.trade_outcomes) < 20:
|
||||||
|
return
|
||||||
|
|
||||||
|
recent_outcomes = list(self.trade_outcomes)[-20:]
|
||||||
|
|
||||||
|
# Calculate win rate and average PnL
|
||||||
|
wins = sum(1 for outcome in recent_outcomes if outcome['net_pnl'] > 0)
|
||||||
|
win_rate = wins / len(recent_outcomes)
|
||||||
|
avg_pnl = np.mean([outcome['net_pnl'] for outcome in recent_outcomes])
|
||||||
|
|
||||||
|
# Adjust thresholds based on performance
|
||||||
|
if win_rate < 0.4: # Low win rate - be more selective
|
||||||
|
self.entry_threshold = min(self.entry_threshold + 0.02, 0.80)
|
||||||
|
logger.info(f"Low win rate ({win_rate:.2%}) - increased entry threshold to {self.entry_threshold:.2%}")
|
||||||
|
|
||||||
|
elif win_rate > 0.6 and avg_pnl > 0: # High win rate - can be more aggressive
|
||||||
|
self.entry_threshold = max(self.entry_threshold - 0.01, 0.50)
|
||||||
|
logger.info(f"High win rate ({win_rate:.2%}) - decreased entry threshold to {self.entry_threshold:.2%}")
|
||||||
|
|
||||||
|
# Adjust exit threshold based on loss severity
|
||||||
|
avg_loss_severity = np.mean(list(self.mistake_severity_tracker)) if self.mistake_severity_tracker else 0
|
||||||
|
|
||||||
|
if avg_loss_severity > 1.0: # Large average losses
|
||||||
|
self.exit_threshold = max(self.exit_threshold - 0.01, 0.20)
|
||||||
|
logger.info(f"High loss severity - decreased exit threshold to {self.exit_threshold:.2%}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating thresholds: {e}")
|
||||||
|
|
||||||
|
def get_current_thresholds(self) -> Dict[str, float]:
|
||||||
|
"""Get current entry and exit thresholds"""
|
||||||
|
return {
|
||||||
|
'entry_threshold': self.entry_threshold,
|
||||||
|
'exit_threshold': self.exit_threshold,
|
||||||
|
'uninvested_threshold': self.max_uninvested_reward_threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper methods
|
||||||
|
|
||||||
|
def _convert_dataframe_to_ohlcv_array(self, df: pd.DataFrame) -> Optional[np.ndarray]:
|
||||||
|
"""Convert pandas DataFrame to numpy array for Williams analysis"""
|
||||||
|
try:
|
||||||
|
if df.empty:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ensure we have required columns
|
||||||
|
required_cols = ['open', 'high', 'low', 'close', 'volume']
|
||||||
|
if not all(col in df.columns for col in required_cols):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Convert to numpy array
|
||||||
|
timestamps = df.index.astype(np.int64) // 10**9 # Convert to Unix timestamp
|
||||||
|
ohlcv_array = np.column_stack([
|
||||||
|
timestamps,
|
||||||
|
df['open'].values,
|
||||||
|
df['high'].values,
|
||||||
|
df['low'].values,
|
||||||
|
df['close'].values,
|
||||||
|
df['volume'].values
|
||||||
|
])
|
||||||
|
|
||||||
|
return ohlcv_array.astype(np.float64)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error converting DataFrame to OHLCV array: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _find_closest_pivot(self,
|
||||||
|
entry_price: float,
|
||||||
|
entry_time: datetime,
|
||||||
|
pivots: List[SwingPoint]) -> Optional[SwingPoint]:
|
||||||
|
"""Find the closest pivot point to the trade entry"""
|
||||||
|
try:
|
||||||
|
if not pivots:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Find pivot closest in time and price
|
||||||
|
best_pivot = None
|
||||||
|
best_score = float('inf')
|
||||||
|
|
||||||
|
for pivot in pivots:
|
||||||
|
time_diff = abs((entry_time - pivot.timestamp).total_seconds()) / 3600.0
|
||||||
|
price_diff = abs(entry_price - pivot.price) / pivot.price
|
||||||
|
|
||||||
|
# Combined score (weighted by time and price proximity)
|
||||||
|
score = time_diff * 0.3 + price_diff * 100 # Weight price difference more heavily
|
||||||
|
|
||||||
|
if score < best_score:
|
||||||
|
best_score = score
|
||||||
|
best_pivot = pivot
|
||||||
|
|
||||||
|
return best_pivot
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error finding closest pivot: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _trade_aligns_with_pivot(self,
|
||||||
|
action: str,
|
||||||
|
pivot: SwingPoint,
|
||||||
|
net_pnl: float) -> bool:
|
||||||
|
"""Check if trade direction aligns with pivot type and was profitable"""
|
||||||
|
try:
|
||||||
|
if net_pnl <= 0: # Only consider profitable trades as aligned
|
||||||
|
return False
|
||||||
|
|
||||||
|
if action == 'BUY' and pivot.swing_type == SwingType.SWING_LOW:
|
||||||
|
return True # Bought at/near swing low
|
||||||
|
elif action == 'SELL' and pivot.swing_type == SwingType.SWING_HIGH:
|
||||||
|
return True # Sold at/near swing high
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error checking trade alignment: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_latest_cnn_prediction(self) -> Optional[np.ndarray]:
|
||||||
|
"""Get the latest CNN prediction from Williams structure"""
|
||||||
|
try:
|
||||||
|
# This would access the Williams CNN model's latest prediction
|
||||||
|
# For now, return None if not available
|
||||||
|
if hasattr(self.williams, 'latest_cnn_prediction'):
|
||||||
|
return self.williams.latest_cnn_prediction
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting CNN prediction: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _interpret_cnn_prediction(self, prediction: np.ndarray) -> str:
|
||||||
|
"""Interpret CNN prediction array to trading action"""
|
||||||
|
try:
|
||||||
|
if len(prediction) < 2:
|
||||||
|
return 'HOLD'
|
||||||
|
|
||||||
|
# Assuming prediction format: [type, price] for level 0
|
||||||
|
predicted_type = prediction[0] # 0 = LOW, 1 = HIGH
|
||||||
|
|
||||||
|
if predicted_type > 0.5:
|
||||||
|
return 'SELL' # Expecting swing high - sell
|
||||||
|
else:
|
||||||
|
return 'BUY' # Expecting swing low - buy
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error interpreting CNN prediction: {e}")
|
||||||
|
return 'HOLD'
|
||||||
|
|
||||||
|
def _update_confidence_learning(self,
|
||||||
|
confidence: float,
|
||||||
|
net_pnl: float,
|
||||||
|
mistake_severity: float):
|
||||||
|
"""Update confidence learning parameters"""
|
||||||
|
try:
|
||||||
|
self.confidence_history.append({
|
||||||
|
'confidence': confidence,
|
||||||
|
'net_pnl': net_pnl,
|
||||||
|
'mistake_severity': mistake_severity,
|
||||||
|
'timestamp': datetime.now()
|
||||||
|
})
|
||||||
|
|
||||||
|
# Periodically update thresholds based on confidence patterns
|
||||||
|
if len(self.confidence_history) % 10 == 0:
|
||||||
|
self.update_thresholds_based_on_performance()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating confidence learning: {e}")
|
||||||
|
|
||||||
|
def _track_reward_outcome(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
trade_outcome: Dict[str, Any],
|
||||||
|
total_reward: float):
|
||||||
|
"""Track reward outcomes for analysis"""
|
||||||
|
try:
|
||||||
|
outcome_record = {
|
||||||
|
'timestamp': datetime.now(),
|
||||||
|
'action': trade_decision.get('action'),
|
||||||
|
'confidence': trade_decision.get('confidence'),
|
||||||
|
'net_pnl': trade_outcome.get('net_pnl'),
|
||||||
|
'reward': total_reward,
|
||||||
|
'duration': trade_outcome.get('duration')
|
||||||
|
}
|
||||||
|
|
||||||
|
self.trade_outcomes.append(outcome_record)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error tracking reward outcome: {e}")
|
||||||
|
|
||||||
|
def _log_reward_breakdown(self,
|
||||||
|
trade_decision: Dict[str, Any],
|
||||||
|
trade_outcome: Dict[str, Any],
|
||||||
|
rewards: Dict[str, float]):
|
||||||
|
"""Log detailed reward breakdown"""
|
||||||
|
try:
|
||||||
|
action = trade_decision.get('action', 'UNKNOWN')
|
||||||
|
confidence = trade_decision.get('confidence', 0.0)
|
||||||
|
net_pnl = trade_outcome.get('net_pnl', 0.0)
|
||||||
|
|
||||||
|
logger.info(f"[REWARD] {action} (conf: {confidence:.2%}) PnL: ${net_pnl:.2f} -> Total: {rewards['total']:.2f}")
|
||||||
|
logger.debug(f" Base: {rewards['base']:.2f}, Pivot: {rewards['pivot']:.2f}, CNN: {rewards['cnn']:.2f}")
|
||||||
|
logger.debug(f" Uninvested: {rewards['uninvested']:.2f}, Confidence: {rewards['confidence']:.2f}, Time: {rewards['time']:.2f}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error logging reward breakdown: {e}")
|
||||||
|
|
||||||
|
def create_enhanced_pivot_trainer(data_provider: DataProvider = None,
|
||||||
|
orchestrator: Optional["EnhancedTradingOrchestrator"] = None) -> EnhancedPivotRLTrainer:
|
||||||
|
"""Factory function to create enhanced pivot trainer"""
|
||||||
|
return EnhancedPivotRLTrainer(data_provider, orchestrator)
|
@ -55,6 +55,7 @@ try:
|
|||||||
from core.enhanced_orchestrator import EnhancedTradingOrchestrator
|
from core.enhanced_orchestrator import EnhancedTradingOrchestrator
|
||||||
from core.universal_data_adapter import UniversalDataAdapter
|
from core.universal_data_adapter import UniversalDataAdapter
|
||||||
from core.unified_data_stream import UnifiedDataStream, TrainingDataPacket, UIDataPacket
|
from core.unified_data_stream import UnifiedDataStream, TrainingDataPacket, UIDataPacket
|
||||||
|
from training.enhanced_pivot_rl_trainer import EnhancedPivotRLTrainer, create_enhanced_pivot_trainer
|
||||||
ENHANCED_RL_AVAILABLE = True
|
ENHANCED_RL_AVAILABLE = True
|
||||||
logger.info("Enhanced RL training components available")
|
logger.info("Enhanced RL training components available")
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
@ -361,6 +362,20 @@ class TradingDashboard:
|
|||||||
self.williams_structure = None
|
self.williams_structure = None
|
||||||
logger.warning("Williams Market Structure not available")
|
logger.warning("Williams Market Structure not available")
|
||||||
|
|
||||||
|
# Initialize Enhanced Pivot RL Trainer for better position management
|
||||||
|
try:
|
||||||
|
self.pivot_rl_trainer = create_enhanced_pivot_trainer(
|
||||||
|
data_provider=self.data_provider,
|
||||||
|
orchestrator=self.orchestrator
|
||||||
|
)
|
||||||
|
logger.info("Enhanced Pivot RL Trainer initialized for better entry/exit decisions")
|
||||||
|
logger.info(f"Entry threshold: {self.pivot_rl_trainer.get_current_thresholds()['entry_threshold']:.1%}")
|
||||||
|
logger.info(f"Exit threshold: {self.pivot_rl_trainer.get_current_thresholds()['exit_threshold']:.1%}")
|
||||||
|
logger.info(f"Uninvested threshold: {self.pivot_rl_trainer.get_current_thresholds()['uninvested_threshold']:.1%}")
|
||||||
|
except Exception as e:
|
||||||
|
self.pivot_rl_trainer = None
|
||||||
|
logger.warning(f"Enhanced Pivot RL Trainer not available: {e}")
|
||||||
|
|
||||||
def _to_local_timezone(self, dt: datetime) -> datetime:
|
def _to_local_timezone(self, dt: datetime) -> datetime:
|
||||||
"""Convert datetime to configured local timezone"""
|
"""Convert datetime to configured local timezone"""
|
||||||
try:
|
try:
|
||||||
@ -4358,7 +4373,47 @@ class TradingDashboard:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _calculate_rl_reward(self, closed_trade):
|
def _calculate_rl_reward(self, closed_trade):
|
||||||
"""Calculate enhanced reward for RL training with proper penalties for losing trades"""
|
"""Calculate enhanced reward for RL training using pivot-based system"""
|
||||||
|
try:
|
||||||
|
# Extract trade information
|
||||||
|
trade_decision = {
|
||||||
|
'action': closed_trade.get('side', 'HOLD'),
|
||||||
|
'confidence': closed_trade.get('confidence', 0.5),
|
||||||
|
'price': closed_trade.get('entry_price', 0.0),
|
||||||
|
'timestamp': closed_trade.get('entry_time', datetime.now())
|
||||||
|
}
|
||||||
|
|
||||||
|
trade_outcome = {
|
||||||
|
'net_pnl': closed_trade.get('net_pnl', 0),
|
||||||
|
'exit_price': closed_trade.get('exit_price', 0.0),
|
||||||
|
'duration': closed_trade.get('duration', timedelta(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get market data context for pivot analysis
|
||||||
|
symbol = closed_trade.get('symbol', 'ETH/USDT')
|
||||||
|
trade_time = trade_decision['timestamp']
|
||||||
|
market_data = self._get_training_context_data(symbol, trade_time, lookback_minutes=120)
|
||||||
|
|
||||||
|
# Use enhanced pivot-based reward if orchestrator is available
|
||||||
|
if hasattr(self, 'orchestrator') and self.orchestrator and hasattr(self.orchestrator, 'calculate_enhanced_pivot_reward'):
|
||||||
|
enhanced_reward = self.orchestrator.calculate_enhanced_pivot_reward(
|
||||||
|
trade_decision, market_data, trade_outcome
|
||||||
|
)
|
||||||
|
|
||||||
|
# Log the enhanced reward
|
||||||
|
logger.info(f"[ENHANCED_REWARD] Using pivot-based reward: {enhanced_reward:.3f}")
|
||||||
|
return enhanced_reward
|
||||||
|
|
||||||
|
# Fallback to original reward calculation if enhanced system not available
|
||||||
|
logger.warning("[ENHANCED_REWARD] Falling back to original reward calculation")
|
||||||
|
return self._calculate_original_rl_reward(closed_trade)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error calculating enhanced RL reward: {e}")
|
||||||
|
return self._calculate_original_rl_reward(closed_trade)
|
||||||
|
|
||||||
|
def _calculate_original_rl_reward(self, closed_trade):
|
||||||
|
"""Original RL reward calculation as fallback"""
|
||||||
try:
|
try:
|
||||||
net_pnl = closed_trade.get('net_pnl', 0)
|
net_pnl = closed_trade.get('net_pnl', 0)
|
||||||
duration = closed_trade.get('duration', timedelta(0))
|
duration = closed_trade.get('duration', timedelta(0))
|
||||||
@ -4419,7 +4474,7 @@ class TradingDashboard:
|
|||||||
return reward
|
return reward
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error calculating RL reward: {e}")
|
logger.warning(f"Error calculating original RL reward: {e}")
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
def _execute_rl_training_step(self, training_episode):
|
def _execute_rl_training_step(self, training_episode):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user