pivot improvement

This commit is contained in:
Dobromir Popov 2025-05-30 20:36:42 +03:00
parent 249ec6f5a7
commit d870f74d0c
7 changed files with 1311 additions and 645 deletions

1
.cursorignore Normal file
View File

@ -0,0 +1 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)

View File

@ -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
} }

View File

@ -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)
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:
return None
try: try:
# Get best prediction if not predictions:
best_pred = max(predictions, key=lambda p: p.overall_confidence) return None
raw_action = best_pred.overall_action
confidence = best_pred.overall_confidence
# Get current position for this symbol # Get the best prediction
current_position = self.current_positions.get(symbol, {'side': 'FLAT'}) best_pred = max(predictions, key=lambda p: p.confidence)
position_side = current_position['side'] confidence = best_pred.confidence
raw_action = best_pred.action
# STRICT LOGIC: Determine action type # Update dynamic thresholds periodically
if hasattr(self, '_last_threshold_update'):
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()
# 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:

View File

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

View 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)

View 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)

View File

@ -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):