Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,1768 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
warnings.filterwarnings("ignore")
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
import time
|
| 7 |
+
import tempfile
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import pandas as pd
|
| 11 |
+
import yfinance as yf
|
| 12 |
+
import gradio as gr
|
| 13 |
+
import plotly.graph_objects as go
|
| 14 |
+
from plotly.subplots import make_subplots
|
| 15 |
+
|
| 16 |
+
from sklearn.impute import SimpleImputer
|
| 17 |
+
from sklearn.preprocessing import StandardScaler
|
| 18 |
+
from sklearn.pipeline import Pipeline
|
| 19 |
+
from sklearn.neighbors import KNeighborsClassifier, NearestNeighbors
|
| 20 |
+
from sklearn.isotonic import IsotonicRegression
|
| 21 |
+
from sklearn.metrics import balanced_accuracy_score, accuracy_score
|
| 22 |
+
|
| 23 |
+
APP_TITLE = "Goshawk Adaptive Risk Engine v5"
|
| 24 |
+
ASSETS = ["BTC-USD", "ETH-USD", "SOL-USD", "AAPL", "MSFT", "NVDA", "SPY", "QQQ",
|
| 25 |
+
"GC=F", "EURUSD=X"]
|
| 26 |
+
K_GRID = [5, 7, 9, 11, 15, 21]
|
| 27 |
+
SL_GRID = [0.8, 1.0, 1.2, 1.5, 1.8, 2.0, 2.5, 3.0]
|
| 28 |
+
RR_GRID = [1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0]
|
| 29 |
+
CLASSES = [-1, 0, 1]
|
| 30 |
+
SIDE_MODES = ["Long + Short", "Sadece Long", "Sadece Short"]
|
| 31 |
+
IND_MODES = ["Kapalı", "Onay filtresi (KNN + ST/UT)", "Sadece indikatör"]
|
| 32 |
+
COMBO_MODES = ["Kapalı", "Onay filtresi (KNN + birleşik)", "Sadece birleşik sinyal"]
|
| 33 |
+
LDC_MODES = ["Kapalı", "Onay filtresi (Lorentzian)", "Sadece Lorentzian"]
|
| 34 |
+
VP_MODES = ["Kapalı", "Onay filtresi (POC / Value Area)", "KNN özelliği + onay filtresi"]
|
| 35 |
+
GANN_MODES = ["Kapalı", "Onay filtresi (Gann Box %50)"]
|
| 36 |
+
SMC_MODES = ["Kapalı", "Onay filtresi (SMC yapı yönü)", "Sadece SMC (yapı kırılımı girişi)"]
|
| 37 |
+
SMC_SRC = ["Swing yapı", "Internal yapı"]
|
| 38 |
+
SMC_OBF = ["Atr", "Cumulative Mean Range"]
|
| 39 |
+
GANN_LEVELS = [0.0, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0]
|
| 40 |
+
GANN_FAN = [(8, "8x1"), (4, "4x1"), (3, "3x1"), (2, "2x1"), (1, "1x1"),
|
| 41 |
+
(1/2, "1x2"), (1/3, "1x3"), (1/4, "1x4"), (1/8, "1x8")]
|
| 42 |
+
|
| 43 |
+
_CACHE = {}
|
| 44 |
+
CACHE_TTL = 600 # saniye; Yahoo'yu gereksiz yere tekrar çağırmamak için
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# ---------------------------------------------------------------- veri / özellik
|
| 48 |
+
def get_market_data(ticker, period):
|
| 49 |
+
key, now = (ticker, period), time.time()
|
| 50 |
+
if key in _CACHE and now - _CACHE[key][0] < CACHE_TTL:
|
| 51 |
+
return _CACHE[key][1].copy()
|
| 52 |
+
df = yf.download(ticker, period=period, interval="1d",
|
| 53 |
+
auto_adjust=True, progress=False)
|
| 54 |
+
if df is None or df.empty:
|
| 55 |
+
raise ValueError(f"Yahoo Finance {ticker} verisi döndürmedi.")
|
| 56 |
+
if isinstance(df.columns, pd.MultiIndex):
|
| 57 |
+
df.columns = df.columns.get_level_values(0)
|
| 58 |
+
df = df[["Open", "High", "Low", "Close", "Volume"]].dropna().copy()
|
| 59 |
+
df.index = pd.to_datetime(df.index)
|
| 60 |
+
_CACHE[key] = (now, df)
|
| 61 |
+
return df.copy()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def calc_atr(df, n=14):
|
| 65 |
+
pc = df.Close.shift(1)
|
| 66 |
+
tr = pd.concat([(df.High - df.Low), (df.High - pc).abs(),
|
| 67 |
+
(df.Low - pc).abs()], axis=1).max(axis=1)
|
| 68 |
+
return tr.ewm(alpha=1/n, adjust=False).mean()
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def calc_rsi(s, n=14):
|
| 72 |
+
d = s.diff()
|
| 73 |
+
up = d.clip(lower=0).ewm(alpha=1/n, adjust=False).mean()
|
| 74 |
+
dn = (-d.clip(upper=0)).ewm(alpha=1/n, adjust=False).mean()
|
| 75 |
+
rs = up / dn.replace(0, np.nan)
|
| 76 |
+
return 100 - 100/(1+rs)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def make_features(df, vp=None):
|
| 80 |
+
c, v = df.Close, df.Volume
|
| 81 |
+
x = pd.DataFrame(index=df.index)
|
| 82 |
+
for n in [1, 2, 3, 5, 10, 20, 50]:
|
| 83 |
+
x[f"ret_{n}"] = c.pct_change(n)
|
| 84 |
+
for n in [5, 10, 20, 50]:
|
| 85 |
+
x[f"ema_gap_{n}"] = c/c.ewm(span=n, adjust=False).mean()-1
|
| 86 |
+
x["rsi_14"] = calc_rsi(c)/100
|
| 87 |
+
atr = calc_atr(df)
|
| 88 |
+
x["atr_pct"] = atr/c
|
| 89 |
+
x["range_pct"] = (df.High-df.Low)/c
|
| 90 |
+
x["body_pct"] = (df.Close-df.Open)/df.Open
|
| 91 |
+
daily_ret = c.pct_change()
|
| 92 |
+
x["vol_10"] = daily_ret.rolling(10).std()
|
| 93 |
+
x["vol_20"] = daily_ret.rolling(20).std()
|
| 94 |
+
x["volume_z20"] = (v-v.rolling(20).mean())/v.rolling(20).std().replace(0, np.nan)
|
| 95 |
+
x["volume_change"] = v.pct_change()
|
| 96 |
+
macd = c.ewm(span=12, adjust=False).mean()-c.ewm(span=26, adjust=False).mean()
|
| 97 |
+
x["macd_pct"] = macd/c
|
| 98 |
+
# v5: fiyat kanalı konumu ve Bollinger z-skoru
|
| 99 |
+
x["dist_hi_20"] = c/df.High.rolling(20).max()-1
|
| 100 |
+
x["dist_lo_20"] = c/df.Low.rolling(20).min()-1
|
| 101 |
+
x["bb_z20"] = (c-c.rolling(20).mean())/c.rolling(20).std().replace(0, np.nan)
|
| 102 |
+
if vp is not None: # Volume Profile özellikleri (ileriye bakmaz)
|
| 103 |
+
x["vp_poc_gap"] = c/vp["poc"]-1
|
| 104 |
+
x["vp_va_pos"] = (c-vp["val"])/(vp["vah"]-vp["val"]).replace(0, np.nan)
|
| 105 |
+
x["vp_va_width"] = (vp["vah"]-vp["val"])/c
|
| 106 |
+
return x.replace([np.inf, -np.inf], np.nan), atr
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def supertrend(df, period=10, mult=3.0):
|
| 110 |
+
"""TradingView ta.supertrend portu. dir: +1 yükseliş, -1 düşüş."""
|
| 111 |
+
atr = calc_atr(df, period).to_numpy(float)
|
| 112 |
+
h, l, c = df.High.to_numpy(float), df.Low.to_numpy(float), df.Close.to_numpy(float)
|
| 113 |
+
hl2 = (h+l)/2
|
| 114 |
+
n = len(c)
|
| 115 |
+
up, dn = hl2-mult*atr, hl2+mult*atr
|
| 116 |
+
trend = np.ones(n, dtype=int)
|
| 117 |
+
for i in range(1, n):
|
| 118 |
+
if c[i-1] > up[i-1]:
|
| 119 |
+
up[i] = max(up[i], up[i-1])
|
| 120 |
+
if c[i-1] < dn[i-1]:
|
| 121 |
+
dn[i] = min(dn[i], dn[i-1])
|
| 122 |
+
trend[i] = trend[i-1]
|
| 123 |
+
if trend[i-1] == -1 and c[i] > dn[i-1]:
|
| 124 |
+
trend[i] = 1
|
| 125 |
+
elif trend[i-1] == 1 and c[i] < up[i-1]:
|
| 126 |
+
trend[i] = -1
|
| 127 |
+
return trend, np.where(trend == 1, up, dn)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def ut_bot(df, key=1.0, period=10):
|
| 131 |
+
"""UT Bot Alerts (QuantNomad) portu. pos: +1 al, -1 sat, 0 henüz sinyal yok."""
|
| 132 |
+
c = df.Close.to_numpy(float)
|
| 133 |
+
nloss = key*calc_atr(df, period).to_numpy(float)
|
| 134 |
+
n = len(c)
|
| 135 |
+
stop, pos = np.zeros(n), np.zeros(n, dtype=int)
|
| 136 |
+
stop[0] = c[0]-nloss[0]
|
| 137 |
+
for i in range(1, n):
|
| 138 |
+
prev = stop[i-1]
|
| 139 |
+
if c[i] > prev and c[i-1] > prev:
|
| 140 |
+
stop[i] = max(prev, c[i]-nloss[i])
|
| 141 |
+
elif c[i] < prev and c[i-1] < prev:
|
| 142 |
+
stop[i] = min(prev, c[i]+nloss[i])
|
| 143 |
+
elif c[i] > prev:
|
| 144 |
+
stop[i] = c[i]-nloss[i]
|
| 145 |
+
else:
|
| 146 |
+
stop[i] = c[i]+nloss[i]
|
| 147 |
+
if c[i-1] < prev and c[i] > prev:
|
| 148 |
+
pos[i] = 1
|
| 149 |
+
elif c[i-1] > prev and c[i] < prev:
|
| 150 |
+
pos[i] = -1
|
| 151 |
+
else:
|
| 152 |
+
pos[i] = pos[i-1]
|
| 153 |
+
return pos, stop
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# ------------------------------------------- Smart Money Concepts (LuxAlgo) Python portu
|
| 159 |
+
# Based on LuxAlgo Smart Money Concepts; attribution retained.
|
| 160 |
+
# Original license: CC BY-NC-SA 4.0 — https://creativecommons.org/licenses/by-nc-sa/4.0/
|
| 161 |
+
def mtf_prev_levels(df):
|
| 162 |
+
"""Önceki gün / hafta / ay high-low (tamamlanmış dönem; ileriye bakma yok), mum bazında dizi."""
|
| 163 |
+
out = {}
|
| 164 |
+
for key, rule in (("d", "D"), ("w", "W"), ("m", "M")):
|
| 165 |
+
per = df.index.to_period(rule)
|
| 166 |
+
hi = df.High.groupby(per).max().shift(1)
|
| 167 |
+
lo = df.Low.groupby(per).min().shift(1)
|
| 168 |
+
out["p"+key+"h"] = hi.reindex(per).to_numpy(float)
|
| 169 |
+
out["p"+key+"l"] = lo.reindex(per).to_numpy(float)
|
| 170 |
+
return out
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _smc_pivots(h, l, size):
|
| 174 |
+
"""Pine leg(size) mantığı: pivotlar `size` mum sonra doğrulanır (ileriye bakma yok).
|
| 175 |
+
Dönüş: {doğrulama_mumu: [("H"|"L", seviye, pivot_mumu)]}"""
|
| 176 |
+
n = len(h)
|
| 177 |
+
hs, ls = pd.Series(h), pd.Series(l)
|
| 178 |
+
hmax = hs.rolling(size).max().to_numpy(float)
|
| 179 |
+
lmin = ls.rolling(size).min().to_numpy(float)
|
| 180 |
+
out, leg = {}, 0
|
| 181 |
+
for i in range(size, n):
|
| 182 |
+
new_leg = leg
|
| 183 |
+
if h[i-size] > hmax[i]:
|
| 184 |
+
new_leg = 0
|
| 185 |
+
elif l[i-size] < lmin[i]:
|
| 186 |
+
new_leg = 1
|
| 187 |
+
if new_leg != leg:
|
| 188 |
+
out.setdefault(i, []).append(("L", float(l[i-size]), i-size) if new_leg == 1
|
| 189 |
+
else ("H", float(h[i-size]), i-size))
|
| 190 |
+
leg = new_leg
|
| 191 |
+
return out
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def smc_analyze(df, swing_len=50, int_len=5, eq_len=3, eq_thr=0.1, ob_keep=5,
|
| 195 |
+
confluence=False, ob_filter="Atr"):
|
| 196 |
+
"""LuxAlgo 'Smart Money Concepts' göstergesinin Python portu: swing/internal yapı (BOS, CHoCH),
|
| 197 |
+
order block, EQH/EQL, Fair Value Gap, premium/discount ve strong/weak high-low.
|
| 198 |
+
Pine kaynağından birebir değil; her mum yalnızca kendisine kadarki veriyi kullanır."""
|
| 199 |
+
n = len(df)
|
| 200 |
+
o, h, l, c = (df[k].to_numpy(float) for k in ("Open", "High", "Low", "Close"))
|
| 201 |
+
atr = calc_atr(df, 200).to_numpy(float)
|
| 202 |
+
pc_ = np.r_[c[0], c[:-1]]
|
| 203 |
+
tr_ = np.maximum(h-l, np.maximum(np.abs(h-pc_), np.abs(l-pc_)))
|
| 204 |
+
vm = (np.cumsum(tr_)/np.arange(1, n+1)) if ob_filter == "Cumulative Mean Range" else atr
|
| 205 |
+
hv = (h-l) >= 2*vm # yüksek volatilite mumları: OB için high/low yer değiştirir
|
| 206 |
+
# LuxAlgo Pine ile aynı gövde/fitil konfluens koşulları.
|
| 207 |
+
# Bearish koşul önceki portta ters yazılmıştı; burada Pine ifadesi birebir korunur.
|
| 208 |
+
bull_bar = (h-np.maximum(c, o)) > np.minimum(c, o-l) if confluence else np.ones(n, bool)
|
| 209 |
+
bear_bar = (h-np.maximum(c, o)) < np.minimum(c, o-l) if confluence else np.ones(n, bool)
|
| 210 |
+
ph, pl = np.where(hv, l, h), np.where(hv, h, l)
|
| 211 |
+
swing_len = int(min(max(swing_len, 3), max(n//4, 3)))
|
| 212 |
+
int_len = int(min(max(int_len, 2), swing_len))
|
| 213 |
+
P_s, P_i, P_e = (_smc_pivots(h, l, k) for k in (swing_len, int_len, int(eq_len)))
|
| 214 |
+
|
| 215 |
+
new = lambda: {"lvl": np.nan, "crossed": True, "idx": -1}
|
| 216 |
+
sh, sl_, ih, il = new(), new(), new(), new()
|
| 217 |
+
s_tr = np.zeros(n, dtype=int)
|
| 218 |
+
i_tr = np.zeros(n, dtype=int)
|
| 219 |
+
top_a, bot_a = np.full(n, np.nan), np.full(n, np.nan)
|
| 220 |
+
swh_a, swl_a = np.full(n, np.nan), np.full(n, np.nan)
|
| 221 |
+
st = it = 0
|
| 222 |
+
top, bot = h[0], l[0]
|
| 223 |
+
events, obs, points, eqs, fvgs = [], [], [], [], []
|
| 224 |
+
act = []
|
| 225 |
+
ob_sup_a, ob_res_a = np.full(n, np.nan), np.full(n, np.nan)
|
| 226 |
+
def _new_ob(d):
|
| 227 |
+
obs.append(d)
|
| 228 |
+
act.append(d)
|
| 229 |
+
if len(act) > 60:
|
| 230 |
+
act.pop(0)
|
| 231 |
+
prev_hi = prev_lo = np.nan
|
| 232 |
+
eq_h = eq_l = None
|
| 233 |
+
dsum = 0.0
|
| 234 |
+
for i in range(n):
|
| 235 |
+
# --- swing / internal pivotlar
|
| 236 |
+
for kind, lvl, bi in P_s.get(i, []):
|
| 237 |
+
if kind == "H":
|
| 238 |
+
points.append((bi, lvl, "HH" if (np.isfinite(prev_hi) and lvl > prev_hi) else "LH", "H"))
|
| 239 |
+
prev_hi = lvl
|
| 240 |
+
sh.update(lvl=lvl, crossed=False, idx=bi)
|
| 241 |
+
top = lvl
|
| 242 |
+
else:
|
| 243 |
+
points.append((bi, lvl, "LL" if (np.isfinite(prev_lo) and lvl < prev_lo) else "HL", "L"))
|
| 244 |
+
prev_lo = lvl
|
| 245 |
+
sl_.update(lvl=lvl, crossed=False, idx=bi)
|
| 246 |
+
bot = lvl
|
| 247 |
+
for kind, lvl, bi in P_i.get(i, []):
|
| 248 |
+
(ih if kind == "H" else il).update(lvl=lvl, crossed=False, idx=bi)
|
| 249 |
+
# --- EQH / EQL
|
| 250 |
+
for kind, lvl, bi in P_e.get(i, []):
|
| 251 |
+
thr = eq_thr*atr[i] if np.isfinite(atr[i]) else 0.0
|
| 252 |
+
if kind == "H":
|
| 253 |
+
if eq_h is not None and abs(eq_h[0]-lvl) < thr:
|
| 254 |
+
eqs.append(("EQH", eq_h[1], eq_h[0], bi, lvl, i))
|
| 255 |
+
eq_h = (lvl, bi)
|
| 256 |
+
else:
|
| 257 |
+
if eq_l is not None and abs(eq_l[0]-lvl) < thr:
|
| 258 |
+
eqs.append(("EQL", eq_l[1], eq_l[0], bi, lvl, i))
|
| 259 |
+
eq_l = (lvl, bi)
|
| 260 |
+
# --- order block mitigasyonu (High/Low)
|
| 261 |
+
for ob in act:
|
| 262 |
+
if ob["on"] and ((ob["bull"] and l[i] < ob["lo"]) or (not ob["bull"] and h[i] > ob["hi"])):
|
| 263 |
+
ob["on"] = False
|
| 264 |
+
act[:] = [o_ for o_ in act if o_["on"]]
|
| 265 |
+
# --- yapı kırılımları
|
| 266 |
+
if i > 0:
|
| 267 |
+
for internal, hp, lp in ((True, ih, il), (False, sh, sl_)):
|
| 268 |
+
tr = it if internal else st
|
| 269 |
+
extra_u = (not internal) or (ih["lvl"] != sh["lvl"] and bool(bull_bar[i]))
|
| 270 |
+
extra_d = (not internal) or (il["lvl"] != sl_["lvl"] and bool(bear_bar[i]))
|
| 271 |
+
if (np.isfinite(hp["lvl"]) and not hp["crossed"] and c[i] > hp["lvl"] >= c[i-1]
|
| 272 |
+
and extra_u):
|
| 273 |
+
events.append({"i": i, "kind": "CHoCH" if tr == -1 else "BOS", "bull": True,
|
| 274 |
+
"internal": internal, "lvl": hp["lvl"], "from": hp["idx"]})
|
| 275 |
+
hp["crossed"] = True
|
| 276 |
+
seg = pl[hp["idx"]:i]
|
| 277 |
+
if len(seg):
|
| 278 |
+
j = hp["idx"] + int(np.argmin(seg))
|
| 279 |
+
_new_ob({"bull": True, "internal": internal, "hi": float(ph[j]),
|
| 280 |
+
"lo": float(pl[j]), "idx": j, "on": True})
|
| 281 |
+
if internal:
|
| 282 |
+
it = 1
|
| 283 |
+
else:
|
| 284 |
+
st = 1
|
| 285 |
+
if (np.isfinite(lp["lvl"]) and not lp["crossed"] and c[i] < lp["lvl"] <= c[i-1]
|
| 286 |
+
and extra_d):
|
| 287 |
+
events.append({"i": i, "kind": "CHoCH" if tr == 1 else "BOS", "bull": False,
|
| 288 |
+
"internal": internal, "lvl": lp["lvl"], "from": lp["idx"]})
|
| 289 |
+
lp["crossed"] = True
|
| 290 |
+
seg = ph[lp["idx"]:i]
|
| 291 |
+
if len(seg):
|
| 292 |
+
j = lp["idx"] + int(np.argmax(seg))
|
| 293 |
+
_new_ob({"bull": False, "internal": internal, "hi": float(ph[j]),
|
| 294 |
+
"lo": float(pl[j]), "idx": j, "on": True})
|
| 295 |
+
if internal:
|
| 296 |
+
it = -1
|
| 297 |
+
else:
|
| 298 |
+
st = -1
|
| 299 |
+
s_tr[i], i_tr[i] = st, it
|
| 300 |
+
top, bot = max(top, h[i]), min(bot, l[i])
|
| 301 |
+
top_a[i], bot_a[i] = top, bot
|
| 302 |
+
swh_a[i], swl_a[i] = sh["lvl"], sl_["lvl"]
|
| 303 |
+
ob_sup_a[i] = max((o_["lo"] for o_ in act if o_["bull"] and o_["lo"] < c[i]), default=np.nan)
|
| 304 |
+
ob_res_a[i] = min((o_["hi"] for o_ in act if (not o_["bull"]) and o_["hi"] > c[i]), default=np.nan)
|
| 305 |
+
# --- Fair Value Gap
|
| 306 |
+
if i >= 2:
|
| 307 |
+
delta = (c[i-1]-o[i-1])/(o[i-1]*100) if o[i-1] else 0.0
|
| 308 |
+
dsum += abs(delta)
|
| 309 |
+
thr = dsum/max(i, 1)*2
|
| 310 |
+
for g in fvgs:
|
| 311 |
+
if g["on"] and ((g["bull"] and l[i] < g["bot"]) or (not g["bull"] and h[i] > g["top"])):
|
| 312 |
+
g["on"] = False
|
| 313 |
+
if l[i] > h[i-2] and c[i-1] > h[i-2] and delta > thr:
|
| 314 |
+
fvgs.append({"bull": True, "top": float(l[i]), "bot": float(h[i-2]), "idx": i-1, "on": True})
|
| 315 |
+
elif h[i] < l[i-2] and c[i-1] < l[i-2] and -delta > thr:
|
| 316 |
+
fvgs.append({"bull": False, "top": float(l[i-2]), "bot": float(h[i]), "idx": i-1, "on": True})
|
| 317 |
+
live = lambda internal, bull: [o_ for o_ in obs if o_["on"] and o_["internal"] == internal
|
| 318 |
+
and o_["bull"] == bull][-int(ob_keep):]
|
| 319 |
+
return {"swing_trend": s_tr, "internal_trend": i_tr, "top": top_a, "bot": bot_a, "sw_hi": swh_a, "sw_lo": swl_a, "ob_sup": ob_sup_a, "ob_res": ob_res_a, **mtf_prev_levels(df),
|
| 320 |
+
"events": events, "points": points, "eq": eqs,
|
| 321 |
+
"obs": live(True, True)+live(True, False)+live(False, True)+live(False, False),
|
| 322 |
+
"fvg": [g for g in fvgs if g["on"]][-8:], "strong_high": st == -1, "strong_low": st == 1}
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def add_smc_traces(fig, S, index, last_n=150, kw=None):
|
| 326 |
+
"""SMC katmanlarını son `last_n` mumluk grafiğe ekler."""
|
| 327 |
+
kw = kw or {}
|
| 328 |
+
n = len(index)
|
| 329 |
+
w0 = max(0, n-int(last_n))
|
| 330 |
+
xi = lambda k: index[min(max(int(k), w0), n-1)]
|
| 331 |
+
for e in S["events"]:
|
| 332 |
+
if e["i"] < w0:
|
| 333 |
+
continue
|
| 334 |
+
col = "#16a34a" if e["bull"] else "#dc2626"
|
| 335 |
+
xa, xb = xi(e["from"]), xi(e["i"])
|
| 336 |
+
fig.add_trace(go.Scatter(x=[xa, xb], y=[e["lvl"], e["lvl"]], mode="lines",
|
| 337 |
+
line=dict(width=1 if e["internal"] else 2, color=col,
|
| 338 |
+
dash="dash" if e["internal"] else "solid"),
|
| 339 |
+
showlegend=False, hoverinfo="skip"), **kw)
|
| 340 |
+
mid = xa + (xb-xa)/2
|
| 341 |
+
fig.add_trace(go.Scatter(x=[mid], y=[e["lvl"]], mode="text", text=[e["kind"]],
|
| 342 |
+
textposition="top center" if e["bull"] else "bottom center",
|
| 343 |
+
textfont=dict(size=9 if e["internal"] else 11, color=col),
|
| 344 |
+
showlegend=False, hoverinfo="skip"), **kw)
|
| 345 |
+
for ob in S["obs"]:
|
| 346 |
+
col = "34,197,94" if ob["bull"] else "239,68,68"
|
| 347 |
+
fig.add_shape(type="rect", x0=xi(ob["idx"]), x1=index[-1], y0=ob["lo"], y1=ob["hi"],
|
| 348 |
+
line=dict(width=1 if ob["internal"] else 0, color=f"rgb({col})"),
|
| 349 |
+
fillcolor=f"rgba({col},{0.12 if ob['internal'] else 0.22})", layer="below", **kw)
|
| 350 |
+
for g in S["fvg"]:
|
| 351 |
+
if g["idx"] < w0:
|
| 352 |
+
continue
|
| 353 |
+
col = "34,197,94" if g["bull"] else "239,68,68"
|
| 354 |
+
fig.add_shape(type="rect", x0=xi(g["idx"]), x1=index[-1], y0=g["bot"], y1=g["top"],
|
| 355 |
+
line=dict(width=0), fillcolor=f"rgba({col},0.10)", layer="below", **kw)
|
| 356 |
+
for kind, xa_i, la, xb_i, lb, _ in S["eq"]:
|
| 357 |
+
if xb_i < w0:
|
| 358 |
+
continue
|
| 359 |
+
fig.add_trace(go.Scatter(x=[xi(xa_i), xi(xb_i)], y=[la, lb], mode="lines+text",
|
| 360 |
+
text=["", kind], textposition="top center" if kind == "EQH" else "bottom center",
|
| 361 |
+
line=dict(width=1, dash="dot", color="#64748b"),
|
| 362 |
+
textfont=dict(size=10, color="#64748b"), showlegend=False,
|
| 363 |
+
hoverinfo="skip"), **kw)
|
| 364 |
+
for bi, lvl, lab, kd in S["points"]:
|
| 365 |
+
if bi >= w0:
|
| 366 |
+
fig.add_trace(go.Scatter(x=[index[bi]], y=[lvl], mode="text", text=[lab],
|
| 367 |
+
textposition="top center" if kd == "H" else "bottom center",
|
| 368 |
+
textfont=dict(size=9, color="#475569"), showlegend=False,
|
| 369 |
+
hoverinfo="skip"), **kw)
|
| 370 |
+
if S.get("show_mtf"):
|
| 371 |
+
for k_, nm_ in (("pdh", "PDH"), ("pdl", "PDL"), ("pwh", "PWH"), ("pwl", "PWL"),
|
| 372 |
+
("pmh", "PMH"), ("pml", "PML")):
|
| 373 |
+
y_ = S[k_][-1]
|
| 374 |
+
if np.isfinite(y_):
|
| 375 |
+
fig.add_trace(go.Scatter(x=[index[w0], index[-1]], y=[y_, y_], mode="lines+text",
|
| 376 |
+
text=["", nm_], textposition="top left",
|
| 377 |
+
line=dict(width=1, dash="dashdot", color="#0ea5e9"),
|
| 378 |
+
textfont=dict(size=9, color="#0ea5e9"), showlegend=False,
|
| 379 |
+
hoverinfo="skip"), **kw)
|
| 380 |
+
top, bot = float(S["top"][-1]), float(S["bot"][-1])
|
| 381 |
+
if np.isfinite(top) and np.isfinite(bot) and top > bot:
|
| 382 |
+
x0 = index[max(0, n-35)]
|
| 383 |
+
rng_ = top-bot
|
| 384 |
+
for y0, y1, col, nm in [(top, top-0.05*rng_, "239,68,68", "Premium"),
|
| 385 |
+
(bot+0.525*rng_, bot+0.475*rng_, "148,163,184", "Equilibrium"),
|
| 386 |
+
(bot+0.05*rng_, bot, "34,197,94", "Discount")]:
|
| 387 |
+
fig.add_shape(type="rect", x0=x0, x1=index[-1], y0=y0, y1=y1, line=dict(width=0),
|
| 388 |
+
fillcolor=f"rgba({col},0.16)", layer="below", **kw)
|
| 389 |
+
for y, nm in [(top, "Strong High" if S["strong_high"] else "Weak High"),
|
| 390 |
+
(bot, "Strong Low" if S["strong_low"] else "Weak Low")]:
|
| 391 |
+
fig.add_trace(go.Scatter(x=[x0, index[-1]], y=[y, y], mode="lines+text", text=["", nm],
|
| 392 |
+
textposition="top left", line=dict(width=1, color="#64748b"),
|
| 393 |
+
textfont=dict(size=9, color="#64748b"), showlegend=False,
|
| 394 |
+
hoverinfo="skip"), **kw)
|
| 395 |
+
|
| 396 |
+
# ------------------------------------------- Gann Box (Zeiierman) Python portu
|
| 397 |
+
def gann_box(df, lookback=150, reverse=False, levels=GANN_LEVELS):
|
| 398 |
+
"""Görünen aralıktaki (son `lookback` mum) en yüksek / en düşük noktadan Gann Box kurar.
|
| 399 |
+
Zeiierman'ın TradingView göstergesinin açıklamasına göre yazıldı (Pine kaynağı birebir değil).
|
| 400 |
+
up: düşük, yüksekten ÖNCE geldiyse True (yükseliş kutusu). reverse: yönü çevirir."""
|
| 401 |
+
w = df.tail(int(lookback))
|
| 402 |
+
if len(w) < 10:
|
| 403 |
+
return None
|
| 404 |
+
hi_i, lo_i = int(np.argmax(w.High.to_numpy(float))), int(np.argmin(w.Low.to_numpy(float)))
|
| 405 |
+
hi, lo = float(w.High.iloc[hi_i]), float(w.Low.iloc[lo_i])
|
| 406 |
+
if hi <= lo:
|
| 407 |
+
return None
|
| 408 |
+
up = lo_i < hi_i
|
| 409 |
+
if reverse:
|
| 410 |
+
up = not up
|
| 411 |
+
# kutu: pencerenin başı -> sonu; seviyeler lo + p*(hi-lo)
|
| 412 |
+
lv = {float(p): lo + float(p)*(hi-lo) for p in levels}
|
| 413 |
+
return {"x0": w.index[0], "x1": w.index[-1], "n": len(w), "hi": hi, "lo": lo,
|
| 414 |
+
"up": up, "levels": lv, "hi_date": w.index[hi_i], "lo_date": w.index[lo_i]}
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def gann_signal(df, lookback=150):
|
| 418 |
+
"""Her mum için yalnızca geçmişe bakan Gann Box %50 konumu: kapanış orta çizgi üstünde +1,
|
| 419 |
+
altında -1 (kutu o muma kadarki son `lookback` mumdan kurulur)."""
|
| 420 |
+
lb = int(lookback)
|
| 421 |
+
hi = df.High.rolling(lb, min_periods=lb).max()
|
| 422 |
+
lo = df.Low.rolling(lb, min_periods=lb).min()
|
| 423 |
+
mid = (hi+lo)/2
|
| 424 |
+
c = df.Close
|
| 425 |
+
out = np.where(c > mid, 1, np.where(c < mid, -1, 0))
|
| 426 |
+
out = np.where(mid.isna(), 0, out)
|
| 427 |
+
return out.astype(int), ((c-lo)/(hi-lo).replace(0, np.nan)).to_numpy(float)
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def add_gann_traces(fig, gb, df_index, extend=True, fan=True, kw=None):
|
| 431 |
+
"""Kutu, yüzde seviyeleri ve (isteğe bağlı) Gann Fan çizgilerini Plotly grafiğine ekler."""
|
| 432 |
+
kw = kw or {}
|
| 433 |
+
x0, x1, n = gb["x0"], gb["x1"], gb["n"]
|
| 434 |
+
step = (df_index[-1]-df_index[-2]) if len(df_index) > 1 else pd.Timedelta(days=1)
|
| 435 |
+
x_end = x1 + step*int(n*0.5) if extend else x1
|
| 436 |
+
hi, lo = gb["hi"], gb["lo"]
|
| 437 |
+
fig.add_shape(type="rect", x0=x0, x1=x1, y0=lo, y1=hi, line=dict(width=1, color="#64748b"),
|
| 438 |
+
fillcolor="rgba(100,116,139,0.05)", layer="below", **kw)
|
| 439 |
+
pal = {0.0: "#16a34a", 0.236: "#84cc16", 0.382: "#eab308", 0.5: "#f59e0b",
|
| 440 |
+
0.618: "#f97316", 0.786: "#ef4444", 1.0: "#dc2626"}
|
| 441 |
+
for p, y in gb["levels"].items():
|
| 442 |
+
fig.add_trace(go.Scatter(x=[x0, x_end], y=[y, y], mode="lines+text",
|
| 443 |
+
text=["", f"Gann {p:.3f} · {fmt_p(y)}"], textposition="middle right",
|
| 444 |
+
line=dict(width=1, dash="dot", color=pal.get(p, "#94a3b8")),
|
| 445 |
+
name=f"Gann {p:.3f}", showlegend=False, hoverinfo="name+y"), **kw)
|
| 446 |
+
if fan:
|
| 447 |
+
# köşe: yükseliş kutusunda düşük (sol-alt), düşüşte yüksek (sol-üst); 1x1 = karşı köşeye köşegen
|
| 448 |
+
y_start = lo if gb["up"] else hi
|
| 449 |
+
sign = 1 if gb["up"] else -1
|
| 450 |
+
base = (hi-lo)/max(gb["n"]-1, 1) # 1x1 eğimi (fiyat/mum)
|
| 451 |
+
steps = (x_end - x0)/step
|
| 452 |
+
for mult, nm in GANN_FAN:
|
| 453 |
+
y_end = y_start + sign*base*mult*steps
|
| 454 |
+
ya, yb = (y_start, y_end)
|
| 455 |
+
# kutu dışına taşan kısmı kes
|
| 456 |
+
lo_c, hi_c = lo - (hi-lo)*0.5, hi + (hi-lo)*0.5
|
| 457 |
+
if ya == yb:
|
| 458 |
+
continue
|
| 459 |
+
t_hit = min(1.0, max(0.0, ((hi_c if yb > ya else lo_c)-ya)/(yb-ya)))
|
| 460 |
+
xe = x0 + (x_end-x0)*t_hit
|
| 461 |
+
ye = ya + (yb-ya)*t_hit
|
| 462 |
+
fig.add_trace(go.Scatter(x=[x0, xe], y=[ya, ye], mode="lines",
|
| 463 |
+
line=dict(width=1.6 if mult == 1 else 0.8,
|
| 464 |
+
color="#2563eb" if mult == 1 else "#93c5fd"),
|
| 465 |
+
name=f"Gann Fan {nm}", showlegend=(mult == 1),
|
| 466 |
+
hoverinfo="name"), **kw)
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
def _profile(h, l, v, bins):
|
| 470 |
+
"""Bar hacmini high-low aralığına, kutularla örtüştüğü oranda dağıtır (VPVR mantığı)."""
|
| 471 |
+
lo, hi = float(np.min(l)), float(np.max(h))
|
| 472 |
+
if not (np.isfinite(lo) and np.isfinite(hi)) or hi <= lo:
|
| 473 |
+
return None
|
| 474 |
+
v = np.where(np.isfinite(v) & (v > 0), v, 0.0)
|
| 475 |
+
if v.sum() <= 0: # hacim verisi yok (ör. forex) -> zaman bazlı profil
|
| 476 |
+
v = np.ones_like(v)
|
| 477 |
+
edges = np.linspace(lo, hi, bins+1)
|
| 478 |
+
step = (hi-lo)/bins
|
| 479 |
+
rng = np.maximum(h-l, 1e-12)
|
| 480 |
+
ov = np.clip(np.minimum(h[:, None], edges[None, 1:]) - np.maximum(l[:, None], edges[None, :-1]), 0, None)
|
| 481 |
+
frac = ov/rng[:, None]
|
| 482 |
+
zr = (h-l) <= 1e-12
|
| 483 |
+
if zr.any():
|
| 484 |
+
frac[zr] = 0.0
|
| 485 |
+
frac[zr, np.clip(((h[zr]-lo)/step).astype(int), 0, bins-1)] = 1.0
|
| 486 |
+
return edges, (frac*v[:, None]).sum(axis=0)
|
| 487 |
+
|
| 488 |
+
|
| 489 |
+
def _value_area(edges, prof, va_pct):
|
| 490 |
+
pi = int(np.argmax(prof))
|
| 491 |
+
lo_i = hi_i = pi
|
| 492 |
+
acc, target = float(prof[pi]), va_pct*float(prof.sum())
|
| 493 |
+
nb = len(prof)
|
| 494 |
+
while acc < target and (lo_i > 0 or hi_i < nb-1):
|
| 495 |
+
up = prof[hi_i+1] if hi_i < nb-1 else -1.0
|
| 496 |
+
dn = prof[lo_i-1] if lo_i > 0 else -1.0
|
| 497 |
+
if up >= dn:
|
| 498 |
+
hi_i += 1
|
| 499 |
+
acc += up
|
| 500 |
+
else:
|
| 501 |
+
lo_i -= 1
|
| 502 |
+
acc += dn
|
| 503 |
+
return float((edges[pi]+edges[pi+1])/2), float(edges[lo_i]), float(edges[hi_i+1])
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
def volume_profile(df, window=60, bins=24, va_pct=0.70):
|
| 507 |
+
"""Kayan Volume Profile: her mum için son `window` mumdan POC / VAL / VAH.
|
| 508 |
+
Yalnızca o muma kadar olan veri kullanılır (ileriye bakma yok)."""
|
| 509 |
+
h, l, v = (df[c].to_numpy(float) for c in ("High", "Low", "Volume"))
|
| 510 |
+
n = len(h)
|
| 511 |
+
out = np.full((n, 3), np.nan)
|
| 512 |
+
for i in range(window-1, n):
|
| 513 |
+
sl = slice(i-window+1, i+1)
|
| 514 |
+
p = _profile(h[sl], l[sl], v[sl], bins)
|
| 515 |
+
if p is None:
|
| 516 |
+
continue
|
| 517 |
+
out[i] = _value_area(p[0], p[1], va_pct)
|
| 518 |
+
return pd.DataFrame(out, index=df.index, columns=["poc", "val", "vah"])
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def latest_profile(df, window, bins, va_pct):
|
| 522 |
+
w = df.tail(int(window))
|
| 523 |
+
p = _profile(w.High.to_numpy(float), w.Low.to_numpy(float), w.Volume.to_numpy(float), int(bins))
|
| 524 |
+
if p is None:
|
| 525 |
+
return None
|
| 526 |
+
poc, val, vah = _value_area(p[0], p[1], va_pct)
|
| 527 |
+
return p[0], p[1], poc, val, vah
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
def vp_signal(df, atr, vp, ext_atr):
|
| 531 |
+
"""+1: long onaylı (kapanış POC üstünde ve VAH'ın ext_atr*ATR'den fazla üstünde değil),
|
| 532 |
+
-1: short onaylı (POC altında ve VAL'ın ext_atr*ATR'den fazla altında değil), 0: onay yok."""
|
| 533 |
+
c, a = df.Close.to_numpy(float), atr.to_numpy(float)
|
| 534 |
+
poc, val, vah = (vp[k].to_numpy(float) for k in ("poc", "val", "vah"))
|
| 535 |
+
with np.errstate(invalid="ignore"):
|
| 536 |
+
ok_l = (c > poc) & ((c-vah) <= ext_atr*a)
|
| 537 |
+
ok_s = (c < poc) & ((val-c) <= ext_atr*a)
|
| 538 |
+
return np.where(ok_l, 1, np.where(ok_s, -1, 0)).astype(int)
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
# ------------------------------------------- Lorentzian Classification (jdehorty) portu
|
| 542 |
+
def adx_wilder(df, n=14):
|
| 543 |
+
h, l, c = df.High, df.Low, df.Close
|
| 544 |
+
up, dn = h.diff(), -l.diff()
|
| 545 |
+
plus = pd.Series(np.where((up > dn) & (up > 0), up, 0.0), index=df.index)
|
| 546 |
+
minus = pd.Series(np.where((dn > up) & (dn > 0), dn, 0.0), index=df.index)
|
| 547 |
+
tr = pd.concat([(h-l), (h-c.shift(1)).abs(), (l-c.shift(1)).abs()], axis=1).max(axis=1)
|
| 548 |
+
a = 1/n
|
| 549 |
+
atr = tr.ewm(alpha=a, adjust=False).mean()
|
| 550 |
+
pdi = 100*plus.ewm(alpha=a, adjust=False).mean()/atr
|
| 551 |
+
mdi = 100*minus.ewm(alpha=a, adjust=False).mean()/atr
|
| 552 |
+
dx = 100*(pdi-mdi).abs()/(pdi+mdi).replace(0, np.nan)
|
| 553 |
+
return dx.ewm(alpha=a, adjust=False).mean().to_numpy(float)
|
| 554 |
+
|
| 555 |
+
|
| 556 |
+
def _norm01(x):
|
| 557 |
+
"""Geçmiş min/max'a göre 0-1 ölçekleme (TV 'normalize'); yalnızca geçmişe bakar."""
|
| 558 |
+
s = pd.Series(x)
|
| 559 |
+
lo, hi = s.cummin(), s.cummax()
|
| 560 |
+
return ((s-lo)/(hi-lo).replace(0, np.nan)).to_numpy(float)
|
| 561 |
+
|
| 562 |
+
|
| 563 |
+
def _ldc_features(df):
|
| 564 |
+
src = (df.High+df.Low+df.Close)/3
|
| 565 |
+
ema = lambda s, n: s.ewm(span=max(int(n), 1), adjust=False).mean()
|
| 566 |
+
rsi = lambda n: ema(calc_rsi(src, n)/100, 1).to_numpy(float)
|
| 567 |
+
e1 = ema(src, 10)
|
| 568 |
+
e2 = ema((src-e1).abs(), 10)
|
| 569 |
+
wt1 = ema((src-e1)/(0.015*e2).replace(0, np.nan), 11)
|
| 570 |
+
wt = _norm01((wt1-wt1.rolling(4).mean()).to_numpy(float))
|
| 571 |
+
ma = src.rolling(20).mean()
|
| 572 |
+
md = src.rolling(20).apply(lambda a: np.abs(a-a.mean()).mean(), raw=True)
|
| 573 |
+
cci = _norm01(((src-ma)/(0.015*md.replace(0, np.nan))).to_numpy(float))
|
| 574 |
+
adx = adx_wilder(df, 20)/100
|
| 575 |
+
# varsayılan öznitelikler: RSI(14,1), WT(10,11), CCI(20,1), ADX(20,2), RSI(9,1)
|
| 576 |
+
return np.column_stack([rsi(14), wt, cci, adx, rsi(9)]), src.to_numpy(float)
|
| 577 |
+
|
| 578 |
+
|
| 579 |
+
def ldc_classify(df, k=8, max_back=2000, regime_thr=-0.1, kern_lb=8, kern_rw=8.0, x0=25,
|
| 580 |
+
use_vol=True, use_regime=True, use_adx=False, adx_thr=20, use_kernel=True, exact=True):
|
| 581 |
+
"""Machine Learning: Lorentzian Classification (jdehorty) Python portu.
|
| 582 |
+
Lorentzian mesafe = sum(log(1+|x-y|)); exact=True: gerçek k-NN, False: Pine'ın yaklaşık-NN döngüsü;
|
| 583 |
+
etiket = 4 bar sonraki yön. Filtreler: volatilite, rejim (Kalman eğimi), ADX; kernel:
|
| 584 |
+
Nadaraya-Watson rational quadratic. Her mum yalnızca kendisine kadarki veriyi kullanır.
|
| 585 |
+
state: +1 / -1 (ML sinyali + kernel eğimi aynı yönde), 0 onay yok."""
|
| 586 |
+
n = len(df)
|
| 587 |
+
F, s = _ldc_features(df)
|
| 588 |
+
y = np.full(n, np.nan)
|
| 589 |
+
y[:-4] = np.sign(s[4:]-s[:-4])
|
| 590 |
+
ok_f = np.isfinite(F).all(axis=1)
|
| 591 |
+
cand = ok_f & np.isfinite(y) & (np.arange(n) % 4 != 0)
|
| 592 |
+
kk = int(k)
|
| 593 |
+
cut = int(np.floor(kk*0.75+0.5))
|
| 594 |
+
pred = np.zeros(n)
|
| 595 |
+
for t in range(60, n):
|
| 596 |
+
if not ok_f[t]:
|
| 597 |
+
continue
|
| 598 |
+
hi_j = t-4 # etiketi t'de bilinen son örnek
|
| 599 |
+
lo_j = max(0, hi_j-int(max_back)+1)
|
| 600 |
+
sel = np.flatnonzero(cand[lo_j:hi_j+1]) + lo_j
|
| 601 |
+
if len(sel) < kk:
|
| 602 |
+
continue
|
| 603 |
+
D = np.log1p(np.abs(F[sel]-F[t])).sum(axis=1)
|
| 604 |
+
if exact: # gerçek k en yakın komşu (Lorentzian mesafe)
|
| 605 |
+
nn_ = np.argpartition(D, kk-1)[:kk]
|
| 606 |
+
pred[t] = y[sel][nn_].sum()
|
| 607 |
+
continue
|
| 608 |
+
last, dist, pr = -1.0, [], [] # Pine'ın kronolojik yaklaşık-NN döngüsü
|
| 609 |
+
for d, yv in zip(D.tolist(), y[sel].tolist()):
|
| 610 |
+
if d >= last:
|
| 611 |
+
dist.append(d)
|
| 612 |
+
pr.append(yv)
|
| 613 |
+
if len(dist) > kk:
|
| 614 |
+
last = dist[cut]
|
| 615 |
+
dist.pop(0)
|
| 616 |
+
pr.pop(0)
|
| 617 |
+
pred[t] = sum(pr)
|
| 618 |
+
|
| 619 |
+
# --- filtreler
|
| 620 |
+
f_vol = (calc_atr(df, 1).to_numpy(float) > calc_atr(df, 10).to_numpy(float)) if use_vol \
|
| 621 |
+
else np.ones(n, bool)
|
| 622 |
+
f_reg = np.ones(n, bool)
|
| 623 |
+
if use_regime:
|
| 624 |
+
o4 = ((df.Open+df.High+df.Low+df.Close)/4).to_numpy(float)
|
| 625 |
+
hl = (df.High-df.Low).to_numpy(float)
|
| 626 |
+
v1 = v2 = kl = 0.0
|
| 627 |
+
klmf = np.zeros(n)
|
| 628 |
+
for i in range(n):
|
| 629 |
+
v1 = 0.2*(o4[i]-(o4[i-1] if i else o4[i]))+0.8*v1
|
| 630 |
+
v2 = 0.1*hl[i]+0.8*v2
|
| 631 |
+
om = abs(v1/v2) if v2 else 0.0
|
| 632 |
+
al = (-om**2+np.sqrt(om**4+16*om**2))/8
|
| 633 |
+
kl = al*o4[i]+(1-al)*kl
|
| 634 |
+
klmf[i] = kl
|
| 635 |
+
slope = pd.Series(np.abs(np.diff(klmf, prepend=klmf[0])))
|
| 636 |
+
ema_s = slope.ewm(span=200, adjust=False).mean()
|
| 637 |
+
f_reg = (((slope-ema_s)/ema_s.replace(0, np.nan)) >= float(regime_thr)).fillna(False).to_numpy(bool)
|
| 638 |
+
f_adx = (adx_wilder(df, 14) > float(adx_thr)) if use_adx else np.ones(n, bool)
|
| 639 |
+
filt = f_vol & f_reg & f_adx
|
| 640 |
+
|
| 641 |
+
sig = np.zeros(n, dtype=int)
|
| 642 |
+
for t in range(1, n):
|
| 643 |
+
sig[t] = 1 if (pred[t] > 0 and filt[t]) else -1 if (pred[t] < 0 and filt[t]) else sig[t-1]
|
| 644 |
+
|
| 645 |
+
# --- kernel regresyon (rational quadratic), yalnızca geçmiş barlar
|
| 646 |
+
L = int(kern_lb)+int(x0)
|
| 647 |
+
i_ = np.arange(L+1)
|
| 648 |
+
w = (1+i_**2/(float(kern_lb)**2*2*float(kern_rw)))**(-float(kern_rw))
|
| 649 |
+
yhat = np.full(n, np.nan)
|
| 650 |
+
for t in range(n):
|
| 651 |
+
m = min(L+1, t+1)
|
| 652 |
+
yhat[t] = (w[:m]*s[t-np.arange(m)]).sum()/w[:m].sum()
|
| 653 |
+
bull = np.r_[False, yhat[1:] > yhat[:-1]]
|
| 654 |
+
bear = np.r_[False, yhat[1:] < yhat[:-1]]
|
| 655 |
+
if use_kernel:
|
| 656 |
+
state = np.where((sig == 1) & bull, 1, np.where((sig == -1) & bear, -1, 0)).astype(int)
|
| 657 |
+
else:
|
| 658 |
+
state = sig.copy()
|
| 659 |
+
state[:60] = 0
|
| 660 |
+
return {"state": state, "signal": sig, "pred": pred, "yhat": yhat,
|
| 661 |
+
"vol": f_vol, "reg": f_reg, "adx": f_adx}
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
def regime_labels(features, ref_vol):
|
| 665 |
+
# Volatilite kantilleri yalnızca geliştirme dönemi verisinden alınır (sızıntı yok).
|
| 666 |
+
q1, q2 = ref_vol.dropna().quantile([0.33, 0.67])
|
| 667 |
+
vol, trend = features["vol_20"], features["ema_gap_20"]
|
| 668 |
+
vname = np.where(vol <= q1, "LOW_VOL", np.where(vol >= q2, "HIGH_VOL", "MID_VOL"))
|
| 669 |
+
tname = np.where(trend > 0.02, "UP", np.where(trend < -0.02, "DOWN", "RANGE"))
|
| 670 |
+
return pd.Series([f"{t}_{v}" for t, v in zip(tname, vname)], index=features.index)
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def make_target(df, atr, horizon, threshold=0.20):
|
| 674 |
+
fwd = df.Close.shift(-horizon)/df.Close-1
|
| 675 |
+
band = threshold*(atr/df.Close)*np.sqrt(horizon)
|
| 676 |
+
y = pd.Series(np.where(fwd > band, 1, np.where(fwd < -band, -1, 0)), index=df.index)
|
| 677 |
+
return y.where(fwd.notna() & band.notna()), fwd
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
# ---------------------------------------------------------------- model
|
| 681 |
+
def pipe(k):
|
| 682 |
+
return Pipeline([
|
| 683 |
+
("imputer", SimpleImputer(strategy="median")),
|
| 684 |
+
("scale", StandardScaler()),
|
| 685 |
+
("knn", KNeighborsClassifier(n_neighbors=int(k), weights="distance"))
|
| 686 |
+
])
|
| 687 |
+
|
| 688 |
+
|
| 689 |
+
def tune_k(X, y, h):
|
| 690 |
+
# İç kronolojik doğrulama; örtüşen etiketleri önlemek için ufuk kadar purge.
|
| 691 |
+
cut = int(len(X)*0.80)
|
| 692 |
+
tr_end = cut-h
|
| 693 |
+
if tr_end <= max(K_GRID)+20 or len(X)-cut < 25:
|
| 694 |
+
return 9, []
|
| 695 |
+
scores = []
|
| 696 |
+
for k in K_GRID:
|
| 697 |
+
model = pipe(k).fit(X.iloc[:tr_end], y.iloc[:tr_end])
|
| 698 |
+
pred = model.predict(X.iloc[cut:])
|
| 699 |
+
scores.append((float(balanced_accuracy_score(y.iloc[cut:], pred)), int(k)))
|
| 700 |
+
scores.sort(reverse=True)
|
| 701 |
+
return scores[0][1], scores
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
def calibrate_current(raw_cal, y_cal, raw_cur, classes):
|
| 705 |
+
# Bire-karşı-hepsi isotonic kalibrasyon; küçük kalibrasyon setinde ham olasılık.
|
| 706 |
+
classes = list(classes)
|
| 707 |
+
raw_vec = np.zeros(len(CLASSES))
|
| 708 |
+
out = np.zeros(len(CLASSES))
|
| 709 |
+
for j, cls in enumerate(CLASSES):
|
| 710 |
+
if cls not in classes:
|
| 711 |
+
continue
|
| 712 |
+
ci = classes.index(cls)
|
| 713 |
+
val = raw_cur[0, ci]
|
| 714 |
+
raw_vec[j] = val
|
| 715 |
+
truth = (np.asarray(y_cal) == cls).astype(int)
|
| 716 |
+
if len(truth) >= 35 and len(np.unique(truth)) == 2:
|
| 717 |
+
iso = IsotonicRegression(out_of_bounds="clip").fit(raw_cal[:, ci], truth)
|
| 718 |
+
out[j] = iso.predict([val])[0]
|
| 719 |
+
else:
|
| 720 |
+
out[j] = val
|
| 721 |
+
if out.sum() > 0:
|
| 722 |
+
return out/out.sum()
|
| 723 |
+
if raw_vec.sum() > 0:
|
| 724 |
+
return raw_vec/raw_vec.sum()
|
| 725 |
+
return np.full(len(CLASSES), 1/3)
|
| 726 |
+
|
| 727 |
+
|
| 728 |
+
# ---------------------------------------------------------------- bariyer / R
|
| 729 |
+
def stop_dist(A, i, side, sl_atr):
|
| 730 |
+
return A["ind_sd"][side][i] if "ind_sd" in A else sl_atr*A["a"][i]
|
| 731 |
+
|
| 732 |
+
|
| 733 |
+
def barrier_trade(A, i, side, sl_atr, rr, h):
|
| 734 |
+
"""(getiri, çıkış_pozisyonu, neden) döndürür. Aynı mumda SL+TP varsa SL kabul edilir."""
|
| 735 |
+
entry, av = A["c"][i], A["a"][i]
|
| 736 |
+
if not np.isfinite(av) or av <= 0 or not np.isfinite(entry):
|
| 737 |
+
return None
|
| 738 |
+
if "ind_sd" in A: # SL/TP indikatör seviyelerinden (ST / UT / Volume Profile)
|
| 739 |
+
sd = A["ind_sd"][side][i]
|
| 740 |
+
if not np.isfinite(sd) or sd <= 0:
|
| 741 |
+
return None
|
| 742 |
+
td = min(sd*rr, A["ind_cap"][side][i])
|
| 743 |
+
else:
|
| 744 |
+
sd = sl_atr*av
|
| 745 |
+
td = sd*rr
|
| 746 |
+
end = min(i+h, len(A["c"])-1)
|
| 747 |
+
for j in range(i+1, end+1):
|
| 748 |
+
hi, lo = A["h"][j], A["l"][j]
|
| 749 |
+
if side == 1:
|
| 750 |
+
sl_hit, tp_hit = lo <= entry-sd, hi >= entry+td
|
| 751 |
+
else:
|
| 752 |
+
sl_hit, tp_hit = hi >= entry+sd, lo <= entry-td
|
| 753 |
+
if sl_hit:
|
| 754 |
+
return -sd/entry, j, "SL"
|
| 755 |
+
if tp_hit:
|
| 756 |
+
return td/entry, j, "TP"
|
| 757 |
+
return side*(A["c"][end]-entry)/entry, end, "TIME"
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
def optimize_barriers(A, pos, regimes_arr, side, cost, current_regime, h):
|
| 761 |
+
pos = list(pos)[-650:]
|
| 762 |
+
sel = [i for i in pos if regimes_arr[i] == current_regime]
|
| 763 |
+
if len(sel) < 35:
|
| 764 |
+
sel, used = pos, "GLOBAL_FALLBACK"
|
| 765 |
+
else:
|
| 766 |
+
used = str(current_regime)
|
| 767 |
+
results = []
|
| 768 |
+
for sl in ([1.0] if "ind_sd" in A else SL_GRID):
|
| 769 |
+
for rr in RR_GRID:
|
| 770 |
+
vals = []
|
| 771 |
+
for i in sel:
|
| 772 |
+
t = barrier_trade(A, i, side, sl, rr, h)
|
| 773 |
+
if t is not None:
|
| 774 |
+
vals.append(t[0]-cost)
|
| 775 |
+
if len(vals) >= 35:
|
| 776 |
+
a = np.asarray(vals)
|
| 777 |
+
downside = np.sqrt(np.mean(np.minimum(a, 0)**2))
|
| 778 |
+
results.append((a.mean()-0.35*downside, sl, rr, len(a),
|
| 779 |
+
a.mean(), (a > 0).mean()))
|
| 780 |
+
if not results:
|
| 781 |
+
return 1.5, 2.0, [], used
|
| 782 |
+
results.sort(reverse=True)
|
| 783 |
+
return float(results[0][1]), float(results[0][2]), results[:10], used
|
| 784 |
+
|
| 785 |
+
|
| 786 |
+
# ---------------------------------------------------------------- risk / MC
|
| 787 |
+
def dd_mult(dd):
|
| 788 |
+
return np.select([dd <= -0.20, dd <= -0.15, dd <= -0.10, dd <= -0.06],
|
| 789 |
+
[0.20, 0.35, 0.50, 0.75], 1.0)
|
| 790 |
+
|
| 791 |
+
|
| 792 |
+
def adaptive_risk(min_risk, max_risk, bacc, agreement, expectancy_r, n_trades, drawdown):
|
| 793 |
+
# Açık, deterministik politika (pekiştirmeli öğrenme değildir).
|
| 794 |
+
quality = np.clip((bacc-0.33)/0.20, 0, 1)
|
| 795 |
+
agree = np.clip((agreement-0.33)/0.50, 0, 1)
|
| 796 |
+
edge = np.clip(expectancy_r/0.30, 0, 1)
|
| 797 |
+
sample = np.clip(n_trades/40, 0, 1)
|
| 798 |
+
score = 0.30*quality+0.25*agree+0.30*edge+0.15*sample
|
| 799 |
+
risk = min_risk+(max_risk-min_risk)*score
|
| 800 |
+
if drawdown <= -0.20:
|
| 801 |
+
risk = min_risk
|
| 802 |
+
elif drawdown <= -0.15:
|
| 803 |
+
risk = min_risk+(risk-min_risk)*0.20
|
| 804 |
+
elif drawdown <= -0.10:
|
| 805 |
+
risk = min_risk+(risk-min_risk)*0.40
|
| 806 |
+
elif drawdown <= -0.06:
|
| 807 |
+
risk = min_risk+(risk-min_risk)*0.70
|
| 808 |
+
return float(np.clip(risk, min_risk, max_risk)), float(score)
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def simulate_equity(r_values, base_risk_pct):
|
| 812 |
+
eq, peak = 1.0, 1.0
|
| 813 |
+
curve, dds = [1.0], [0.0]
|
| 814 |
+
for r in r_values:
|
| 815 |
+
risk = base_risk_pct/100*float(dd_mult(eq/peak-1))
|
| 816 |
+
eq *= max(0.01, 1+risk*float(r))
|
| 817 |
+
peak = max(peak, eq)
|
| 818 |
+
curve.append(eq)
|
| 819 |
+
dds.append(eq/peak-1)
|
| 820 |
+
return np.array(curve), np.array(dds)
|
| 821 |
+
|
| 822 |
+
|
| 823 |
+
def risk_path_mc(r_multiples, base_risk_pct, sims=2000, n_trades=100):
|
| 824 |
+
# R biriminde bootstrap; kayıp 1R ile sınırlanmaz (maliyet nedeniyle ~-1.05R olabilir).
|
| 825 |
+
if len(r_multiples) < 10:
|
| 826 |
+
return None
|
| 827 |
+
rng = np.random.default_rng(20261103)
|
| 828 |
+
S = rng.choice(np.asarray(r_multiples, float), size=(sims, n_trades), replace=True)
|
| 829 |
+
eq, peak, mdd = np.ones(sims), np.ones(sims), np.zeros(sims)
|
| 830 |
+
paths = np.empty((n_trades+1, sims))
|
| 831 |
+
paths[0] = 1.0
|
| 832 |
+
for t in range(n_trades):
|
| 833 |
+
risk = base_risk_pct/100*dd_mult(eq/peak-1)
|
| 834 |
+
eq = eq*np.maximum(0.01, 1+risk*S[:, t])
|
| 835 |
+
peak = np.maximum(peak, eq)
|
| 836 |
+
mdd = np.minimum(mdd, eq/peak-1)
|
| 837 |
+
paths[t+1] = eq
|
| 838 |
+
fin = eq-1
|
| 839 |
+
return {"median": float(np.median(fin)), "p05": float(np.percentile(fin, 5)),
|
| 840 |
+
"p95": float(np.percentile(fin, 95)), "dd95": float(np.percentile(mdd, 5)),
|
| 841 |
+
"ruin": float(np.mean(eq < 0.5)),
|
| 842 |
+
"pct": np.percentile(paths, [5, 25, 50, 75, 95], axis=1)-1.0}
|
| 843 |
+
|
| 844 |
+
|
| 845 |
+
# ---------------------------------------------------------------- yardımcılar
|
| 846 |
+
def fmt_p(x):
|
| 847 |
+
return f"{x:,.2f}" if abs(x) >= 100 else f"{x:,.4f}"
|
| 848 |
+
|
| 849 |
+
|
| 850 |
+
def md_table(header, rows):
|
| 851 |
+
if not rows:
|
| 852 |
+
return "_Veri yok._"
|
| 853 |
+
out = ["| " + " | ".join(header) + " |", "|" + "|".join(["---"]*len(header)) + "|"]
|
| 854 |
+
out += ["| " + " | ".join(str(c) for c in r) + " |" for r in rows]
|
| 855 |
+
return "\n".join(out)
|
| 856 |
+
|
| 857 |
+
|
| 858 |
+
def trade_stats(R, curve, dds, trades_df):
|
| 859 |
+
if len(R) == 0:
|
| 860 |
+
return None
|
| 861 |
+
wins, losses = R[R > 0].sum(), -R[R < 0].sum()
|
| 862 |
+
reasons = trades_df.exit_reason.value_counts().to_dict()
|
| 863 |
+
return {"n": len(R), "win": float((R > 0).mean()), "exp": float(R.mean()),
|
| 864 |
+
"pf": float(wins/losses) if losses > 0 else float("inf"),
|
| 865 |
+
"ret": float(curve[-1]-1), "mdd": float(dds.min()),
|
| 866 |
+
"tp": reasons.get("TP", 0), "sl": reasons.get("SL", 0),
|
| 867 |
+
"time": reasons.get("TIME", 0)}
|
| 868 |
+
|
| 869 |
+
|
| 870 |
+
TRADE_COLS = ["entry_date", "exit_date", "side", "entry_price", "exit_reason",
|
| 871 |
+
"net_return_pct", "R", "regime"]
|
| 872 |
+
|
| 873 |
+
|
| 874 |
+
# ---------------------------------------------------------------- grafikler
|
| 875 |
+
def price_figure(df, ticker, trades_df, trade_ok, stop, target, ind=None, vp=None, vp_prof=None,
|
| 876 |
+
ldc=None, combo=None, gann=None, gann_fan=True, gann_extend=True, smc=None):
|
| 877 |
+
w = df.tail(150)
|
| 878 |
+
sub = vp is not None and vp_prof is not None
|
| 879 |
+
if sub:
|
| 880 |
+
fig = make_subplots(rows=1, cols=2, shared_yaxes=True,
|
| 881 |
+
column_widths=[0.86, 0.14], horizontal_spacing=0.005)
|
| 882 |
+
else:
|
| 883 |
+
fig = go.Figure()
|
| 884 |
+
kw = dict(row=1, col=1) if sub else {}
|
| 885 |
+
fig.add_trace(go.Candlestick(x=w.index, open=w.Open, high=w.High, low=w.Low,
|
| 886 |
+
close=w.Close, name=ticker), **kw)
|
| 887 |
+
for n, col in [(20, "#f59e0b"), (50, "#3b82f6")]:
|
| 888 |
+
ema = df.Close.ewm(span=n, adjust=False).mean().tail(150)
|
| 889 |
+
fig.add_trace(go.Scatter(x=w.index, y=ema, mode="lines", name=f"EMA{n}",
|
| 890 |
+
line=dict(width=1.2, color=col)), **kw)
|
| 891 |
+
if ind is not None:
|
| 892 |
+
st_line, st_dir, ut_stop = (np.asarray(a)[-150:] for a in ind)
|
| 893 |
+
for d, col, nm in [(1, "#16a34a", "Supertrend ↑"), (-1, "#dc2626", "Supertrend ↓")]:
|
| 894 |
+
fig.add_trace(go.Scatter(x=w.index, y=np.where(st_dir == d, st_line, np.nan),
|
| 895 |
+
mode="lines", name=nm, line=dict(width=1.5, color=col)), **kw)
|
| 896 |
+
fig.add_trace(go.Scatter(x=w.index, y=ut_stop, mode="lines", name="UT trailing stop",
|
| 897 |
+
line=dict(width=1, dash="dot", color="#9333ea")), **kw)
|
| 898 |
+
if sub:
|
| 899 |
+
vw = vp.tail(150)
|
| 900 |
+
for k, col, dash, nm in [("poc", "#f59e0b", "solid", "POC"),
|
| 901 |
+
("vah", "#64748b", "dash", "VAH"),
|
| 902 |
+
("val", "#64748b", "dash", "VAL")]:
|
| 903 |
+
fig.add_trace(go.Scatter(x=w.index, y=vw[k], mode="lines", name=nm,
|
| 904 |
+
line=dict(width=1.4, color=col, dash=dash)), **kw)
|
| 905 |
+
edges, prof, poc, val, vah = vp_prof
|
| 906 |
+
mid = (edges[:-1]+edges[1:])/2
|
| 907 |
+
colors = ["#f59e0b" if edges[k] <= poc <= edges[k+1]
|
| 908 |
+
else ("#60a5fa" if val <= mid[k] <= vah else "#cbd5e1")
|
| 909 |
+
for k in range(len(mid))]
|
| 910 |
+
fig.add_trace(go.Bar(x=prof, y=mid, orientation="h", width=(edges[1]-edges[0])*0.95,
|
| 911 |
+
marker_color=colors, name="Volume Profile", showlegend=False,
|
| 912 |
+
hoverinfo="skip"), row=1, col=2)
|
| 913 |
+
fig.update_xaxes(showticklabels=False, row=1, col=2)
|
| 914 |
+
if ldc is not None:
|
| 915 |
+
yh = np.asarray(ldc["yhat"])[-150:]
|
| 916 |
+
rising = np.r_[False, np.diff(yh) > 0]
|
| 917 |
+
for flag, col, nm in [(True, "#16a34a", "Lorentzian kernel ↑"), (False, "#dc2626", "Lorentzian kernel ↓")]:
|
| 918 |
+
fig.add_trace(go.Scatter(x=w.index, y=np.where(rising == flag, yh, np.nan), mode="lines",
|
| 919 |
+
name=nm, line=dict(width=2, color=col)), **kw)
|
| 920 |
+
st_ = np.asarray(ldc["state"])[-150:]
|
| 921 |
+
prev = np.r_[0, st_[:-1]]
|
| 922 |
+
for d, nm, col, ycol, off in [(1, "LDC long", "#15803d", "Low", 0.995),
|
| 923 |
+
(-1, "LDC short", "#b91c1c", "High", 1.005)]:
|
| 924 |
+
m_ = (st_ == d) & (prev != d)
|
| 925 |
+
if m_.any():
|
| 926 |
+
fig.add_trace(go.Scatter(x=w.index[m_], y=w[ycol].to_numpy()[m_]*off, mode="markers",
|
| 927 |
+
name=nm, marker=dict(symbol="diamond", size=8, color=col,
|
| 928 |
+
line=dict(width=1, color="white"))), **kw)
|
| 929 |
+
if combo is not None:
|
| 930 |
+
cs = np.asarray(combo)[-150:]
|
| 931 |
+
pv = np.r_[0, cs[:-1]]
|
| 932 |
+
for d, nm, col, ycol, off in [(1, "Birleşik LONG", "#f59e0b", "Low", 0.985),
|
| 933 |
+
(-1, "Birleşik SHORT", "#7c3aed", "High", 1.015)]:
|
| 934 |
+
m_ = (cs == d) & (pv != d)
|
| 935 |
+
if m_.any():
|
| 936 |
+
fig.add_trace(go.Scatter(x=w.index[m_], y=w[ycol].to_numpy()[m_]*off, mode="markers",
|
| 937 |
+
name=nm, marker=dict(symbol="star", size=12, color=col,
|
| 938 |
+
line=dict(width=1, color="white"))), **kw)
|
| 939 |
+
if len(trades_df):
|
| 940 |
+
t = trades_df[trades_df.entry_date.isin(w.index)]
|
| 941 |
+
for side, sym, nm in [("LONG", "triangle-up", "OOS long"),
|
| 942 |
+
("SHORT", "triangle-down", "OOS short")]:
|
| 943 |
+
s = t[t.side == side]
|
| 944 |
+
if len(s):
|
| 945 |
+
fig.add_trace(go.Scatter(
|
| 946 |
+
x=s.entry_date, y=s.entry_price, mode="markers", name=nm,
|
| 947 |
+
marker=dict(symbol=sym, size=9,
|
| 948 |
+
color=["#16a34a" if r > 0 else "#dc2626" for r in s.R],
|
| 949 |
+
line=dict(width=1, color="white")),
|
| 950 |
+
text=[f"R={r:.2f} · {why}" for r, why in zip(s.R, s.exit_reason)]), **kw)
|
| 951 |
+
if smc is not None:
|
| 952 |
+
add_smc_traces(fig, smc, df.index, last_n=150, kw=kw)
|
| 953 |
+
if gann is not None:
|
| 954 |
+
add_gann_traces(fig, gann, df.index, extend=gann_extend, fan=gann_fan, kw=kw)
|
| 955 |
+
if trade_ok:
|
| 956 |
+
fig.add_hline(y=stop, line_dash="dash", line_color="#dc2626", annotation_text="SL", **kw)
|
| 957 |
+
fig.add_hline(y=target, line_dash="dash", line_color="#16a34a", annotation_text="TP", **kw)
|
| 958 |
+
fig.update_layout(title=f"{ticker} Günlük — son 150 mum (üçgenler: OOS sinyalleri)"
|
| 959 |
+
+ (" + Volume Profile" if sub else "") + (" + Gann Box" if gann is not None else "") + (" + SMC" if smc is not None else ""),
|
| 960 |
+
height=560, xaxis_rangeslider_visible=False)
|
| 961 |
+
return fig
|
| 962 |
+
|
| 963 |
+
|
| 964 |
+
def prob_figure(p_down, p_flat, p_up):
|
| 965 |
+
fig = go.Figure(go.Bar(x=["DOWN", "FLAT", "UP"], y=[p_down*100, p_flat*100, p_up*100],
|
| 966 |
+
marker_color=["#dc2626", "#9ca3af", "#16a34a"],
|
| 967 |
+
text=[f"{p*100:.1f}%" for p in (p_down, p_flat, p_up)],
|
| 968 |
+
textposition="outside"))
|
| 969 |
+
fig.update_layout(title="Kalibre edilmiş olasılıklar", height=300,
|
| 970 |
+
yaxis=dict(range=[0, 100], title="%"), margin=dict(t=50, b=30))
|
| 971 |
+
return fig
|
| 972 |
+
|
| 973 |
+
|
| 974 |
+
def equity_figure(curve, dds):
|
| 975 |
+
x = np.arange(len(curve))
|
| 976 |
+
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.7, 0.3],
|
| 977 |
+
vertical_spacing=0.06)
|
| 978 |
+
fig.add_trace(go.Scatter(x=x, y=(curve-1)*100, mode="lines", name="Kümülatif getiri %"), 1, 1)
|
| 979 |
+
fig.add_trace(go.Scatter(x=x, y=dds*100, fill="tozeroy", name="Drawdown %",
|
| 980 |
+
line=dict(color="#dc2626")), 2, 1)
|
| 981 |
+
fig.update_xaxes(title_text="İşlem #", row=2, col=1)
|
| 982 |
+
fig.update_layout(title="OOS backtest — adaptif risk ile bileşik özsermaye", height=520)
|
| 983 |
+
return fig
|
| 984 |
+
|
| 985 |
+
|
| 986 |
+
def pf_gauge(pf, n):
|
| 987 |
+
shown = 3.0 if np.isinf(pf) else float(min(pf, 3.0))
|
| 988 |
+
fig = go.Figure(go.Indicator(
|
| 989 |
+
mode="gauge+number", value=shown,
|
| 990 |
+
number=dict(valueformat=".2f", suffix=" (∞)" if np.isinf(pf) else ""),
|
| 991 |
+
title=dict(text=f"Profit Factor — OOS ({n} işlem)"),
|
| 992 |
+
gauge=dict(
|
| 993 |
+
axis=dict(range=[0, 3]),
|
| 994 |
+
bar=dict(color="#1d4ed8"),
|
| 995 |
+
steps=[dict(range=[0, 1], color="#fecaca"),
|
| 996 |
+
dict(range=[1, 1.5], color="#fef08a"),
|
| 997 |
+
dict(range=[1.5, 3], color="#bbf7d0")],
|
| 998 |
+
threshold=dict(line=dict(color="black", width=3), thickness=0.8, value=1.0))))
|
| 999 |
+
fig.update_layout(height=320, margin=dict(t=70, b=20))
|
| 1000 |
+
return fig
|
| 1001 |
+
|
| 1002 |
+
|
| 1003 |
+
def mc_figure(mc):
|
| 1004 |
+
p = mc["pct"]*100
|
| 1005 |
+
x = np.arange(p.shape[1])
|
| 1006 |
+
fig = go.Figure()
|
| 1007 |
+
fig.add_trace(go.Scatter(x=x, y=p[0], line=dict(width=0), showlegend=False, hoverinfo="skip"))
|
| 1008 |
+
fig.add_trace(go.Scatter(x=x, y=p[4], fill="tonexty", line=dict(width=0),
|
| 1009 |
+
fillcolor="rgba(59,130,246,0.15)", name="%5–%95"))
|
| 1010 |
+
fig.add_trace(go.Scatter(x=x, y=p[1], line=dict(width=0), showlegend=False, hoverinfo="skip"))
|
| 1011 |
+
fig.add_trace(go.Scatter(x=x, y=p[3], fill="tonexty", line=dict(width=0),
|
| 1012 |
+
fillcolor="rgba(59,130,246,0.30)", name="%25–%75"))
|
| 1013 |
+
fig.add_trace(go.Scatter(x=x, y=p[2], line=dict(color="#1d4ed8", width=2), name="Medyan"))
|
| 1014 |
+
fig.update_layout(title="Monte Carlo — 100 işlemlik getiri yelpazesi (%)",
|
| 1015 |
+
xaxis_title="İşlem #", yaxis_title="Getiri %", height=460)
|
| 1016 |
+
return fig
|
| 1017 |
+
|
| 1018 |
+
|
| 1019 |
+
# ---------------------------------------------------------------- ana motor
|
| 1020 |
+
def run_engine(ticker, period, horizon, side_mode, equity, min_risk, max_risk,
|
| 1021 |
+
max_leverage, fee, slip, train_window,
|
| 1022 |
+
p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
|
| 1023 |
+
skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period,
|
| 1024 |
+
vp_mode, vp_window, vp_bins, vp_va, vp_ext,
|
| 1025 |
+
ldc_mode, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw,
|
| 1026 |
+
ldc_use_vol, ldc_use_regime, ldc_use_adx, ldc_kernel, ldc_exact,
|
| 1027 |
+
combo_mode, combo_votes, ind_tpsl, tpsl_smin, tpsl_smax, tpsl_minrr,
|
| 1028 |
+
gann_mode, gann_lb, gann_fan_on, gann_ext_on, gann_rev,
|
| 1029 |
+
smc_mode, smc_src, smc_swing_len, smc_int_len, smc_zone, smc_draw,
|
| 1030 |
+
smc_conf, smc_obf, smc_lvl):
|
| 1031 |
+
try:
|
| 1032 |
+
ticker = str(ticker).strip().upper()
|
| 1033 |
+
h = int(horizon)
|
| 1034 |
+
equity, fee, slip = float(equity), float(fee), float(slip)
|
| 1035 |
+
min_risk, max_risk = float(min_risk), float(max_risk)
|
| 1036 |
+
if max_risk < min_risk:
|
| 1037 |
+
raise ValueError("Maksimum risk, minimum riskten küçük olamaz.")
|
| 1038 |
+
cost = 2*(fee+slip)/100
|
| 1039 |
+
allow = {1: side_mode != "Sadece Short", -1: side_mode != "Sadece Long"}
|
| 1040 |
+
|
| 1041 |
+
df = get_market_data(ticker, period)
|
| 1042 |
+
if len(df) < 450:
|
| 1043 |
+
raise ValueError("En az 450 günlük mum gerekli.")
|
| 1044 |
+
df = df.iloc[:-1].copy() # tamamlanmamış güncel mumu çıkar
|
| 1045 |
+
vp_on = vp_mode != VP_MODES[0]
|
| 1046 |
+
vp_feat = vp_mode == VP_MODES[2]
|
| 1047 |
+
vp_win, vp_nb, vp_vap = int(vp_window), int(vp_bins), float(vp_va)
|
| 1048 |
+
vp = volume_profile(df, vp_win, vp_nb, vp_vap)
|
| 1049 |
+
X_all, atr_all = make_features(df, vp if vp_feat else None)
|
| 1050 |
+
vp_arr = vp_signal(df, atr_all, vp, float(vp_ext))
|
| 1051 |
+
vp_names = {1: "LONG onaylı", -1: "SHORT onaylı", 0: "onay yok"}
|
| 1052 |
+
vp_no_vol = float(df.Volume.tail(vp_win).sum()) <= 0
|
| 1053 |
+
lp = latest_profile(df, vp_win, vp_nb, vp_vap)
|
| 1054 |
+
y_all, _ = make_target(df, atr_all, h)
|
| 1055 |
+
gann_on = gann_mode != GANN_MODES[0]
|
| 1056 |
+
gann_arr, gann_pos = gann_signal(df, int(gann_lb))
|
| 1057 |
+
gann_now = int(gann_arr[-1])
|
| 1058 |
+
GB = gann_box(df, int(gann_lb), bool(gann_rev))
|
| 1059 |
+
smc_on = smc_mode != SMC_MODES[0]
|
| 1060 |
+
smc_only = smc_mode == SMC_MODES[2]
|
| 1061 |
+
SM = smc_analyze(df, int(smc_swing_len), int(smc_int_len), confluence=bool(smc_conf),
|
| 1062 |
+
ob_filter=smc_obf) if (smc_on or smc_draw) else None
|
| 1063 |
+
smc_lvl_on = bool(smc_lvl) and SM is not None and smc_on
|
| 1064 |
+
if SM is not None:
|
| 1065 |
+
SM["show_mtf"] = bool(smc_lvl)
|
| 1066 |
+
smc_arr = np.zeros(len(df), dtype=int)
|
| 1067 |
+
if SM is not None:
|
| 1068 |
+
smc_arr = (SM["swing_trend"] if smc_src == SMC_SRC[0] else SM["internal_trend"]).copy()
|
| 1069 |
+
if smc_zone: # LuxAlgo Premium/Discount kutuları: uçlarda %5, equilibrium %47.5-%52.5
|
| 1070 |
+
top_, bot_ = SM["top"], SM["bot"]
|
| 1071 |
+
rng_ = top_ - bot_
|
| 1072 |
+
close_ = df.Close.to_numpy(float)
|
| 1073 |
+
discount_edge = bot_ + 0.05*rng_
|
| 1074 |
+
premium_edge = top_ - 0.05*rng_
|
| 1075 |
+
zone_ = np.where(close_ <= discount_edge, 1,
|
| 1076 |
+
np.where(close_ >= premium_edge, -1, 0))
|
| 1077 |
+
smc_arr = np.where(zone_ == smc_arr, smc_arr, 0).astype(int)
|
| 1078 |
+
smc_now = int(smc_arr[-1])
|
| 1079 |
+
smc_entry = np.zeros(len(df), dtype=int) # yalnızca yapı kırılımı mumlarında giriş
|
| 1080 |
+
if SM is not None:
|
| 1081 |
+
want_internal = smc_src == SMC_SRC[1]
|
| 1082 |
+
ev_arr = np.zeros(len(df), dtype=int)
|
| 1083 |
+
for e_ in SM["events"]:
|
| 1084 |
+
if e_["internal"] == want_internal:
|
| 1085 |
+
ev_arr[e_["i"]] = 1 if e_["bull"] else -1
|
| 1086 |
+
smc_entry = np.where(ev_arr == smc_arr, ev_arr, 0).astype(int)
|
| 1087 |
+
|
| 1088 |
+
valid = X_all.notna().all(axis=1) & y_all.notna()
|
| 1089 |
+
X = X_all.loc[valid].copy()
|
| 1090 |
+
y = y_all.loc[X.index].astype(int)
|
| 1091 |
+
if len(X) < 300:
|
| 1092 |
+
raise ValueError("Temiz özelliklerden sonra yeterli veri kalmadı.")
|
| 1093 |
+
|
| 1094 |
+
oos_n = min(180, max(75, int(len(X)*0.22)))
|
| 1095 |
+
dev_end = len(X)-oos_n-h
|
| 1096 |
+
if dev_end < 200:
|
| 1097 |
+
raise ValueError("OOS ayrımı için yeterli geliştirme verisi yok.")
|
| 1098 |
+
tw = int(train_window)
|
| 1099 |
+
dev_X, dev_y = X.iloc[:dev_end], y.iloc[:dev_end]
|
| 1100 |
+
if len(dev_X) > tw:
|
| 1101 |
+
dev_X, dev_y = dev_X.iloc[-tw:], dev_y.iloc[-tw:]
|
| 1102 |
+
|
| 1103 |
+
regimes_all = regime_labels(X_all, X_all.loc[dev_X.index, "vol_20"])
|
| 1104 |
+
reg_arr = regimes_all.to_numpy()
|
| 1105 |
+
A = {"h": df.High.to_numpy(float), "l": df.Low.to_numpy(float),
|
| 1106 |
+
"c": df.Close.to_numpy(float), "a": atr_all.to_numpy(float)}
|
| 1107 |
+
pos_of = lambda idx: df.index.get_indexer(idx)
|
| 1108 |
+
|
| 1109 |
+
# --- indikatörler (Supertrend + UT Bot), tamamen Python'da
|
| 1110 |
+
st_dir, st_line = supertrend(df, int(st_period), float(st_mult))
|
| 1111 |
+
ut_pos, ut_stop = ut_bot(df, float(ut_key), int(ut_period))
|
| 1112 |
+
ind_arr = np.where((st_dir == ut_pos) & (ut_pos != 0), ut_pos, 0).astype(int)
|
| 1113 |
+
ind_only = ind_mode == "Sadece indikatör"
|
| 1114 |
+
ind_confirm = ind_mode == IND_MODES[1]
|
| 1115 |
+
ind_now = int(ind_arr[-1])
|
| 1116 |
+
|
| 1117 |
+
# --- Lorentzian Classification (ML + kernel) ve birleşik sinyal (oylama)
|
| 1118 |
+
combo_on = combo_mode != COMBO_MODES[0]
|
| 1119 |
+
combo_only = combo_mode == COMBO_MODES[2]
|
| 1120 |
+
ldc_on = ldc_mode != LDC_MODES[0] and not combo_only
|
| 1121 |
+
ldc_only = ldc_mode == LDC_MODES[2] and not combo_only
|
| 1122 |
+
ldc_confirm = ldc_mode == LDC_MODES[1] and not combo_only
|
| 1123 |
+
if combo_only: # oylar zaten birleşik sinyale girer; tekil filtreler çift sayılmasın
|
| 1124 |
+
ind_only = ind_confirm = vp_on = False
|
| 1125 |
+
ind_only = ind_only and not ldc_only # iki "Sadece" modu birlikteyse Lorentzian öncelikli
|
| 1126 |
+
if smc_only: # backtest yalnızca SMC girişlerine göre; diğer sinyal/filtre modları devre dışı
|
| 1127 |
+
ind_only = ind_confirm = vp_on = ldc_on = ldc_only = ldc_confirm = False
|
| 1128 |
+
combo_on = combo_only = False
|
| 1129 |
+
need_ldc = ldc_mode != LDC_MODES[0] or combo_on
|
| 1130 |
+
LD = ldc_classify(df, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw,
|
| 1131 |
+
use_vol=bool(ldc_use_vol), use_regime=bool(ldc_use_regime),
|
| 1132 |
+
use_adx=bool(ldc_use_adx), use_kernel=bool(ldc_kernel),
|
| 1133 |
+
exact=bool(ldc_exact)) if need_ldc else None
|
| 1134 |
+
ldc_arr = LD["state"] if LD else np.zeros(len(df), dtype=int)
|
| 1135 |
+
ldc_now = int(ldc_arr[-1])
|
| 1136 |
+
vp_now = int(vp_arr[-1])
|
| 1137 |
+
votes = np.vstack([ind_arr, ldc_arr, vp_arr]) # ST+UT, Lorentzian, Volume Profile
|
| 1138 |
+
n_long, n_short = (votes == 1).sum(axis=0), (votes == -1).sum(axis=0)
|
| 1139 |
+
mv = int(combo_votes)
|
| 1140 |
+
combo_arr = np.where((n_long >= mv) & (n_short == 0), 1,
|
| 1141 |
+
np.where((n_short >= mv) & (n_long == 0), -1, 0)).astype(int)
|
| 1142 |
+
combo_now = int(combo_arr[-1])
|
| 1143 |
+
|
| 1144 |
+
# --- indikatör bazlı SL/TP: en yakın ST / UT / VP seviyesi = invalidasyon (SL),
|
| 1145 |
+
# önündeki Value Area / POC seviyesi >= 1 ATR uzaktaysa TP üst sınırı
|
| 1146 |
+
ind_tpsl = bool(ind_tpsl)
|
| 1147 |
+
if ind_tpsl:
|
| 1148 |
+
c_, a_ = A["c"], A["a"]
|
| 1149 |
+
vpo, vpl, vph = (vp[k].to_numpy(float) for k in ("poc", "val", "vah"))
|
| 1150 |
+
|
| 1151 |
+
def nearest(levels, sign): # sign=+1: seviye fiyatın altında, -1: üstünde
|
| 1152 |
+
d = np.full(len(c_), np.inf)
|
| 1153 |
+
for lv in levels:
|
| 1154 |
+
dd = sign*(c_-lv)
|
| 1155 |
+
ok = np.isfinite(dd) & (dd > 0)
|
| 1156 |
+
d = np.where(ok & (dd < d), dd, d)
|
| 1157 |
+
return d
|
| 1158 |
+
smin, smax = float(tpsl_smin), float(tpsl_smax)
|
| 1159 |
+
sd_of = lambda d: np.clip(np.where(np.isfinite(d), d+0.1*a_, 1.5*a_), smin*a_, smax*a_)
|
| 1160 |
+
cap_of = lambda d: np.where(d >= 1.0*a_, d, np.inf)
|
| 1161 |
+
lv_l = [st_line, ut_stop, vpl] + ([SM["sw_lo"]] if (smc_only and SM is not None) else [])
|
| 1162 |
+
lv_s = [st_line, ut_stop, vph] + ([SM["sw_hi"]] if (smc_only and SM is not None) else [])
|
| 1163 |
+
cap_l, cap_s = [vpo, vph], [vpo, vpl]
|
| 1164 |
+
if smc_lvl_on: # Order Block + önceki D/W/M high-low: SL referansı ve TP sınırı
|
| 1165 |
+
lv_l += [SM["ob_sup"], SM["pdl"], SM["pwl"], SM["pml"]]
|
| 1166 |
+
lv_s += [SM["ob_res"], SM["pdh"], SM["pwh"], SM["pmh"]]
|
| 1167 |
+
cap_l += [SM["ob_res"], SM["pdh"], SM["pwh"], SM["pmh"]]
|
| 1168 |
+
cap_s += [SM["ob_sup"], SM["pdl"], SM["pwl"], SM["pml"]]
|
| 1169 |
+
A["ind_sd"] = {1: sd_of(nearest(lv_l, 1)),
|
| 1170 |
+
-1: sd_of(nearest(lv_s, -1))}
|
| 1171 |
+
A["ind_cap"] = {1: cap_of(nearest(cap_l, -1)),
|
| 1172 |
+
-1: cap_of(nearest(cap_s, 1))}
|
| 1173 |
+
|
| 1174 |
+
# --- K seçimi, kalibrasyon, güncel olasılık
|
| 1175 |
+
best_k, k_scores = tune_k(dev_X, dev_y, h)
|
| 1176 |
+
cal_n = min(120, max(45, int(len(dev_X)*0.20)))
|
| 1177 |
+
fit_end = len(dev_X)-cal_n-h
|
| 1178 |
+
if fit_end <= 100:
|
| 1179 |
+
raise ValueError("Kalibrasyon için eğitim verisi yetersiz.")
|
| 1180 |
+
fit_X, fit_y = dev_X.iloc[:fit_end], dev_y.iloc[:fit_end]
|
| 1181 |
+
cal_X, cal_y = dev_X.iloc[fit_end+h:], dev_y.iloc[fit_end+h:]
|
| 1182 |
+
cal_model = pipe(best_k).fit(fit_X, fit_y)
|
| 1183 |
+
classes_now = cal_model.named_steps["knn"].classes_
|
| 1184 |
+
current_x = X_all.iloc[[-1]]
|
| 1185 |
+
probs = calibrate_current(cal_model.predict_proba(cal_X), cal_y.to_numpy(),
|
| 1186 |
+
cal_model.predict_proba(current_x), classes_now)
|
| 1187 |
+
p_down, p_flat, p_up = (float(p) for p in probs)
|
| 1188 |
+
|
| 1189 |
+
if side_mode == "Sadece Long":
|
| 1190 |
+
side = 1
|
| 1191 |
+
elif side_mode == "Sadece Short":
|
| 1192 |
+
side = -1
|
| 1193 |
+
else:
|
| 1194 |
+
side = 1 if p_up >= p_down else -1
|
| 1195 |
+
if ind_only and ind_now != 0 and allow[ind_now]:
|
| 1196 |
+
side = ind_now
|
| 1197 |
+
if ldc_only and ldc_now != 0 and allow[ldc_now]:
|
| 1198 |
+
side = ldc_now
|
| 1199 |
+
if combo_only and combo_now != 0 and allow[combo_now]:
|
| 1200 |
+
side = combo_now
|
| 1201 |
+
if smc_only and smc_now != 0 and allow[smc_now]:
|
| 1202 |
+
side = smc_now
|
| 1203 |
+
p_side, p_opp = (p_up, p_down) if side == 1 else (p_down, p_up)
|
| 1204 |
+
|
| 1205 |
+
imp, scaler = cal_model.named_steps["imputer"], cal_model.named_steps["scale"]
|
| 1206 |
+
nn = NearestNeighbors(n_neighbors=best_k).fit(scaler.transform(imp.transform(fit_X)))
|
| 1207 |
+
_, idx = nn.kneighbors(scaler.transform(imp.transform(current_x)))
|
| 1208 |
+
agreement = float((fit_y.iloc[idx[0]].to_numpy() == side).mean())
|
| 1209 |
+
if ind_only: # indikatör modunda: son 10 mumda yönün sürekliliği
|
| 1210 |
+
agreement = float((ind_arr[-10:] == side).mean())
|
| 1211 |
+
if ldc_only:
|
| 1212 |
+
agreement = float((ldc_arr[-10:] == side).mean())
|
| 1213 |
+
if combo_only:
|
| 1214 |
+
agreement = float((combo_arr[-10:] == side).mean())
|
| 1215 |
+
if smc_only:
|
| 1216 |
+
agreement = float((smc_arr[-10:] == side).mean())
|
| 1217 |
+
|
| 1218 |
+
# --- yön bazlı TP/SL optimizasyonu (yalnızca geliştirme verisi)
|
| 1219 |
+
current_regime = str(regimes_all.iloc[-1])
|
| 1220 |
+
dev_pos = pos_of(dev_X.index)
|
| 1221 |
+
params, top, regime_used = {}, [], "—"
|
| 1222 |
+
|
| 1223 |
+
def opt_pos(s_):
|
| 1224 |
+
f_ = list(dev_pos)
|
| 1225 |
+
if ind_only or ind_confirm:
|
| 1226 |
+
f_ = [i for i in f_ if ind_arr[i] == s_]
|
| 1227 |
+
if ldc_on:
|
| 1228 |
+
f_ = [i for i in f_ if ldc_arr[i] == s_]
|
| 1229 |
+
if combo_on:
|
| 1230 |
+
f_ = [i for i in f_ if combo_arr[i] == s_]
|
| 1231 |
+
if vp_on:
|
| 1232 |
+
f_ = [i for i in f_ if vp_arr[i] == s_]
|
| 1233 |
+
if gann_on:
|
| 1234 |
+
f_ = [i for i in f_ if gann_arr[i] == s_]
|
| 1235 |
+
if smc_on and not smc_only:
|
| 1236 |
+
f_ = [i for i in f_ if smc_arr[i] == s_]
|
| 1237 |
+
if smc_only:
|
| 1238 |
+
f_ = [i for i in f_ if smc_entry[i] == s_]
|
| 1239 |
+
return f_ if len(f_) >= 35 else dev_pos
|
| 1240 |
+
for s in (1, -1):
|
| 1241 |
+
if allow[s]:
|
| 1242 |
+
sl_s, rr_s, top_s, used_s = optimize_barriers(
|
| 1243 |
+
A, opt_pos(s), reg_arr, s, cost, current_regime, h)
|
| 1244 |
+
params[s] = (sl_s, rr_s)
|
| 1245 |
+
if s == side:
|
| 1246 |
+
top, regime_used = top_s, used_s
|
| 1247 |
+
sl, rr = params[side]
|
| 1248 |
+
|
| 1249 |
+
# --- dokunulmamış OOS bloğu
|
| 1250 |
+
oos_X = X.iloc[dev_end+h:]
|
| 1251 |
+
oos_y = y.loc[oos_X.index]
|
| 1252 |
+
oos_pred = pipe(best_k).fit(dev_X, dev_y).predict(oos_X)
|
| 1253 |
+
oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
|
| 1254 |
+
oos_acc = float(accuracy_score(oos_y, oos_pred))
|
| 1255 |
+
oos_pos_arr = pos_of(oos_X.index)
|
| 1256 |
+
if ldc_only: # Lorentzian yönü OOS'ta tahmin yerine geçer
|
| 1257 |
+
oos_pred = ldc_arr[oos_pos_arr]
|
| 1258 |
+
oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
|
| 1259 |
+
oos_acc = float(accuracy_score(oos_y, oos_pred))
|
| 1260 |
+
if combo_only: # birleşik sinyal OOS'ta tahmin yerine geçer
|
| 1261 |
+
oos_pred = combo_arr[oos_pos_arr]
|
| 1262 |
+
oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
|
| 1263 |
+
oos_acc = float(accuracy_score(oos_y, oos_pred))
|
| 1264 |
+
if ind_only: # indikatör yönü OOS'ta tahmin yerine geçer
|
| 1265 |
+
oos_pred = ind_arr[oos_pos_arr]
|
| 1266 |
+
oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
|
| 1267 |
+
oos_acc = float(accuracy_score(oos_y, oos_pred))
|
| 1268 |
+
|
| 1269 |
+
if smc_only: # metrik: SMC yön durumu; işlem: yapı kırılımı mumları
|
| 1270 |
+
oos_pred = smc_arr[oos_pos_arr]
|
| 1271 |
+
oos_bacc = float(balanced_accuracy_score(oos_y, oos_pred))
|
| 1272 |
+
oos_acc = float(accuracy_score(oos_y, oos_pred))
|
| 1273 |
+
oos_pred = smc_entry[oos_pos_arr]
|
| 1274 |
+
rows, busy = [], -1
|
| 1275 |
+
for i, pred in zip(oos_pos_arr, oos_pred):
|
| 1276 |
+
pred = int(pred)
|
| 1277 |
+
if pred == 0 or not allow[pred]:
|
| 1278 |
+
continue
|
| 1279 |
+
if ind_confirm and ind_arr[i] != pred:
|
| 1280 |
+
continue
|
| 1281 |
+
if vp_on and vp_arr[i] != pred:
|
| 1282 |
+
continue
|
| 1283 |
+
if gann_on and gann_arr[i] != pred:
|
| 1284 |
+
continue
|
| 1285 |
+
if smc_on and smc_arr[i] != pred:
|
| 1286 |
+
continue
|
| 1287 |
+
if ldc_confirm and ldc_arr[i] != pred:
|
| 1288 |
+
continue
|
| 1289 |
+
if combo_on and combo_arr[i] != pred:
|
| 1290 |
+
continue
|
| 1291 |
+
if skip_overlap and i <= busy:
|
| 1292 |
+
continue
|
| 1293 |
+
sl_p, rr_p = params[pred]
|
| 1294 |
+
t = barrier_trade(A, i, pred, sl_p, rr_p, h)
|
| 1295 |
+
if t is None:
|
| 1296 |
+
continue
|
| 1297 |
+
ret, j, why = t
|
| 1298 |
+
net = ret-cost
|
| 1299 |
+
r = net/max(stop_dist(A, i, pred, sl_p)/A["c"][i], 1e-12)
|
| 1300 |
+
rows.append((df.index[i], df.index[j], "LONG" if pred == 1 else "SHORT",
|
| 1301 |
+
A["c"][i], why, net*100, r, reg_arr[i]))
|
| 1302 |
+
busy = j
|
| 1303 |
+
trades_df = pd.DataFrame(rows, columns=TRADE_COLS)
|
| 1304 |
+
R = trades_df.R.to_numpy() if len(trades_df) else np.array([])
|
| 1305 |
+
expectancy_r = float(R.mean()) if len(R) else 0.0
|
| 1306 |
+
|
| 1307 |
+
recent = df.Close.tail(30)
|
| 1308 |
+
drawdown = float(recent.iloc[-1]/recent.cummax().iloc[-1]-1)
|
| 1309 |
+
risk_pct, _ = adaptive_risk(min_risk, max_risk, oos_bacc, agreement,
|
| 1310 |
+
expectancy_r, len(R), drawdown)
|
| 1311 |
+
curve, dds = simulate_equity(R, risk_pct)
|
| 1312 |
+
stats = trade_stats(R, curve, dds, trades_df)
|
| 1313 |
+
mc = risk_path_mc(R, risk_pct) if len(R) else None
|
| 1314 |
+
|
| 1315 |
+
# --- güncel seviyeler ve pozisyon
|
| 1316 |
+
entry, cur_atr = float(df.Close.iloc[-1]), float(atr_all.iloc[-1])
|
| 1317 |
+
if ind_tpsl:
|
| 1318 |
+
stop_d = float(A["ind_sd"][side][-1])
|
| 1319 |
+
target_d = float(min(stop_d*rr, A["ind_cap"][side][-1]))
|
| 1320 |
+
cl_ = float(A["c"][-1])
|
| 1321 |
+
cands = {"Supertrend": float(st_line[-1]), "UT trailing stop": float(ut_stop[-1]),
|
| 1322 |
+
"VP " + ("VAL" if side == 1 else "VAH"):
|
| 1323 |
+
float((vp.val if side == 1 else vp.vah).iloc[-1])}
|
| 1324 |
+
if smc_lvl_on:
|
| 1325 |
+
kk_ = ("ob_sup", "pdl", "pwl", "pml") if side == 1 else ("ob_res", "pdh", "pwh", "pmh")
|
| 1326 |
+
for k_, nm_ in zip(kk_, ("SMC OB", "Önceki gün", "Önceki hafta", "Önceki ay")):
|
| 1327 |
+
cands[nm_] = float(SM[k_][-1])
|
| 1328 |
+
if smc_only and SM is not None:
|
| 1329 |
+
cands["SMC swing " + ("low" if side == 1 else "high")] = float(SM["sw_lo" if side == 1 else "sw_hi"][-1])
|
| 1330 |
+
valid = {k_: side*(cl_-v_) for k_, v_ in cands.items() if np.isfinite(v_) and side*(cl_-v_) > 0}
|
| 1331 |
+
sl_name = min(valid, key=valid.get) if valid else "ATR varsayılan (seviye yok)"
|
| 1332 |
+
else:
|
| 1333 |
+
stop_d, target_d = sl*cur_atr, sl*cur_atr*rr
|
| 1334 |
+
stop = entry-stop_d if side == 1 else entry+stop_d
|
| 1335 |
+
target = entry+target_d if side == 1 else entry-target_d
|
| 1336 |
+
|
| 1337 |
+
risk_cash = equity*risk_pct/100
|
| 1338 |
+
cost_per_unit = stop_d+entry*cost
|
| 1339 |
+
qty = risk_cash/cost_per_unit if cost_per_unit > 0 else 0.0
|
| 1340 |
+
max_notional = equity*float(max_leverage)
|
| 1341 |
+
capped = qty*entry > max_notional
|
| 1342 |
+
if capped:
|
| 1343 |
+
qty = max_notional/entry
|
| 1344 |
+
if whole_units:
|
| 1345 |
+
qty = float(np.floor(qty))
|
| 1346 |
+
notional = qty*entry
|
| 1347 |
+
actual_risk = qty*cost_per_unit
|
| 1348 |
+
margin = notional/max(float(max_leverage), 1)
|
| 1349 |
+
|
| 1350 |
+
# --- işlem kapısı (her kontrol ayrı gösterilir)
|
| 1351 |
+
checks = [
|
| 1352 |
+
("Y��n izni", side_mode, "—", allow[side]),
|
| 1353 |
+
("P(yön)", f"{p_side:.1%}", f"≥ {p_min:.0%}", p_side >= p_min),
|
| 1354 |
+
("P(yön) − P(karşı)", f"{(p_side-p_opp):.1%}", f"≥ {p_gap:.0%}", (p_side-p_opp) >= p_gap),
|
| 1355 |
+
("P(FLAT)", f"{p_flat:.1%}", f"< {flat_max:.0%}", p_flat < flat_max),
|
| 1356 |
+
("Komşu yön uyumu", f"{agreement:.1%}", f"≥ {agree_min:.0%}", agreement >= agree_min),
|
| 1357 |
+
("OOS balanced acc.", f"{oos_bacc:.3f}", f"≥ {bacc_min:.2f}", oos_bacc >= bacc_min),
|
| 1358 |
+
("OOS işlem sayısı", f"{len(R)}", f"≥ {int(min_trades)}", len(R) >= int(min_trades)),
|
| 1359 |
+
("OOS expectancy", f"{expectancy_r:.3f}R", "> 0", expectancy_r > 0),
|
| 1360 |
+
]
|
| 1361 |
+
dname = {1: "LONG ↑", -1: "SHORT ↓", 0: "belirsiz"}
|
| 1362 |
+
ind_check = ("İndikatör yönü (ST + UT aynı)", dname[ind_now], "yönle aynı", ind_now == side)
|
| 1363 |
+
if ind_only or ldc_only or combo_only or smc_only:
|
| 1364 |
+
keep = ("Yön izni", "OOS balanced acc.", "OOS işlem sayısı", "OOS expectancy")
|
| 1365 |
+
if smc_only:
|
| 1366 |
+
keep = tuple(k_ for k_ in keep if k_ != "OOS balanced acc.")
|
| 1367 |
+
checks = [c for c in checks if c[0] in keep]
|
| 1368 |
+
if ind_only or ind_confirm:
|
| 1369 |
+
checks.insert(1, ind_check)
|
| 1370 |
+
if ldc_on:
|
| 1371 |
+
checks.insert(1, ("Lorentzian yönü (ML + kernel)", dname[ldc_now], "yönle aynı",
|
| 1372 |
+
ldc_now == side))
|
| 1373 |
+
if combo_on:
|
| 1374 |
+
nv = int(sum(1 for v_ in (ind_now, ldc_now, vp_now) if v_ == side))
|
| 1375 |
+
no_ = int(sum(1 for v_ in (ind_now, ldc_now, vp_now) if v_ == -side))
|
| 1376 |
+
checks.insert(1, ("Birleşik sinyal (ST+UT · Lorentzian · VP)", f"{nv}/3 oy, karşı {no_}",
|
| 1377 |
+
f"≥ {mv} oy, karşı oy yok", combo_now == side))
|
| 1378 |
+
if ind_tpsl:
|
| 1379 |
+
rr_now = target_d/stop_d if stop_d > 0 else 0.0
|
| 1380 |
+
checks.append(("İndikatör R:R (TP/SL)", f"{rr_now:.2f}", f"≥ {float(tpsl_minrr):g}",
|
| 1381 |
+
rr_now >= float(tpsl_minrr)))
|
| 1382 |
+
if vp_on:
|
| 1383 |
+
checks.insert(1, ("Volume Profile (POC/VA konumu)", vp_names[int(vp_arr[-1])],
|
| 1384 |
+
"yönle aynı", int(vp_arr[-1]) == side))
|
| 1385 |
+
if gann_on:
|
| 1386 |
+
checks.insert(1, ("Gann Box (%50 konumu)", dname[gann_now], "yönle aynı", gann_now == side))
|
| 1387 |
+
if smc_on:
|
| 1388 |
+
checks.insert(1, (f"SMC yapı yönü ({smc_src}" + (" + premium/discount)" if smc_zone else ")"),
|
| 1389 |
+
dname[smc_now], "yönle aynı", smc_now == side))
|
| 1390 |
+
trade_ok = all(c[3] for c in checks)
|
| 1391 |
+
if trade_ok:
|
| 1392 |
+
signal = "LONG" if side == 1 else "SHORT"
|
| 1393 |
+
else:
|
| 1394 |
+
signal = "WAIT — İŞLEM AÇMA"
|
| 1395 |
+
qty = notional = margin = actual_risk = 0.0
|
| 1396 |
+
gate_md = md_table(["Kontrol", "Değer", "Eşik", "Durum"],
|
| 1397 |
+
[(n, v, t, "✅" if ok else "❌") for n, v, t, ok in checks])
|
| 1398 |
+
|
| 1399 |
+
# --- rapor
|
| 1400 |
+
if stats:
|
| 1401 |
+
pf = "∞" if np.isinf(stats["pf"]) else f"{stats['pf']:.2f}"
|
| 1402 |
+
oos_md = md_table(["Ölçüm", "Değer"], [
|
| 1403 |
+
("İşlem sayısı", stats["n"]),
|
| 1404 |
+
("Kazanma oranı", f"{stats['win']:.1%}"),
|
| 1405 |
+
("Expectancy", f"{stats['exp']:.3f}R"),
|
| 1406 |
+
("Profit factor", pf),
|
| 1407 |
+
(f"Toplam getiri (risk {risk_pct:.2f}%)", f"{stats['ret']:.2%}"),
|
| 1408 |
+
("Maks. drawdown", f"{stats['mdd']:.2%}"),
|
| 1409 |
+
("Çıkış dağılımı TP / SL / süre", f"{stats['tp']} / {stats['sl']} / {stats['time']}"),
|
| 1410 |
+
])
|
| 1411 |
+
g = trades_df.groupby("regime").R.agg(["count", "mean", lambda s: (s > 0).mean()])
|
| 1412 |
+
reg_md = md_table(["Rejim", "İşlem", "Ort. R", "Kazanma"],
|
| 1413 |
+
[(r, int(c), f"{m:.3f}", f"{w:.1%}")
|
| 1414 |
+
for r, (c, m, w) in g.iterrows()])
|
| 1415 |
+
else:
|
| 1416 |
+
oos_md = reg_md = "_OOS işlem yok._"
|
| 1417 |
+
|
| 1418 |
+
mc_text = (
|
| 1419 |
+
f"100 işlem · 2.000 bootstrap senaryosu · medyan: {mc['median']:.2%} · "
|
| 1420 |
+
f"%5 kötü sonuç: {mc['p05']:.2%} · %95 iyi sonuç: {mc['p95']:.2%} · "
|
| 1421 |
+
f"%5 kuyruk max DD: {mc['dd95']:.2%} · "
|
| 1422 |
+
f"%50 sermaye altına inme frekansı: {mc['ruin']:.1%}"
|
| 1423 |
+
if mc else "Monte Carlo için yeterli OOS işlem yok.")
|
| 1424 |
+
ind_md = md_table(["İndikatör", "Yön", "Seviye"], [
|
| 1425 |
+
(f"Supertrend ({int(st_period)}, {float(st_mult):g})", dname[int(st_dir[-1])], fmt_p(float(st_line[-1]))),
|
| 1426 |
+
(f"UT Bot ({float(ut_key):g}, {int(ut_period)})", dname[int(ut_pos[-1])], fmt_p(float(ut_stop[-1]))),
|
| 1427 |
+
("Bileşik (ikisi aynı yönde)", dname[ind_now], "—")])
|
| 1428 |
+
if lp is not None:
|
| 1429 |
+
_, _, poc_n, val_n, vah_n = lp
|
| 1430 |
+
pos_txt = ("VA üstünde" if entry > vah_n else "VA altında" if entry < val_n
|
| 1431 |
+
else "VA içinde · POC üstü" if entry > poc_n else "VA içinde · POC altı")
|
| 1432 |
+
vp_md = md_table(["Seviye", "Fiyat"], [
|
| 1433 |
+
("VAH (Value Area üst)", fmt_p(vah_n)), ("POC (en yüksek hacim)", fmt_p(poc_n)),
|
| 1434 |
+
("VAL (Value Area alt)", fmt_p(val_n)), ("Son kapanış", f"{fmt_p(entry)} — {pos_txt}"),
|
| 1435 |
+
("VP yön onayı", vp_names[int(vp_arr[-1])])])
|
| 1436 |
+
else:
|
| 1437 |
+
vp_md = "_Profil hesaplanamadı._"
|
| 1438 |
+
vp_note = (f"Pencere {vp_win} mum · {vp_nb} kutu · Value Area %{vp_vap*100:.0f} · "
|
| 1439 |
+
f"uzama sınırı {float(vp_ext):g} ATR."
|
| 1440 |
+
+ (" Hacim verisi yok → zaman bazlı profil kullanıldı." if vp_no_vol else "")
|
| 1441 |
+
+ (" Mod Kapalı: yalnızca bilgi amaçlı." if not vp_on else ""))
|
| 1442 |
+
if SM is not None:
|
| 1443 |
+
last_ev = [e for e in SM["events"] if not e["internal"]][-1:] or [None]
|
| 1444 |
+
last_iv = [e for e in SM["events"] if e["internal"]][-1:] or [None]
|
| 1445 |
+
evtxt = lambda e: "—" if e is None else (f"{e['kind']} {'↑' if e['bull'] else '↓'} · {fmt_p(e['lvl'])} · "
|
| 1446 |
+
f"{df.index[e['i']].date()}")
|
| 1447 |
+
tp_, bt_ = float(SM["top"][-1]), float(SM["bot"][-1])
|
| 1448 |
+
zpos = (entry-bt_)/(tp_-bt_) if tp_ > bt_ else float("nan")
|
| 1449 |
+
zname = ("—" if not np.isfinite(zpos) else
|
| 1450 |
+
"Premium" if zpos >= 0.95 else
|
| 1451 |
+
"Discount" if zpos <= 0.05 else
|
| 1452 |
+
"Equilibrium")
|
| 1453 |
+
obt = lambda b: [o_ for o_ in SM["obs"] if o_["bull"] == b and not o_["internal"]][-1:]
|
| 1454 |
+
ob_txt = lambda b: (f"{fmt_p(obt(b)[0]['lo'])} – {fmt_p(obt(b)[0]['hi'])}" if obt(b) else "—")
|
| 1455 |
+
smc_md = md_table(["Bileşen", "Değer"], [
|
| 1456 |
+
("Swing yapı yönü", dname[int(SM["swing_trend"][-1])]),
|
| 1457 |
+
("Internal yapı yönü", dname[int(SM["internal_trend"][-1])]),
|
| 1458 |
+
("Son swing kırılımı", evtxt(last_ev[0])), ("Son internal kırılımı", evtxt(last_iv[0])),
|
| 1459 |
+
("Premium / Discount", f"{zname}" + (f" (%{zpos*100:.0f})" if np.isfinite(zpos) else "")),
|
| 1460 |
+
("Strong/Weak High · Low", f"{'Strong' if SM['strong_high'] else 'Weak'} {fmt_p(tp_)} · "
|
| 1461 |
+
f"{'Strong' if SM['strong_low'] else 'Weak'} {fmt_p(bt_)}"),
|
| 1462 |
+
("Aktif swing OB (bull)", ob_txt(True)), ("Aktif swing OB (bear)", ob_txt(False)),
|
| 1463 |
+
("Önceki gün H / L", f"{fmt_p(SM['pdh'][-1])} / {fmt_p(SM['pdl'][-1])}"),
|
| 1464 |
+
("Önceki hafta H / L", f"{fmt_p(SM['pwh'][-1])} / {fmt_p(SM['pwl'][-1])}"),
|
| 1465 |
+
("Önceki ay H / L", f"{fmt_p(SM['pmh'][-1])} / {fmt_p(SM['pml'][-1])}"),
|
| 1466 |
+
("Açık FVG sayısı", len(SM["fvg"])), ("SMC yön onayı", dname[smc_now])])
|
| 1467 |
+
smc_note = (f"Swing {int(smc_swing_len)} · internal {int(smc_int_len)} mum"
|
| 1468 |
+
+ (" · premium/discount kapısı açık" if smc_zone else "")
|
| 1469 |
+
+ (" · internal confluence filtresi (LuxAlgo Pine koşulları)" if smc_conf else "")
|
| 1470 |
+
+ " · BOS/CHoCH yalnızca pivot doğrulandıktan ve kapanış seviyesi kırdıktan sonra sayılır"
|
| 1471 |
+
+ f" · OB filtresi: {smc_obf}"
|
| 1472 |
+
+ (" · OB + önceki D/W/M seviyeleri SL/TP'de" if smc_lvl_on else "")
|
| 1473 |
+
+ (" · Mod Kapalı: yalnızca bilgi/grafik." if not smc_on else "") + ".")
|
| 1474 |
+
else:
|
| 1475 |
+
smc_md, smc_note = "_Mod kapalı._", ""
|
| 1476 |
+
if GB is not None:
|
| 1477 |
+
gpos = gann_pos[-1]
|
| 1478 |
+
gann_md = md_table(["Seviye", "Fiyat"],
|
| 1479 |
+
[(f"{p:.3f}" + (" (orta)" if p == 0.5 else ""), fmt_p(y)) for p, y in sorted(GB["levels"].items(), reverse=True)]
|
| 1480 |
+
+ [("Son kapanış", f"{fmt_p(entry)} — kutu içi konum %{gpos*100:.0f}" if np.isfinite(gpos) else fmt_p(entry)),
|
| 1481 |
+
("Kutu yönü", "yükseliş (düşük → yüksek)" if GB["up"] else "düşüş (yüksek → düşük)"),
|
| 1482 |
+
("Gann yön onayı", dname[gann_now])])
|
| 1483 |
+
gann_note = (f"Pencere {int(gann_lb)} mum · yüksek {GB['hi_date'].date()} · düşük {GB['lo_date'].date()}"
|
| 1484 |
+
+ (" · yön çevrildi (reverse)" if gann_rev else "")
|
| 1485 |
+
+ (" · Mod Kapalı: yalnızca bilgi amaçlı." if not gann_on else "") + ".")
|
| 1486 |
+
else:
|
| 1487 |
+
gann_md, gann_note = "_Kutu hesaplanamadı._", ""
|
| 1488 |
+
if LD is not None:
|
| 1489 |
+
fl = lambda on, arr: "kapalı" if not on else ("✅" if bool(arr[-1]) else "❌")
|
| 1490 |
+
ldc_md = md_table(["Bileşen", "Değer"], [
|
| 1491 |
+
("Lorentzian yönü (ML + kernel)", dname[ldc_now]),
|
| 1492 |
+
("ML sinyali (filtreli)", dname[int(LD["signal"][-1])]),
|
| 1493 |
+
("ML tahmin skoru", f"{int(LD['pred'][-1]):+d} / ±{int(ldc_k)}"),
|
| 1494 |
+
("Kernel tahmini · eğim", f"{fmt_p(float(LD['yhat'][-1]))} · "
|
| 1495 |
+
f"{'↑' if LD['yhat'][-1] > LD['yhat'][-2] else '↓'}"),
|
| 1496 |
+
("Volatilite / Rejim / ADX filtresi",
|
| 1497 |
+
f"{fl(ldc_use_vol, LD['vol'])} / {fl(ldc_use_regime, LD['reg'])} / {fl(ldc_use_adx, LD['adx'])}")])
|
| 1498 |
+
ldc_note = (f"{int(ldc_k)} komşu · en fazla {int(ldc_back)} geçmiş mum · kernel "
|
| 1499 |
+
f"({int(ldc_kern_lb)}, {float(ldc_kern_rw):g}) · rejim eşiği {float(ldc_regime):g}.")
|
| 1500 |
+
else:
|
| 1501 |
+
ldc_md, ldc_note = "_Mod kapalı._", ""
|
| 1502 |
+
if combo_on:
|
| 1503 |
+
combo_md = md_table(["Kaynak", "Oy"], [
|
| 1504 |
+
("Supertrend + UT Bot", dname[ind_now]), ("Lorentzian (ML + kernel)", dname[ldc_now]),
|
| 1505 |
+
("Volume Profile (POC/VA)", dname[vp_now]),
|
| 1506 |
+
("**Birleşik sinyal**", f"**{dname[combo_now]}** (en az {mv} oy, karşı oy yok)")])
|
| 1507 |
+
else:
|
| 1508 |
+
combo_md = "_SMC-only modunda devre dışı._" if smc_only else "_Mod kapalı._"
|
| 1509 |
+
if ind_tpsl:
|
| 1510 |
+
capped = target_d < stop_d*rr-1e-12
|
| 1511 |
+
tp_title = "indikatörlerden türetildi"
|
| 1512 |
+
tpsl_note = (f"SL: en yakın invalidasyon seviyesi — **{sl_name}** (+0,1 ATR tampon, "
|
| 1513 |
+
f"{float(tpsl_smin):g}–{float(tpsl_smax):g} ATR aralığına sıkıştırılır). "
|
| 1514 |
+
f"TP: {rr:.2f}R katı"
|
| 1515 |
+
+ (", önündeki VA/POC/SMC OB/MTF seviyesinde sınırlandı" if capped else "")
|
| 1516 |
+
+ ". Optimize edilen tek parametre R katıdır.\n\n")
|
| 1517 |
+
else:
|
| 1518 |
+
tp_title, tpsl_note = "optimize", ""
|
| 1519 |
+
k_text = " | ".join(f"K={k}: {s:.3f}" for s, k in sorted(k_scores, reverse=True)) or "—"
|
| 1520 |
+
overlap_note = ("tek pozisyon (çakışma yok)" if skip_overlap
|
| 1521 |
+
else "çakışan sinyaller bağımsız işlem sayıldı")
|
| 1522 |
+
report = f"""
|
| 1523 |
+
# {signal}
|
| 1524 |
+
|
| 1525 |
+
**Son kapanmış mum:** {df.index[-1].date()} · **{ticker} referans fiyatı:** {fmt_p(entry)} · **Ufuk:** {h} gün
|
| 1526 |
+
|
| 1527 |
+
## Kapı kontrolleri
|
| 1528 |
+
{gate_md}
|
| 1529 |
+
|
| 1530 |
+
## {ticker} — Kalibre edilmiş KNN
|
| 1531 |
+
| Ölçüm | Değer |
|
| 1532 |
+
|---|---:|
|
| 1533 |
+
| P(UP) / P(FLAT) / P(DOWN) | {p_up:.1%} / {p_flat:.1%} / {p_down:.1%} |
|
| 1534 |
+
| Seçilen K | {best_k} |
|
| 1535 |
+
| OOS accuracy | {oos_acc:.3f} |
|
| 1536 |
+
| Güncel rejim | {current_regime} |
|
| 1537 |
+
|
| 1538 |
+
**K seçim skorları:** {k_text}
|
| 1539 |
+
|
| 1540 |
+
## İndikatörler — mod: {ind_mode}
|
| 1541 |
+
{ind_md}
|
| 1542 |
+
|
| 1543 |
+
## Birleşik sinyal — mod: {combo_mode}
|
| 1544 |
+
{combo_md}
|
| 1545 |
+
|
| 1546 |
+
## Lorentzian Classification — mod: {ldc_mode}
|
| 1547 |
+
{ldc_md}
|
| 1548 |
+
|
| 1549 |
+
{ldc_note}
|
| 1550 |
+
|
| 1551 |
+
## Volume Profile — mod: {vp_mode}
|
| 1552 |
+
{vp_md}
|
| 1553 |
+
|
| 1554 |
+
{vp_note}
|
| 1555 |
+
|
| 1556 |
+
## Smart Money Concepts (LuxAlgo) — mod: {smc_mode}
|
| 1557 |
+
{smc_md}
|
| 1558 |
+
|
| 1559 |
+
{smc_note}
|
| 1560 |
+
|
| 1561 |
+
## Gann Box (Zeiierman) — mod: {gann_mode}
|
| 1562 |
+
{gann_md}
|
| 1563 |
+
|
| 1564 |
+
{gann_note}
|
| 1565 |
+
|
| 1566 |
+
## TP / SL ({'LONG' if side == 1 else 'SHORT'} için {tp_title})
|
| 1567 |
+
| Parametre | Değer |
|
| 1568 |
+
|---|---:|
|
| 1569 |
+
| SL | {stop_d/cur_atr:.2f} ATR |
|
| 1570 |
+
| TP | {target_d/stop_d:.2f}R |
|
| 1571 |
+
| Stop-loss | {fmt_p(stop)} |
|
| 1572 |
+
| Take-profit | {fmt_p(target)} |
|
| 1573 |
+
| ATR(14) | {fmt_p(cur_atr)} |
|
| 1574 |
+
|
| 1575 |
+
{tpsl_note}TP/SL optimizasyon rejimi: {regime_used}. Yeterli rejim örneği yoksa tüm geliştirme dönemine geri dönülür.
|
| 1576 |
+
|
| 1577 |
+
## OOS backtest ({overlap_note})
|
| 1578 |
+
{oos_md}
|
| 1579 |
+
|
| 1580 |
+
**Rejim bazlı OOS:**
|
| 1581 |
+
|
| 1582 |
+
{reg_md}
|
| 1583 |
+
|
| 1584 |
+
## Risk-bütçeli pozisyon ({ticker})
|
| 1585 |
+
| Parametre | Değer |
|
| 1586 |
+
|---|---:|
|
| 1587 |
+
| Hesap büyüklüğü | ${equity:,.2f} |
|
| 1588 |
+
| Adaptif risk | {risk_pct:.3f}% |
|
| 1589 |
+
| Risk bütçesi | ${risk_cash:,.2f} |
|
| 1590 |
+
| Varlık miktarı (birim) | {qty:.8f} |
|
| 1591 |
+
| Nominal pozisyon | ${notional:,.2f} |
|
| 1592 |
+
| Tahmini teminat | ${margin:,.2f} |
|
| 1593 |
+
| Tahmini stop kaybı + maliyet | ${actual_risk:,.2f} |
|
| 1594 |
+
| Kaldıraç sınırı | {"Uygulandı" if capped else "Uygulanmadı"} |
|
| 1595 |
+
| Son 30 günlük kapanış DD | {drawdown:.2%} |
|
| 1596 |
+
|
| 1597 |
+
## Monte Carlo
|
| 1598 |
+
{mc_text}
|
| 1599 |
+
"""
|
| 1600 |
+
top_df = pd.DataFrame(top, columns=["score", "SL_ATR", "TP_R", "samples",
|
| 1601 |
+
"mean_net_return", "win_rate"])
|
| 1602 |
+
if ind_tpsl and not top_df.empty:
|
| 1603 |
+
top_df["SL_ATR"] = "indikatör"
|
| 1604 |
+
if not top_df.empty:
|
| 1605 |
+
top_df["score"] = top_df.score.map(lambda v: f"{v:.5f}")
|
| 1606 |
+
top_df["mean_net_return"] = top_df.mean_net_return.map(lambda v: f"{v:.3%}")
|
| 1607 |
+
top_df["win_rate"] = top_df.win_rate.map(lambda v: f"{v:.1%}")
|
| 1608 |
+
|
| 1609 |
+
csv_path = None
|
| 1610 |
+
if len(trades_df):
|
| 1611 |
+
safe = re.sub(r"[^A-Za-z0-9_-]", "_", ticker)
|
| 1612 |
+
csv_path = os.path.join(tempfile.gettempdir(), f"goshawk_{safe}_oos_trades.csv")
|
| 1613 |
+
trades_df.to_csv(csv_path, index=False)
|
| 1614 |
+
|
| 1615 |
+
price_fig = price_figure(df, ticker, trades_df, trade_ok, stop, target,
|
| 1616 |
+
ind=(st_line, st_dir, ut_stop),
|
| 1617 |
+
vp=vp if (vp_on or combo_on) else None, vp_prof=lp,
|
| 1618 |
+
ldc=LD, combo=combo_arr if combo_on else None,
|
| 1619 |
+
gann=GB, gann_fan=bool(gann_fan_on), gann_extend=bool(gann_ext_on), smc=SM)
|
| 1620 |
+
eq_fig = equity_figure(curve, dds) if len(R) else None
|
| 1621 |
+
pf_fig = pf_gauge(stats["pf"], stats["n"]) if stats else None
|
| 1622 |
+
mc_fig = mc_figure(mc) if mc else None
|
| 1623 |
+
|
| 1624 |
+
disclaimer = (
|
| 1625 |
+
"**Araştırma prototipi; otomatik emir göndermez.** KNN olasılık kalibrasyonu tek bir "
|
| 1626 |
+
"tarihsel kalibrasyon dilimine dayanır ve küçük örneklerde ham olasılığa geri döner. "
|
| 1627 |
+
"TP/SL optimizasyonu geliştirme döneminde ve yön bazında yapılır; son dönem OOS olarak "
|
| 1628 |
+
"ayrılır. Rejim etiketleri geliştirme verisinin volatilite kantillerine dayanır; canlı "
|
| 1629 |
+
"dağılım değişebilir. Adaptif risk yüzdesi OOS metriklerinden türetildiği için OOS "
|
| 1630 |
+
"özsermaye eğrisi iyimser olabilir. Günlük mumda SL ve TP aynı mumda görülürse SL "
|
| 1631 |
+
"varsayılır. Monte Carlo, OOS R-multiple serisini bootstrap eder; bağımsızlık varsayımı "
|
| 1632 |
+
"seri korelasyonunu ve rejim değişimini tam modellemez. Pozisyon hesabı spot miktar "
|
| 1633 |
+
"varsayar; CFD lot sözleşmesi, spread, funding, swap, komisyon, minimum lot, marjin ve "
|
| 1634 |
+
"broker fiyatı farklı olabilir. Gap ve kayma kaybı planlanan riskin üstüne çıkarabilir. "
|
| 1635 |
+
"Supertrend ve UT Bot, TradingView formüllerinin Python portudur; ATR başlangıcı ve veri kaynağı yüzünden TV değerlerinden küçük sapmalar olabilir. Birleşik sinyal; ST+UT, Lorentzian ve Volume Profile oylarının birleşimidir. İndikatör bazlı TP/SL, en yakın ST/UT/VP seviyesini invalidasyon sayar ve ATR aralığına sıkıştırır. Lorentzian Classification, jdehorty'nin TradingView göstergesinin Python portudur (ANN komşu seçimi, filtreler ve kernel regresyon); Pine kaynağıyla birebir aynı sonuç vermeyebilir ve yalnızca günlük mumda çalışır. Volume Profile, günlük OHLCV'den (her mumun hacmi high–low aralığına eşit dağıtılarak) yaklaşık hesaplanır; TradingView VPVR'nin alt-zaman dilimi verisiyle birebir aynı olmaz. Gann Box, Zeiierman göstergesinin açıklamasına göre yazılmış bir Python portudur (Pine kaynağı birebir alınmadı); kutu son N mumun en yüksek/en düşük noktasından kurulur, Gann Fan 1x1 köşegeni ve katlarıyla çizilir, Gann Curves/Angles eklenmedi. Onay filtresi, kapanışın kutu %50 çizgisine göre konumuna bakar ve geçmişe bakan (kayan) pencere kullanır. SMC, LuxAlgo göstergesinin açıklamasına göre yazılmış bir Python portudur (Pine kaynağı birebir değil): swing/internal yapı, BOS/CHoCH, order block (high/low mitigasyonu), EQH/EQL, FVG ve premium/discount hesaplanır; mum boyama yoktur; internal confluence ve OB filtresi, Pine formüllerinin hafızadan yazılmış karşılığıdır. OB ve önceki gün/hafta/ay high-low seviyeleri, «TP/SL'i indikatörlerden türet» açıkken en yakın seviye SL referansı ve TP sınırı olarak backtest'e girer. Sadece SMC modunda işlemler yalnızca seçilen yapıdaki (swing/internal) BOS/CHoCH kırılım mumunda, kırılım yönünde açılır; SL en yakın ST/UT/VP/SMC swing seviyesinden, TP R katından türetilir; diğer sinyal modları devre dışıdır. Pivotlar pencere kadar mum sonra doğrulanır, bu yüzden sinyal geç gelir. SMC kavramlarının istatistiksel geçerliliği kanıtlı değildir; OOS sonuçlarıyla doğrulayın. Canlı hesap öncesi paper trading ve broker bazlı doğrulama zorunludur."
|
| 1636 |
+
)
|
| 1637 |
+
return (report, prob_figure(p_down, p_flat, p_up), price_fig, eq_fig, pf_fig, mc_fig,
|
| 1638 |
+
top_df, csv_path, disclaimer)
|
| 1639 |
+
|
| 1640 |
+
except Exception as exc:
|
| 1641 |
+
return (f"# Hata\n{type(exc).__name__}: {exc}", None, None, None, None, None,
|
| 1642 |
+
pd.DataFrame(), None,
|
| 1643 |
+
"Veri bağlantısını ve parametreleri kontrol edin.")
|
| 1644 |
+
|
| 1645 |
+
|
| 1646 |
+
# ---------------------------------------------------------------- arayüz
|
| 1647 |
+
with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
|
| 1648 |
+
gr.Markdown(
|
| 1649 |
+
"# GOSHAWK — Multi-Asset Adaptive Risk Engine v5\n"
|
| 1650 |
+
"Kalibre KNN · adaptif K · yön bazlı TP/SL · bağımsız OOS backtest · "
|
| 1651 |
+
"R-multiple Monte Carlo · drawdown bazlı risk · kapı kontrolleri."
|
| 1652 |
+
)
|
| 1653 |
+
with gr.Row():
|
| 1654 |
+
with gr.Column():
|
| 1655 |
+
ticker = gr.Dropdown(ASSETS, value="BTC-USD", allow_custom_value=True,
|
| 1656 |
+
label="Varlık (listeden seç ya da Yahoo sembolü yaz)")
|
| 1657 |
+
period = gr.Dropdown(["2y", "5y", "10y", "max"], value="5y", label="Veri geçmişi")
|
| 1658 |
+
horizon = gr.Dropdown([3, 5, 10], value=5, label="Tahmin ufku (gün)")
|
| 1659 |
+
side_mode = gr.Radio(SIDE_MODES, value="Long + Short", label="İzin verilen yön")
|
| 1660 |
+
ind_mode = gr.Radio(IND_MODES, value="Kapalı", label="İndikatör modu (Supertrend + UT Bot)")
|
| 1661 |
+
vp_mode = gr.Radio(VP_MODES, value="Kapalı", label="Volume Profile modu")
|
| 1662 |
+
ldc_mode = gr.Radio(LDC_MODES, value="Kapalı", label="Lorentzian Classification modu")
|
| 1663 |
+
combo_mode = gr.Radio(COMBO_MODES, value=COMBO_MODES[2],
|
| 1664 |
+
label="Birleşik sinyal (ST+UT · Lorentzian · Volume Profile oylaması)")
|
| 1665 |
+
gann_mode = gr.Radio(GANN_MODES, value=GANN_MODES[0], label="Gann Box modu (Zeiierman)")
|
| 1666 |
+
smc_mode = gr.Radio(SMC_MODES, value=SMC_MODES[0], label="Smart Money Concepts modu (LuxAlgo)")
|
| 1667 |
+
ind_tpsl = gr.Checkbox(value=True, label="TP/SL'i indikatörlerden türet (ST / UT / VP seviyeleri)")
|
| 1668 |
+
train_window = gr.Slider(300, 1500, value=900, step=50, label="Maksimum eğitim penceresi")
|
| 1669 |
+
with gr.Column():
|
| 1670 |
+
equity = gr.Number(value=10000, label="Hesap büyüklüğü (USD)")
|
| 1671 |
+
max_leverage = gr.Slider(1, 10, value=3, step=1, label="Azami kaldıraç")
|
| 1672 |
+
min_risk = gr.Slider(0.05, 0.5, value=0.10, step=0.05, label="Minimum risk (%)")
|
| 1673 |
+
max_risk = gr.Slider(0.25, 1.5, value=0.75, step=0.05, label="Maksimum risk (%)")
|
| 1674 |
+
fee = gr.Number(value=0.05, label="Tek yön komisyon (%)")
|
| 1675 |
+
slip = gr.Number(value=0.03, label="Tek yön slippage (%)")
|
| 1676 |
+
with gr.Accordion("Gelişmiş: işlem kapısı eşikleri ve simülasyon", open=False):
|
| 1677 |
+
with gr.Row():
|
| 1678 |
+
p_min = gr.Slider(0.34, 0.70, value=0.43, step=0.01, label="Min. P(yön)")
|
| 1679 |
+
p_gap = gr.Slider(0.0, 0.40, value=0.12, step=0.01, label="Min. P(yön) − P(karşı)")
|
| 1680 |
+
flat_max = gr.Slider(0.30, 0.90, value=0.60, step=0.01, label="Maks. P(FLAT)")
|
| 1681 |
+
with gr.Row():
|
| 1682 |
+
agree_min = gr.Slider(0.30, 0.90, value=0.55, step=0.01, label="Min. komşu uyumu")
|
| 1683 |
+
bacc_min = gr.Slider(0.30, 0.60, value=0.36, step=0.01, label="Min. OOS balanced acc.")
|
| 1684 |
+
min_trades = gr.Slider(5, 50, value=15, step=1, label="Min. OOS işlem sayısı")
|
| 1685 |
+
with gr.Row():
|
| 1686 |
+
st_period = gr.Slider(5, 30, value=10, step=1, label="Supertrend ATR periyodu")
|
| 1687 |
+
st_mult = gr.Slider(1.0, 6.0, value=3.0, step=0.1, label="Supertrend çarpanı")
|
| 1688 |
+
ut_key = gr.Slider(0.5, 4.0, value=1.0, step=0.1, label="UT Bot key value")
|
| 1689 |
+
ut_period = gr.Slider(5, 30, value=10, step=1, label="UT Bot ATR periyodu")
|
| 1690 |
+
with gr.Row():
|
| 1691 |
+
combo_votes = gr.Slider(1, 3, value=2, step=1, label="Birleşik sinyal: min. aynı yön oyu (karşı oy yok)")
|
| 1692 |
+
tpsl_smin = gr.Slider(0.3, 2.0, value=0.8, step=0.1, label="İndikatör SL alt sınır (ATR)")
|
| 1693 |
+
tpsl_smax = gr.Slider(1.5, 6.0, value=3.0, step=0.1, label="İndikatör SL üst sınır (ATR)")
|
| 1694 |
+
tpsl_minrr = gr.Slider(0.5, 3.0, value=1.0, step=0.1, label="Min. R:R (kapı)")
|
| 1695 |
+
with gr.Row():
|
| 1696 |
+
ldc_k = gr.Slider(1, 30, value=8, step=1, label="Lorentzian komşu sayısı")
|
| 1697 |
+
ldc_back = gr.Slider(300, 3000, value=2000, step=100, label="Lorentzian maks. geçmiş mum")
|
| 1698 |
+
ldc_regime = gr.Slider(-2.0, 2.0, value=-0.1, step=0.1, label="Rejim eşiği")
|
| 1699 |
+
with gr.Row():
|
| 1700 |
+
ldc_kern_lb = gr.Slider(3, 50, value=8, step=1, label="Kernel lookback")
|
| 1701 |
+
ldc_kern_rw = gr.Slider(0.25, 25, value=8, step=0.25, label="Kernel göreli ağırlık")
|
| 1702 |
+
with gr.Row():
|
| 1703 |
+
ldc_use_vol = gr.Checkbox(value=True, label="Volatilite filtresi")
|
| 1704 |
+
ldc_use_regime = gr.Checkbox(value=True, label="Rejim filtresi")
|
| 1705 |
+
ldc_use_adx = gr.Checkbox(value=False, label="ADX filtresi (eşik 20)")
|
| 1706 |
+
ldc_kernel = gr.Checkbox(value=True, label="Kernel ile onayla")
|
| 1707 |
+
ldc_exact = gr.Checkbox(value=True, label="Gerçek en yakın komşu (kapalı: Pine yaklaşık-NN)")
|
| 1708 |
+
with gr.Row():
|
| 1709 |
+
vp_window = gr.Slider(20, 250, value=60, step=5, label="Volume Profile penceresi (mum)")
|
| 1710 |
+
vp_bins = gr.Slider(12, 60, value=24, step=2, label="Fiyat kutusu sayısı")
|
| 1711 |
+
vp_va = gr.Slider(0.50, 0.90, value=0.70, step=0.05, label="Value Area oranı")
|
| 1712 |
+
vp_ext = gr.Slider(0.5, 4.0, value=1.5, step=0.1, label="Maks. uzama (ATR) — kovalamayı engeller")
|
| 1713 |
+
with gr.Row():
|
| 1714 |
+
gann_lb = gr.Slider(30, 150, value=150, step=5, label="Gann Box penceresi (mum)")
|
| 1715 |
+
gann_fan_on = gr.Checkbox(value=True, label="Gann Fan göster")
|
| 1716 |
+
gann_ext_on = gr.Checkbox(value=True, label="Kutuyu geleceğe uzat (Extend)")
|
| 1717 |
+
gann_rev = gr.Checkbox(value=False, label="Reverse (yönü çevir)")
|
| 1718 |
+
with gr.Row():
|
| 1719 |
+
smc_src = gr.Radio(SMC_SRC, value=SMC_SRC[0], label="SMC yön kaynağı")
|
| 1720 |
+
smc_swing_len = gr.Slider(10, 100, value=50, step=5, label="SMC swing yapı uzunluğu")
|
| 1721 |
+
smc_int_len = gr.Slider(3, 15, value=5, step=1, label="SMC internal yapı uzunluğu")
|
| 1722 |
+
smc_zone = gr.Checkbox(value=False, label="Premium/Discount kapısı (long=discount, short=premium)")
|
| 1723 |
+
smc_draw = gr.Checkbox(value=True, label="SMC katmanlarını grafikte göster")
|
| 1724 |
+
with gr.Row():
|
| 1725 |
+
smc_conf = gr.Checkbox(value=False, label="Internal confluence filtresi (zayıf kırılımları ele)")
|
| 1726 |
+
smc_obf = gr.Radio(SMC_OBF, value=SMC_OBF[0], label="Order Block volatilite filtresi")
|
| 1727 |
+
smc_lvl = gr.Checkbox(value=True, label="SMC seviyeleri TP/SL'de kullan (Order Block + önceki gün/hafta/ay H-L)")
|
| 1728 |
+
with gr.Row():
|
| 1729 |
+
skip_overlap = gr.Checkbox(value=False, label="Açık pozisyon varken yeni sinyali atla (tek pozisyon)")
|
| 1730 |
+
whole_units = gr.Checkbox(value=False, label="Miktarı tam sayıya yuvarla (hisse)")
|
| 1731 |
+
run = gr.Button("Öğren, optimize et ve test et", variant="primary")
|
| 1732 |
+
|
| 1733 |
+
with gr.Tabs():
|
| 1734 |
+
with gr.Tab("Sinyal"):
|
| 1735 |
+
output = gr.Markdown()
|
| 1736 |
+
prob_plot = gr.Plot()
|
| 1737 |
+
with gr.Tab("Grafik"):
|
| 1738 |
+
chart = gr.Plot()
|
| 1739 |
+
with gr.Tab("OOS backtest"):
|
| 1740 |
+
pf_plot = gr.Plot()
|
| 1741 |
+
equity_plot = gr.Plot()
|
| 1742 |
+
with gr.Tab("Monte Carlo"):
|
| 1743 |
+
mc_plot = gr.Plot()
|
| 1744 |
+
with gr.Tab("TP/SL optimizasyon"):
|
| 1745 |
+
optimization = gr.Dataframe(label="En iyi TP/SL kombinasyonları", interactive=False)
|
| 1746 |
+
with gr.Tab("İşlem kaydı"):
|
| 1747 |
+
csv_file = gr.File(label="OOS işlemleri (CSV)", interactive=False)
|
| 1748 |
+
disclaimer = gr.Markdown()
|
| 1749 |
+
|
| 1750 |
+
run.click(
|
| 1751 |
+
run_engine,
|
| 1752 |
+
inputs=[ticker, period, horizon, side_mode, equity, min_risk, max_risk,
|
| 1753 |
+
max_leverage, fee, slip, train_window,
|
| 1754 |
+
p_min, p_gap, flat_max, agree_min, bacc_min, min_trades,
|
| 1755 |
+
skip_overlap, whole_units, ind_mode, st_period, st_mult, ut_key, ut_period,
|
| 1756 |
+
vp_mode, vp_window, vp_bins, vp_va, vp_ext,
|
| 1757 |
+
ldc_mode, ldc_k, ldc_back, ldc_regime, ldc_kern_lb, ldc_kern_rw,
|
| 1758 |
+
ldc_use_vol, ldc_use_regime, ldc_use_adx, ldc_kernel, ldc_exact,
|
| 1759 |
+
combo_mode, combo_votes, ind_tpsl, tpsl_smin, tpsl_smax, tpsl_minrr,
|
| 1760 |
+
gann_mode, gann_lb, gann_fan_on, gann_ext_on, gann_rev,
|
| 1761 |
+
smc_mode, smc_src, smc_swing_len, smc_int_len, smc_zone, smc_draw,
|
| 1762 |
+
smc_conf, smc_obf, smc_lvl],
|
| 1763 |
+
outputs=[output, prob_plot, chart, equity_plot, pf_plot, mc_plot, optimization,
|
| 1764 |
+
csv_file, disclaimer],
|
| 1765 |
+
)
|
| 1766 |
+
|
| 1767 |
+
if __name__ == "__main__":
|
| 1768 |
+
demo.launch(server_name="0.0.0.0", server_port=7860)
|