forexsmartbot 3.3.1__tar.gz → 3.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/PKG-INFO +1 -1
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/__init__.py +1 -1
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/__init__.py +4 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/companion_api.py +30 -2
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/remote_monitor.py +110 -3
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot.egg-info/PKG-INFO +1 -1
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/pyproject.toml +1 -1
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_companion_q2.py +49 -1
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/LICENSE +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/README.md +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/brokers/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/brokers/ib_tws_broker.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/brokers/mt4_broker.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/brokers/paper_broker.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/brokers/rest_broker.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/alpha_vantage_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/config.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/csv_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/dummy_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/mt4_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/multi_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/oanda_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/stooq_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/twelve_data_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/yfinance_provider.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/chart_patterns.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/correlation_matrix.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/economic_calendar.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/enhanced_chart.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/live_metrics.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/market_depth.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/performance_attribution.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/portfolio_analytics.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/risk_analytics.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/strategy_comparison.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/trade_journal.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/brokers/mt4_bridge.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/builder/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/builder/code_generator.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/builder/strategy_builder.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/builder/strategy_template.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/api_client.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/api_server.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/auth_utils.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/cloud_sync.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/error_handler.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/mobile_api.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/cloud/security.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/backtester.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/config.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/interfaces.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/paper_broker.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/portfolio.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/risk.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/risk_engine.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/core/trade_manager.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/gui/main_window.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/gui/theme.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/marketplace/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/marketplace/marketplace.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/marketplace/strategy_rating.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/monitoring/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/monitoring/health_check.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/monitoring/performance_tracker.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/monitoring/strategy_monitor.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/adaptive_parameters.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/genetic_optimizer.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/hyperparameter_optimizer.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/monte_carlo.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/multi_objective_optimizer.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/parameter_sensitivity.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/walk_forward.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/backtest.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/calculation_engine.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/controller.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/data_streaming.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/enhanced_backtest.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/fast_trading_controller.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/gpu_backtest.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/language_manager.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/notification_service.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/persistence.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/startup_manager.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/adaptive_trend_flow.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/breakout_atr.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/ensemble_ml_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/fear_index.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/lstm_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/mean_reversion.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/ml_adaptive_supertrend.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/momentum_breakout.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/multi_timeframe_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/news_trading.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/rl_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/rsi_reversion.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/scalping_ma.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/sma_crossover.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/svm_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/transformer_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/testing/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/testing/ci_cd_pipeline.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/testing/paper_account_tester.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/testing/strategy_sandbox.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/analytics_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/analytics_widget.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/backtest_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/charts.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/cloud_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/enhanced_charts.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/enhanced_main_window.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/export_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/main_window.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/marketplace_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/monitoring_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/monitoring_widget.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/optimization_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/settings_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/simple_chart.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/strategy_builder_dialog.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/theme.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/ui/tradingview_chart.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/utils/__init__.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/utils/backtest_utils.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/utils/gpu_numpy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/utils/gpu_utils.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/utils/native_indicators.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot.egg-info/SOURCES.txt +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot.egg-info/dependency_links.txt +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot.egg-info/entry_points.txt +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot.egg-info/requires.txt +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot.egg-info/top_level.txt +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/setup.cfg +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_backtest_dialog_ui.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_cloud_hardening.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_config.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_live_metrics.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_multi_timeframe_strategy.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_native_indicators.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_risk.py +0 -0
- {forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/tests/test_strategies.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# ForexSmartBot Package
|
|
2
|
-
__version__ = "3.3.
|
|
2
|
+
__version__ = "3.3.2"
|
|
@@ -12,6 +12,8 @@ from .companion_api import (
|
|
|
12
12
|
CompanionActionQueue,
|
|
13
13
|
CompanionPairingStore,
|
|
14
14
|
WebhookNotifier,
|
|
15
|
+
normalize_cert_pin,
|
|
16
|
+
resolve_companion_cert_pin,
|
|
15
17
|
)
|
|
16
18
|
from .security import SecurityManager, RateLimiter
|
|
17
19
|
from .api_client import APIClient, WebSocketClient
|
|
@@ -29,6 +31,8 @@ __all__ = [
|
|
|
29
31
|
'CompanionActionQueue',
|
|
30
32
|
'CompanionPairingStore',
|
|
31
33
|
'WebhookNotifier',
|
|
34
|
+
'normalize_cert_pin',
|
|
35
|
+
'resolve_companion_cert_pin',
|
|
32
36
|
'SecurityManager',
|
|
33
37
|
'RateLimiter',
|
|
34
38
|
'APIClient',
|
|
@@ -40,6 +40,32 @@ PAIRING_TTL_SECONDS = 300
|
|
|
40
40
|
ALLOWED_ACTIONS = frozenset({"start_trading", "stop_trading", "pause_trading"})
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def normalize_cert_pin(pin: Optional[str]) -> str:
|
|
44
|
+
"""Normalize SPKI pin to ``sha256/...`` form (empty string if unset)."""
|
|
45
|
+
value = (pin or "").strip()
|
|
46
|
+
if not value:
|
|
47
|
+
return ""
|
|
48
|
+
if value.startswith("sha256/"):
|
|
49
|
+
return value
|
|
50
|
+
return f"sha256/{value}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def resolve_companion_cert_pin(explicit: Optional[str] = None) -> str:
|
|
54
|
+
"""
|
|
55
|
+
Production cert-pin default for pairing QR payloads.
|
|
56
|
+
|
|
57
|
+
Order: explicit arg → ``FSB_COMPANION_CERT_PIN`` → ``COMPANION_CERT_PIN``.
|
|
58
|
+
"""
|
|
59
|
+
import os
|
|
60
|
+
|
|
61
|
+
if explicit and str(explicit).strip():
|
|
62
|
+
return normalize_cert_pin(str(explicit))
|
|
63
|
+
return normalize_cert_pin(
|
|
64
|
+
os.getenv("FSB_COMPANION_CERT_PIN") or os.getenv("COMPANION_CERT_PIN") or ""
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
43
69
|
class CompanionActionQueue:
|
|
44
70
|
"""Pending high-level companion actions awaiting host approve/reject."""
|
|
45
71
|
|
|
@@ -282,7 +308,7 @@ class CompanionAPI:
|
|
|
282
308
|
self.require_api_key = require_api_key
|
|
283
309
|
self.allow_control = allow_control
|
|
284
310
|
self.public_base_url = (public_base_url or "").rstrip("/")
|
|
285
|
-
self.pin_sha256 = (pin_sha256
|
|
311
|
+
self.pin_sha256 = resolve_companion_cert_pin(pin_sha256)
|
|
286
312
|
self.url_prefix = url_prefix.rstrip("/") or "/api/v1/companion"
|
|
287
313
|
|
|
288
314
|
self._portfolio_provider = portfolio_provider or (lambda: {})
|
|
@@ -480,7 +506,9 @@ class CompanionAPI:
|
|
|
480
506
|
or self.public_base_url
|
|
481
507
|
or request.host_url.rstrip("/")
|
|
482
508
|
).rstrip("/")
|
|
483
|
-
pin =
|
|
509
|
+
pin = resolve_companion_cert_pin(
|
|
510
|
+
str(data.get("pin_sha256") or self.pin_sha256 or "").strip() or None
|
|
511
|
+
)
|
|
484
512
|
session = self.pairing_store.create(
|
|
485
513
|
base_url=base, api_key=self.api_key, pin_sha256=pin or None
|
|
486
514
|
)
|
|
@@ -222,7 +222,13 @@ class RemoteMonitorServer:
|
|
|
222
222
|
|
|
223
223
|
def _mount_companion(self) -> None:
|
|
224
224
|
"""Mount companion API on the same Flask app."""
|
|
225
|
-
|
|
225
|
+
from .companion_api import resolve_companion_cert_pin
|
|
226
|
+
|
|
227
|
+
public = (
|
|
228
|
+
os.getenv("FSB_PUBLIC_BASE_URL")
|
|
229
|
+
or os.getenv("COMPANION_PUBLIC_BASE_URL")
|
|
230
|
+
or f"http://{self.host}:{self.port}"
|
|
231
|
+
).rstrip("/")
|
|
226
232
|
self.companion = CompanionAPI(
|
|
227
233
|
app=self.app,
|
|
228
234
|
api_key=self.api_key,
|
|
@@ -232,7 +238,8 @@ class RemoteMonitorServer:
|
|
|
232
238
|
strategies_provider=self._strategies_payload,
|
|
233
239
|
history_provider=lambda name, limit: self._history_payload(name, limit),
|
|
234
240
|
allow_control=False,
|
|
235
|
-
public_base_url=
|
|
241
|
+
public_base_url=public,
|
|
242
|
+
pin_sha256=resolve_companion_cert_pin(),
|
|
236
243
|
)
|
|
237
244
|
|
|
238
245
|
def set_strategy_monitor(self, monitor: Any) -> None:
|
|
@@ -387,7 +394,7 @@ class RemoteMonitorServer:
|
|
|
387
394
|
<body>
|
|
388
395
|
<div class="container">
|
|
389
396
|
<h1>ForexSmartBot Remote Monitor</h1>
|
|
390
|
-
<p style="color:#aaa;">Strategy health + history charts + companion pairing / approve-reject at <code>/api/v1/companion/*</code
|
|
397
|
+
<p style="color:#aaa;">Strategy health + history charts + companion pairing / approve-reject at <code>/api/v1/companion/*</code> · multi-host health poll</p>
|
|
391
398
|
|
|
392
399
|
<div class="status-card">
|
|
393
400
|
<h2>API Key</h2>
|
|
@@ -396,6 +403,27 @@ class RemoteMonitorServer:
|
|
|
396
403
|
<span id="auth-hint" style="color:#aaa;margin-left:8px;"></span>
|
|
397
404
|
</div>
|
|
398
405
|
|
|
406
|
+
<div class="status-card">
|
|
407
|
+
<h2>Multi-Host Monitors</h2>
|
|
408
|
+
<p style="color:#aaa;">Poll additional remote-monitor hosts (browser-side). Keys stay in this browser session only.</p>
|
|
409
|
+
<input id="remote-host-url" type="text" placeholder="https://host:8080" style="width:360px;" />
|
|
410
|
+
<input id="remote-host-key" type="password" placeholder="API key (optional)" style="width:200px;" />
|
|
411
|
+
<button class="btn" onclick="addRemoteHost()">Add host</button>
|
|
412
|
+
<button class="btn" onclick="refreshRemoteHosts()">Refresh</button>
|
|
413
|
+
<table id="remote-hosts-table" style="margin-top:12px;">
|
|
414
|
+
<thead>
|
|
415
|
+
<tr>
|
|
416
|
+
<th>Host</th>
|
|
417
|
+
<th>Health</th>
|
|
418
|
+
<th>Version</th>
|
|
419
|
+
<th>Strategies</th>
|
|
420
|
+
<th></th>
|
|
421
|
+
</tr>
|
|
422
|
+
</thead>
|
|
423
|
+
<tbody id="remote-hosts-body"></tbody>
|
|
424
|
+
</table>
|
|
425
|
+
</div>
|
|
426
|
+
|
|
399
427
|
<div class="status-card">
|
|
400
428
|
<h2>Status</h2>
|
|
401
429
|
<div id="status-indicator" class="status-indicator status-disconnected"></div>
|
|
@@ -503,6 +531,83 @@ class RemoteMonitorServer:
|
|
|
503
531
|
updateStatus();
|
|
504
532
|
}
|
|
505
533
|
|
|
534
|
+
function loadRemoteHosts() {
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(localStorage.getItem('fsb_remote_hosts') || '[]');
|
|
537
|
+
} catch (e) {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function saveRemoteHosts(hosts) {
|
|
543
|
+
localStorage.setItem('fsb_remote_hosts', JSON.stringify(hosts || []));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function addRemoteHost() {
|
|
547
|
+
const url = document.getElementById('remote-host-url').value.trim().replace(/\\/$/, '');
|
|
548
|
+
const key = document.getElementById('remote-host-key').value.trim();
|
|
549
|
+
if (!url) return alert('Enter a host base URL');
|
|
550
|
+
const hosts = loadRemoteHosts().filter(h => h.url !== url);
|
|
551
|
+
hosts.push({url: url, key: key});
|
|
552
|
+
saveRemoteHosts(hosts);
|
|
553
|
+
document.getElementById('remote-host-url').value = '';
|
|
554
|
+
document.getElementById('remote-host-key').value = '';
|
|
555
|
+
refreshRemoteHosts();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function removeRemoteHost(url) {
|
|
559
|
+
saveRemoteHosts(loadRemoteHosts().filter(h => h.url !== url));
|
|
560
|
+
refreshRemoteHosts();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function refreshRemoteHosts() {
|
|
564
|
+
const hosts = loadRemoteHosts();
|
|
565
|
+
const tbody = document.getElementById('remote-hosts-body');
|
|
566
|
+
if (!hosts.length) {
|
|
567
|
+
tbody.innerHTML = '<tr><td colspan="5" style="color:#888;">No remote hosts — add a monitor URL</td></tr>';
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
tbody.innerHTML = hosts.map(h =>
|
|
571
|
+
'<tr data-url="' + h.url + '">' +
|
|
572
|
+
'<td>' + h.url + '</td>' +
|
|
573
|
+
'<td class="host-health">…</td>' +
|
|
574
|
+
'<td class="host-version">…</td>' +
|
|
575
|
+
'<td class="host-strats">…</td>' +
|
|
576
|
+
'<td><button class="btn btn-stop" onclick="removeRemoteHost(\\'' + h.url.replace(/'/g, '') + '\\')">Remove</button></td>' +
|
|
577
|
+
'</tr>'
|
|
578
|
+
).join('');
|
|
579
|
+
hosts.forEach(h => {
|
|
580
|
+
const headers = {'Content-Type': 'application/json'};
|
|
581
|
+
if (h.key) {
|
|
582
|
+
headers['Authorization'] = 'Bearer ' + h.key;
|
|
583
|
+
headers['X-API-Key'] = h.key;
|
|
584
|
+
}
|
|
585
|
+
const row = tbody.querySelector('tr[data-url="' + h.url + '"]');
|
|
586
|
+
if (!row) return;
|
|
587
|
+
fetch(h.url + '/api/v1/health').then(r => r.json()).then(health => {
|
|
588
|
+
row.querySelector('.host-health').textContent = health.status || 'ok';
|
|
589
|
+
row.querySelector('.host-health').className = 'host-health ' +
|
|
590
|
+
(health.status === 'healthy' ? 'health-healthy' : 'health-warning');
|
|
591
|
+
row.querySelector('.host-version').textContent = health.version || '—';
|
|
592
|
+
}).catch(() => {
|
|
593
|
+
row.querySelector('.host-health').textContent = 'unreachable';
|
|
594
|
+
row.querySelector('.host-health').className = 'host-health health-critical';
|
|
595
|
+
row.querySelector('.host-version').textContent = '—';
|
|
596
|
+
row.querySelector('.host-strats').textContent = '—';
|
|
597
|
+
});
|
|
598
|
+
fetch(h.url + '/api/v1/strategies', {headers: headers}).then(async r => {
|
|
599
|
+
if (!r.ok) throw new Error('auth');
|
|
600
|
+
return r.json();
|
|
601
|
+
}).then(data => {
|
|
602
|
+
const total = (data.summary && data.summary.total) ||
|
|
603
|
+
Object.keys(data.strategies || {}).length;
|
|
604
|
+
row.querySelector('.host-strats').textContent = String(total);
|
|
605
|
+
}).catch(() => {
|
|
606
|
+
row.querySelector('.host-strats').textContent = 'n/a';
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
506
611
|
function statusClass(status) {
|
|
507
612
|
const s = (status || 'unknown').toLowerCase();
|
|
508
613
|
if (s === 'healthy') return 'health-healthy';
|
|
@@ -726,7 +831,9 @@ class RemoteMonitorServer:
|
|
|
726
831
|
const existing = sessionStorage.getItem('fsb_api_key');
|
|
727
832
|
if (existing) document.getElementById('api-key').value = existing;
|
|
728
833
|
setInterval(updateStatus, 3000);
|
|
834
|
+
setInterval(refreshRemoteHosts, 15000);
|
|
729
835
|
updateStatus();
|
|
836
|
+
refreshRemoteHosts();
|
|
730
837
|
</script>
|
|
731
838
|
</body>
|
|
732
839
|
</html>
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "forexsmartbot"
|
|
7
|
-
version = "3.3.
|
|
7
|
+
version = "3.3.2"
|
|
8
8
|
description = "Professional forex trading bot with advanced risk management and strategy plugins"
|
|
9
9
|
authors = [{name = "VoxHash", email = "contact@voxhash.dev"}]
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -2,11 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from forexsmartbot.cloud.companion_api import
|
|
5
|
+
from forexsmartbot.cloud.companion_api import (
|
|
6
|
+
CompanionAPI,
|
|
7
|
+
WebhookNotifier,
|
|
8
|
+
normalize_cert_pin,
|
|
9
|
+
resolve_companion_cert_pin,
|
|
10
|
+
)
|
|
6
11
|
from forexsmartbot.cloud.remote_monitor import RemoteMonitorServer
|
|
7
12
|
from forexsmartbot.monitoring.strategy_monitor import StrategyMonitor
|
|
8
13
|
|
|
9
14
|
|
|
15
|
+
class TestCertPinHelpers:
|
|
16
|
+
def test_normalize_cert_pin(self):
|
|
17
|
+
assert normalize_cert_pin("") == ""
|
|
18
|
+
assert normalize_cert_pin("sha256/ABC=") == "sha256/ABC="
|
|
19
|
+
assert normalize_cert_pin("ABC=") == "sha256/ABC="
|
|
20
|
+
|
|
21
|
+
def test_resolve_from_env(self, monkeypatch):
|
|
22
|
+
monkeypatch.delenv("FSB_COMPANION_CERT_PIN", raising=False)
|
|
23
|
+
monkeypatch.delenv("COMPANION_CERT_PIN", raising=False)
|
|
24
|
+
assert resolve_companion_cert_pin(None) == ""
|
|
25
|
+
monkeypatch.setenv("FSB_COMPANION_CERT_PIN", "DEADBEEF")
|
|
26
|
+
assert resolve_companion_cert_pin(None) == "sha256/DEADBEEF"
|
|
27
|
+
assert resolve_companion_cert_pin("sha256/EXPLICIT=") == "sha256/EXPLICIT="
|
|
28
|
+
|
|
29
|
+
|
|
10
30
|
class TestWebhookNotifier:
|
|
11
31
|
def test_rejects_plain_http_non_loopback(self):
|
|
12
32
|
n = WebhookNotifier()
|
|
@@ -278,6 +298,34 @@ class TestStrategyHealthDashboard:
|
|
|
278
298
|
assert "History Charts" in html
|
|
279
299
|
assert "pairing/session" in html
|
|
280
300
|
assert "Pending Companion Actions" in html
|
|
301
|
+
assert "Multi-Host Monitors" in html
|
|
302
|
+
assert "fsb_remote_hosts" in html
|
|
303
|
+
|
|
304
|
+
def test_companion_pin_from_env_on_monitor(self, monkeypatch):
|
|
305
|
+
monkeypatch.setenv(
|
|
306
|
+
"FSB_COMPANION_CERT_PIN",
|
|
307
|
+
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
|
308
|
+
)
|
|
309
|
+
monkeypatch.setenv("FSB_PUBLIC_BASE_URL", "https://monitor.example")
|
|
310
|
+
monitor = RemoteMonitorServer(
|
|
311
|
+
host="127.0.0.1",
|
|
312
|
+
port=18085,
|
|
313
|
+
api_key="monitor-test-key-16",
|
|
314
|
+
require_api_key=True,
|
|
315
|
+
)
|
|
316
|
+
assert monitor.companion is not None
|
|
317
|
+
assert monitor.companion.pin_sha256.startswith("sha256/")
|
|
318
|
+
assert monitor.companion.public_base_url == "https://monitor.example"
|
|
319
|
+
client = monitor.app.test_client()
|
|
320
|
+
created = client.post(
|
|
321
|
+
"/api/v1/companion/pairing/session",
|
|
322
|
+
headers={"X-API-Key": "monitor-test-key-16"},
|
|
323
|
+
json={},
|
|
324
|
+
)
|
|
325
|
+
assert created.status_code == 201
|
|
326
|
+
payload = created.get_json()["qr_payload"]
|
|
327
|
+
assert payload["base_url"] == "https://monitor.example"
|
|
328
|
+
assert payload["pin_sha256"].startswith("sha256/")
|
|
281
329
|
|
|
282
330
|
def test_strategy_history_endpoint(self):
|
|
283
331
|
sm = StrategyMonitor()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/alpha_vantage_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/twelve_data_provider.py
RENAMED
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/adapters/data/yfinance_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/analytics/performance_attribution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/adaptive_parameters.py
RENAMED
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/hyperparameter_optimizer.py
RENAMED
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/multi_objective_optimizer.py
RENAMED
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/optimization/parameter_sensitivity.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/services/fast_trading_controller.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/ensemble_ml_strategy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/ml_adaptive_supertrend.py
RENAMED
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/multi_timeframe_strategy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{forexsmartbot-3.3.1 → forexsmartbot-3.3.2}/forexsmartbot/strategies/transformer_strategy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|