* Reordered navigator live ea's at the top
* added the live ea's ftp html reports to be available via trade analysis page.
This commit is contained in:
@@ -142,8 +142,8 @@ with st.sidebar:
|
|||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
page = option_menu(
|
page = option_menu(
|
||||||
menu_title = None,
|
menu_title = None,
|
||||||
options = ["Trade Analysis", "Trade Compare", "Portfolio Builder", "Portfolio Master", "Live MT5 EAs", "EA Comparator", "Batch Backtest", "Settings"],
|
options = ["Live MT5 EAs", "Trade Analysis", "Trade Compare", "Portfolio Builder", "Portfolio Master", "EA Comparator", "Batch Backtest", "Settings"],
|
||||||
icons = ["bar-chart-line", "arrow-left-right", "briefcase", "trophy", "wifi", "sliders", "cpu", "gear"],
|
icons = ["wifi", "bar-chart-line", "arrow-left-right", "briefcase", "trophy", "sliders", "cpu", "gear"],
|
||||||
default_index = 0,
|
default_index = 0,
|
||||||
styles = {
|
styles = {
|
||||||
"container" : {"background-color": "transparent", "padding": "0"},
|
"container" : {"background-color": "transparent", "padding": "0"},
|
||||||
|
|||||||
+59
-5
@@ -7,6 +7,10 @@ MT5 Trade Analysis page — migrated from main dashboard.
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
import sys, os
|
import sys, os
|
||||||
|
import json
|
||||||
|
import pickle
|
||||||
|
from pathlib import Path
|
||||||
|
import pandas as pd
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
from mt5_parser import detect_and_parse, calc_stats
|
from mt5_parser import detect_and_parse, calc_stats
|
||||||
|
|
||||||
@@ -49,6 +53,29 @@ def _normalise_ic(df):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
FTP_CONFIG_FILE = Path("ftp_config.json")
|
||||||
|
FTP_ACCOUNTS_FILE = Path("ftp_accounts.json")
|
||||||
|
FTP_CACHE_DIR = Path("cache")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_ftp_account_configs() -> list:
|
||||||
|
if FTP_ACCOUNTS_FILE.exists():
|
||||||
|
try:
|
||||||
|
return json.loads(FTP_ACCOUNTS_FILE.read_text())
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _load_ftp_cache(account_folder: str):
|
||||||
|
p = FTP_CACHE_DIR / f"ftp_{account_folder}.pkl"
|
||||||
|
if not p.exists():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return pickle.loads(p.read_bytes())
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _generate_html_report(df_plot, stats, fmt, view_sel,
|
def _generate_html_report(df_plot, stats, fmt, view_sel,
|
||||||
stats_compare=None, df_compare=None,
|
stats_compare=None, df_compare=None,
|
||||||
@@ -418,6 +445,38 @@ def render():
|
|||||||
st.session_state['ta_accounts'] = []
|
st.session_state['ta_accounts'] = []
|
||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
|
ftp_accounts = _load_ftp_account_configs()
|
||||||
|
ftp_choices = [
|
||||||
|
(f"{ac.get('label', ac['account'])} ({ac['account']})", ac['account'])
|
||||||
|
for ac in ftp_accounts
|
||||||
|
if _load_ftp_cache(ac['account'])
|
||||||
|
]
|
||||||
|
with st.expander("Import FTP account history", expanded=False):
|
||||||
|
if ftp_choices:
|
||||||
|
ftp_labels = [display for display, _ in ftp_choices]
|
||||||
|
sel_name = st.radio("Select account", ["None"] + ftp_labels,
|
||||||
|
index=0, key='ta_ftp_choice')
|
||||||
|
if sel_name != "None":
|
||||||
|
selected_account = next(ac for label, ac in ftp_choices if label == sel_name)
|
||||||
|
if st.button("Load FTP history", key='ta_load_ftp_history'):
|
||||||
|
cached = _load_ftp_cache(selected_account)
|
||||||
|
if cached and cached.get('df') is not None:
|
||||||
|
df = cached['df'].copy()
|
||||||
|
if 'close_time' in df.columns:
|
||||||
|
df['close_time'] = pd.to_datetime(df['close_time'], errors='coerce')
|
||||||
|
st.session_state['ta_df'] = df
|
||||||
|
st.session_state['ta_df_original'] = df.copy()
|
||||||
|
st.session_state['ta_format'] = "FTP Cached"
|
||||||
|
st.session_state['ta_accounts'] = []
|
||||||
|
st.success(f"✓ Loaded {len(df)} trades from FTP account {sel_name}")
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.error("Could not load FTP cache. Refresh the account cache in Live MT5 EAs first.")
|
||||||
|
elif ftp_accounts:
|
||||||
|
st.info("No cached FTP history found. Open Live MT5 EAs and refresh accounts to populate cache.")
|
||||||
|
else:
|
||||||
|
st.info("No FTP accounts configured. Add accounts in Live MT5 EAs and click Refresh All.")
|
||||||
|
|
||||||
# ── File upload ───────────────────────────────────────────────────────────
|
# ── File upload ───────────────────────────────────────────────────────────
|
||||||
if source == "MT5 / Quant Analyzer":
|
if source == "MT5 / Quant Analyzer":
|
||||||
uploaded = st.file_uploader(
|
uploaded = st.file_uploader(
|
||||||
@@ -666,7 +725,6 @@ def render():
|
|||||||
delta=_delta('trades_per_day','x'))
|
delta=_delta('trades_per_day','x'))
|
||||||
|
|
||||||
def render_equity_curve(df_plot, label="Equity Curve", df_compare=None, compare_label="Edited"):
|
def render_equity_curve(df_plot, label="Equity Curve", df_compare=None, compare_label="Edited"):
|
||||||
import pandas as pd
|
|
||||||
df_s = df_plot.sort_values('close_time').copy()
|
df_s = df_plot.sort_values('close_time').copy()
|
||||||
|
|
||||||
COLORS = ['#7c6af7','#34C27A','#F5A623','#E05555','#4C8EF5',
|
COLORS = ['#7c6af7','#34C27A','#F5A623','#E05555','#4C8EF5',
|
||||||
@@ -871,7 +929,6 @@ def render():
|
|||||||
st.plotly_chart(fig, use_container_width=True)
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
def render_monthly_table(df_plot, label="Monthly Performance", key_prefix="mt"):
|
def render_monthly_table(df_plot, label="Monthly Performance", key_prefix="mt"):
|
||||||
import pandas as pd
|
|
||||||
if 'close_time' not in df_plot.columns or df_plot.empty:
|
if 'close_time' not in df_plot.columns or df_plot.empty:
|
||||||
return
|
return
|
||||||
tmp = df_plot[['close_time','net_profit']].dropna().copy()
|
tmp = df_plot[['close_time','net_profit']].dropna().copy()
|
||||||
@@ -1055,7 +1112,6 @@ def render():
|
|||||||
row['Expectancy'] = f"{stat['expectancy']:.2f}{_arr('expectancy')}"
|
row['Expectancy'] = f"{stat['expectancy']:.2f}{_arr('expectancy')}"
|
||||||
row['Max DD'] = f"{stat['max_drawdown']:.2f}{_arr('max_drawdown')}"
|
row['Max DD'] = f"{stat['max_drawdown']:.2f}{_arr('max_drawdown')}"
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
import pandas as pd
|
|
||||||
sdf_sum = pd.DataFrame(rows).sort_values('Net Profit', ascending=False)
|
sdf_sum = pd.DataFrame(rows).sort_values('Net Profit', ascending=False)
|
||||||
st.dataframe(sdf_sum, use_container_width=True, hide_index=True)
|
st.dataframe(sdf_sum, use_container_width=True, hide_index=True)
|
||||||
st.divider()
|
st.divider()
|
||||||
@@ -1098,7 +1154,6 @@ def render():
|
|||||||
'Expectancy' : stat['expectancy'],
|
'Expectancy' : stat['expectancy'],
|
||||||
'Max DD' : stat['max_drawdown'],
|
'Max DD' : stat['max_drawdown'],
|
||||||
})
|
})
|
||||||
import pandas as pd
|
|
||||||
sdf_sum = pd.DataFrame(rows).sort_values('Net Profit', ascending=False)
|
sdf_sum = pd.DataFrame(rows).sort_values('Net Profit', ascending=False)
|
||||||
st.dataframe(
|
st.dataframe(
|
||||||
sdf_sum.style.map(colour_profit, subset=['Net Profit', 'Expectancy', 'Max DD']),
|
sdf_sum.style.map(colour_profit, subset=['Net Profit', 'Expectancy', 'Max DD']),
|
||||||
@@ -1184,7 +1239,6 @@ def render():
|
|||||||
)
|
)
|
||||||
|
|
||||||
if do_update:
|
if do_update:
|
||||||
import pandas as pd
|
|
||||||
upd = edited.drop(columns=['#'])
|
upd = edited.drop(columns=['#'])
|
||||||
for col in ['open_time','close_time']:
|
for col in ['open_time','close_time']:
|
||||||
if col in upd.columns:
|
if col in upd.columns:
|
||||||
|
|||||||
Reference in New Issue
Block a user