Fájl részletek
Ezen az oldalon egy konkrét fájl aktuális állapotát tudod megnézni.
Fájl útvonala
/opt/bots/saturnus/app/app.py
Aktuális státusz
MODIFIED
Módosítás ideje
1779876687.1075823
Korábbi baseline időpont
1775632282.5379288
Előnézet (első 120 sor)
import json
import os
import freqtrade_ui
import tempfile
import hashlib
import subprocess
import csv
from datetime import datetime, timezone
from flask import Flask, jsonify, render_template, Response, redirect, request
from rules_loader import (
load_rules_doc,
save_rules_doc,
load_lab_override_doc,
save_lab_override_doc,
list_lab_profiles,
)
from rules_merge import build_effective_rules
APP_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.abspath(os.path.join(APP_DIR, ".."))
STATE_PATH = os.path.join(ROOT_DIR, "state.json")
FREQTRADE_CONFIG_PATH = os.path.join(ROOT_DIR, "freqtrade", "user_data", "config.json")
SETTINGS_ENV_FILE = "/etc/systemd/system/saturnus-runner.service.d/30-canonical-env.conf"
POLL_INTERVAL_MS = 15000
MONITOR_DATA_DIR = "/opt/bots/saturnus_monitor/data"
MONITOR_PRICES_CSV = os.path.join(MONITOR_DATA_DIR, "prices.csv")
def _parse_iso_ts(value: str):
if not value:
return None
raw = str(value).strip()
try:
if raw.endswith("Z"):
return datetime.fromisoformat(raw.replace("Z", "+00:00"))
return datetime.fromisoformat(raw)
except Exception:
return None
def load_chart_history_from_prices_csv(days: int = 10, short_days: int = 1, long_days: int = 10):
rows = []
try:
with open(MONITOR_PRICES_CSV, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
ts = _parse_iso_ts(row.get("timestamp_utc"))
if ts is None:
continue
try:
last = float(row.get("last"))
except Exception:
continue
rows.append((ts, last))
except Exception as e:
return {
"ok": False,
"error": "prices_csv_unavailable",
"detail": str(e),
"labels": [],
"price": [],
"ma_short": [],
"ma_long": [],
}
if not rows:
return {
"ok": False,
"error": "prices_csv_empty",
"labels": [],
"price": [],
"ma_short": [],
"ma_long": [],
}
rows.sort(key=lambda x: x[0])
# perces gyertyásítás: adott perc utolsó ára maradjon meg
minute_map = {}
minute_order = []
for ts, last in rows:
minute_ts = ts.replace(second=0, microsecond=0)
key = minute_ts.isoformat()
if key not in minute_map:
minute_order.append(key)
minute_map[key] = (minute_ts, last)
minute_rows = [minute_map[k] for k in minute_order]
if not minute_rows:
return {
"ok": False,
"error": "minute_rows_empty",
"labels": [],
"price": [],
"ma_short": [],
"ma_long": [],
}
max_ts = minute_rows[-1][0]
cutoff = max_ts.timestamp() - (int(days) * 86400)
filtered = [(ts, last) for ts, last in minute_rows if ts.timestamp() >= cutoff]
if not filtered:
filtered = minute_rows[-min(len(minute_rows), int(days) * 1440):]
short_window = max(1, int(short_days) * 1440)
long_window = max(1, int(long_days) * 1440)
labels = []
price = []
ma_short = []
ma_long = []
short_buf = []
Csak változott diff sorok
--- baseline
+++ current
@@ -4,6 +4,7 @@
+import csv
@@ -27,6 +28,142 @@
+MONITOR_DATA_DIR = "/opt/bots/saturnus_monitor/data"
+MONITOR_PRICES_CSV = os.path.join(MONITOR_DATA_DIR, "prices.csv")
+
+
+def _parse_iso_ts(value: str):
+ if not value:
+ return None
+ raw = str(value).strip()
+ try:
+ if raw.endswith("Z"):
+ return datetime.fromisoformat(raw.replace("Z", "+00:00"))
+ return datetime.fromisoformat(raw)
+ except Exception:
+ return None
+
+
+def load_chart_history_from_prices_csv(days: int = 10, short_days: int = 1, long_days: int = 10):
+ rows = []
+ try:
+ with open(MONITOR_PRICES_CSV, "r", encoding="utf-8") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ ts = _parse_iso_ts(row.get("timestamp_utc"))
+ if ts is None:
+ continue
+ try:
+ last = float(row.get("last"))
+ except Exception:
+ continue
+ rows.append((ts, last))
+ except Exception as e:
+ return {
+ "ok": False,
+ "error": "prices_csv_unavailable",
+ "detail": str(e),
+ "labels": [],
+ "price": [],
+ "ma_short": [],
+ "ma_long": [],
+ }
+
+ if not rows:
+ return {
+ "ok": False,
+ "error": "prices_csv_empty",
+ "labels": [],
+ "price": [],
+ "ma_short": [],
+ "ma_long": [],
+ }
+
+ rows.sort(key=lambda x: x[0])
+
+ # perces gyertyásítás: adott perc utolsó ára maradjon meg
+ minute_map = {}
+ minute_order = []
+ for ts, last in rows:
+ minute_ts = ts.replace(second=0, microsecond=0)
+ key = minute_ts.isoformat()
+ if key not in minute_map:
+ minute_order.append(key)
+ minute_map[key] = (minute_ts, last)
+
+ minute_rows = [minute_map[k] for k in minute_order]
+ if not minute_rows:
+ return {
+ "ok": False,
+ "error": "minute_rows_empty",
+ "labels": [],
+ "price": [],
+ "ma_short": [],
+ "ma_long": [],
+ }
+
+ max_ts = minute_rows[-1][0]
+ cutoff = max_ts.timestamp() - (int(days) * 86400)
+
+ filtered = [(ts, last) for ts, last in minute_rows if ts.timestamp() >= cutoff]
+ if not filtered:
+ filtered = minute_rows[-min(len(minute_rows), int(days) * 1440):]
+
+ short_window = max(1, int(short_days) * 1440)
+ long_window = max(1, int(long_days) * 1440)
+
+ labels = []
+ price = []
+ ma_short = []
+ ma_long = []
+
+ short_buf = []
+ long_buf = []
+
+ for ts, last in filtered:
+ labels.append(ts.isoformat().replace("+00:00", "Z"))
+ price.append(last)
+
+ short_buf.append(last)
+ if len(short_buf) > short_window:
+ short_buf.pop(0)
+
+ long_buf.append(last)
+ if len(long_buf) > long_window:
+ long_buf.pop(0)
+
+ ma_short.append(sum(short_buf) / len(short_buf) if short_buf else None)
+ ma_long.append(sum(long_buf) / len(long_buf) if long_buf else None)
+
+ return {
+ "ok": True,
+ "source": MONITOR_PRICES_CSV,
+ "days": int(days),
+ "short_days": int(short_days),
+ "long_days": int(long_days),
+ "points": len(labels),
+ "labels": labels,
+ "price": price,
+ "ma_short": ma_short,
+ "ma_long": ma_long,
+ }
+
+import os
+
+def get_env(name, default=None):
+ return os.environ.get(name, default)
+
+def env_float(name, default=0.0):
+ try:
+ return float(get_env(name, default))
+ except:
+ return float(default)
+
+def env_int(name, default=0):
+ try:
+ return int(float(get_env(name, default)))
+ except:
+ return int(default)
@@ -762,59 +899,124 @@
+
+ "std_sell_enabled": True,
+ "recovery_sell_retrace_pct": 0.3,
+ "recovery_profit_target_pct": 0.1,
+ "panic_sell_enabled": True,
+ "sell_reversal_min_pct": 0.0,
+
+ "std_buy_enabled": True,
+ "recovery_buy_rebound_pct": 0.3,
+ "panic_buy_enabled": True,
+ "panic_buy_confirm_ticks": 2,
- "recovery_buy_rebound_pct": 0.3,
- "recovery_sell_retrace_pct": 0.3,
+
+ "ma_filter_enabled": True,
+ "ma_period": 20,
+ "ma_sideways_band_pct": 0.05,
+ def apply_env(k, v):
+ try:
+ if k == "TICK_SECONDS":
+ cfg["tick_seconds"] = float(v)
+ elif k == "PAIR":
+ cfg["pair"] = v
+ elif k == "TIMEFRAME":
+ cfg["timeframe"] = v
+ elif k == "LIMIT":
+ cfg["limit"] = int(float(v))
+ elif k == "EXECUTION_ENABLED":
+ cfg["execution_enabled"] = env_to_bool(v)
+ elif k == "EXECUTION_LOG_ONLY":
+ cfg["execution_log_only"] = env_to_bool(v)
+ elif k == "BUY_LOCK_TTL_SEC":
+ cfg["buy_lock_ttl_sec"] = int(float(v))
+ elif k == "STD_SELL_ENABLED":
+ cfg["std_sell_enabled"] = env_to_bool(v)
Teljes diff
--- baseline
+++ current
@@ -4,6 +4,7 @@
import tempfile
import hashlib
import subprocess
+import csv
from datetime import datetime, timezone
from flask import Flask, jsonify, render_template, Response, redirect, request
@@ -27,6 +28,142 @@
POLL_INTERVAL_MS = 15000
+MONITOR_DATA_DIR = "/opt/bots/saturnus_monitor/data"
+MONITOR_PRICES_CSV = os.path.join(MONITOR_DATA_DIR, "prices.csv")
+
+
+def _parse_iso_ts(value: str):
+ if not value:
+ return None
+ raw = str(value).strip()
+ try:
+ if raw.endswith("Z"):
+ return datetime.fromisoformat(raw.replace("Z", "+00:00"))
+ return datetime.fromisoformat(raw)
+ except Exception:
+ return None
+
+
+def load_chart_history_from_prices_csv(days: int = 10, short_days: int = 1, long_days: int = 10):
+ rows = []
+ try:
+ with open(MONITOR_PRICES_CSV, "r", encoding="utf-8") as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ ts = _parse_iso_ts(row.get("timestamp_utc"))
+ if ts is None:
+ continue
+ try:
+ last = float(row.get("last"))
+ except Exception:
+ continue
+ rows.append((ts, last))
+ except Exception as e:
+ return {
+ "ok": False,
+ "error": "prices_csv_unavailable",
+ "detail": str(e),
+ "labels": [],
+ "price": [],
+ "ma_short": [],
+ "ma_long": [],
+ }
+
+ if not rows:
+ return {
+ "ok": False,
+ "error": "prices_csv_empty",
+ "labels": [],
+ "price": [],
+ "ma_short": [],
+ "ma_long": [],
+ }
+
+ rows.sort(key=lambda x: x[0])
+
+ # perces gyertyásítás: adott perc utolsó ára maradjon meg
+ minute_map = {}
+ minute_order = []
+ for ts, last in rows:
+ minute_ts = ts.replace(second=0, microsecond=0)
+ key = minute_ts.isoformat()
+ if key not in minute_map:
+ minute_order.append(key)
+ minute_map[key] = (minute_ts, last)
+
+ minute_rows = [minute_map[k] for k in minute_order]
+ if not minute_rows:
+ return {
+ "ok": False,
+ "error": "minute_rows_empty",
+ "labels": [],
+ "price": [],
+ "ma_short": [],
+ "ma_long": [],
+ }
+
+ max_ts = minute_rows[-1][0]
+ cutoff = max_ts.timestamp() - (int(days) * 86400)
+
+ filtered = [(ts, last) for ts, last in minute_rows if ts.timestamp() >= cutoff]
+ if not filtered:
+ filtered = minute_rows[-min(len(minute_rows), int(days) * 1440):]
+
+ short_window = max(1, int(short_days) * 1440)
+ long_window = max(1, int(long_days) * 1440)
+
+ labels = []
+ price = []
+ ma_short = []
+ ma_long = []
+
+ short_buf = []
+ long_buf = []
+
+ for ts, last in filtered:
+ labels.append(ts.isoformat().replace("+00:00", "Z"))
+ price.append(last)
+
+ short_buf.append(last)
+ if len(short_buf) > short_window:
+ short_buf.pop(0)
+
+ long_buf.append(last)
+ if len(long_buf) > long_window:
+ long_buf.pop(0)
+
+ ma_short.append(sum(short_buf) / len(short_buf) if short_buf else None)
+ ma_long.append(sum(long_buf) / len(long_buf) if long_buf else None)
+
+ return {
+ "ok": True,
+ "source": MONITOR_PRICES_CSV,
+ "days": int(days),
+ "short_days": int(short_days),
+ "long_days": int(long_days),
+ "points": len(labels),
+ "labels": labels,
+ "price": price,
+ "ma_short": ma_short,
+ "ma_long": ma_long,
+ }
+
+import os
+
+def get_env(name, default=None):
+ return os.environ.get(name, default)
+
+def env_float(name, default=0.0):
+ try:
+ return float(get_env(name, default))
+ except:
+ return float(default)
+
+def env_int(name, default=0):
+ try:
+ return int(float(get_env(name, default)))
+ except:
+ return int(default)
app = Flask(__name__, template_folder=os.path.join(APP_DIR, "templates"))
@@ -762,59 +899,124 @@
"max_trades_per_day": 20,
"daily_loss_cap_pct": 5.0,
"ft_url": "http://127.0.0.1:8089",
+
+ "std_sell_enabled": True,
"std_sell_pct": 1.0,
+ "recovery_sell_retrace_pct": 0.3,
+ "recovery_profit_target_pct": 0.1,
+ "panic_sell_enabled": True,
"panic_sell_pct": 1.0,
+ "sell_reversal_min_pct": 0.0,
"catastrophe_sell_pct": 10.0,
+
+ "std_buy_enabled": True,
"std_buy_pct": 1.0,
+ "recovery_buy_rebound_pct": 0.3,
+ "panic_buy_enabled": True,
"panic_buy_pct": 1.0,
+ "panic_buy_confirm_ticks": 2,
"catastrophe_buy_pct": 10.0,
- "recovery_buy_rebound_pct": 0.3,
- "recovery_sell_retrace_pct": 0.3,
+
+ "ma_filter_enabled": True,
+ "ma_period": 20,
+ "ma_sideways_band_pct": 0.05,
}
+ def apply_env(k, v):
+ try:
+ if k == "TICK_SECONDS":
+ cfg["tick_seconds"] = float(v)
+ elif k == "PAIR":
+ cfg["pair"] = v
+ elif k == "TIMEFRAME":
+ cfg["timeframe"] = v
+ elif k == "LIMIT":
+ cfg["limit"] = int(float(v))
+ elif k == "EXECUTION_ENABLED":
+ cfg["execution_enabled"] = env_to_bool(v)
+ elif k == "EXECUTION_LOG_ONLY":
+ cfg["execution_log_only"] = env_to_bool(v)
+ elif k == "BUY_LOCK_TTL_SEC":
+ cfg["buy_lock_ttl_sec"] = int(float(v))
+ elif k == "STD_SELL_ENABLED":
+ cfg["std_sell_enabled"] = env_to_bool(v)
... [DIFF LEVÁGVA] további sorok: 287