Merge pull request #6 from Aitous/feature/discovery-enhancements
Feature/discovery enhancements
This commit is contained in:
commit
feccdb77fc
|
|
@ -27,11 +27,13 @@ logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logger.info("""
|
logger.info(
|
||||||
|
"""
|
||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ TradingAgents - Historical Memory Builder ║
|
║ TradingAgents - Historical Memory Builder ║
|
||||||
╚══════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
tickers = [
|
tickers = [
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,8 @@ def build_strategy_memories(strategy_name: str, config: dict):
|
||||||
|
|
||||||
strategy = STRATEGIES[strategy_name]
|
strategy = STRATEGIES[strategy_name]
|
||||||
|
|
||||||
logger.info(f"""
|
logger.info(
|
||||||
|
f"""
|
||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ Building Memories: {strategy_name.upper().replace('_', ' ')}
|
║ Building Memories: {strategy_name.upper().replace('_', ' ')}
|
||||||
╚══════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
|
@ -98,7 +99,8 @@ Strategy: {strategy['description']}
|
||||||
Lookforward: {strategy['lookforward_days']} days
|
Lookforward: {strategy['lookforward_days']} days
|
||||||
Sampling: Every {strategy['interval_days']} days
|
Sampling: Every {strategy['interval_days']} days
|
||||||
Tickers: {', '.join(strategy['tickers'])}
|
Tickers: {', '.join(strategy['tickers'])}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# Date range - last 2 years
|
# Date range - last 2 years
|
||||||
end_date = datetime.now()
|
end_date = datetime.now()
|
||||||
|
|
@ -157,7 +159,8 @@ Tickers: {', '.join(strategy['tickers'])}
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logger.info("""
|
logger.info(
|
||||||
|
"""
|
||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ TradingAgents - Strategy-Specific Memory Builder ║
|
║ TradingAgents - Strategy-Specific Memory Builder ║
|
||||||
╚══════════════════════════════════════════════════════════════╝
|
╚══════════════════════════════════════════════════════════════╝
|
||||||
|
|
@ -168,7 +171,8 @@ This script builds optimized memories for different trading styles:
|
||||||
2. Swing Trading - 7-day returns, weekly samples
|
2. Swing Trading - 7-day returns, weekly samples
|
||||||
3. Position Trading - 30-day returns, monthly samples
|
3. Position Trading - 30-day returns, monthly samples
|
||||||
4. Long-term - 90-day returns, quarterly samples
|
4. Long-term - 90-day returns, quarterly samples
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("Available strategies:")
|
logger.info("Available strategies:")
|
||||||
for i, (name, config) in enumerate(STRATEGIES.items(), 1):
|
for i, (name, config) in enumerate(STRATEGIES.items(), 1):
|
||||||
|
|
@ -216,11 +220,13 @@ This script builds optimized memories for different trading styles:
|
||||||
|
|
||||||
logger.info("\n" + "=" * 70)
|
logger.info("\n" + "=" * 70)
|
||||||
logger.info("\n💡 TIP: To use a specific strategy's memories, update your config:")
|
logger.info("\n💡 TIP: To use a specific strategy's memories, update your config:")
|
||||||
logger.info("""
|
logger.info(
|
||||||
|
"""
|
||||||
config = DEFAULT_CONFIG.copy()
|
config = DEFAULT_CONFIG.copy()
|
||||||
config["memory_dir"] = "data/memories/swing_trading" # or your strategy
|
config["memory_dir"] = "data/memories/swing_trading" # or your strategy
|
||||||
config["load_historical_memories"] = True
|
config["load_historical_memories"] = True
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -129,10 +129,12 @@ def main():
|
||||||
6. Save updated positions
|
6. Save updated positions
|
||||||
7. Print progress messages
|
7. Print progress messages
|
||||||
"""
|
"""
|
||||||
logger.info("""
|
logger.info(
|
||||||
|
"""
|
||||||
╔══════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════╗
|
||||||
║ TradingAgents - Position Updater ║
|
║ TradingAgents - Position Updater ║
|
||||||
╚══════════════════════════════════════════════════════════════╝""".strip())
|
╚══════════════════════════════════════════════════════════════╝""".strip()
|
||||||
|
)
|
||||||
|
|
||||||
# Initialize position tracker
|
# Initialize position tracker
|
||||||
tracker = PositionTracker(data_dir="data")
|
tracker = PositionTracker(data_dir="data")
|
||||||
|
|
|
||||||
|
|
@ -79,92 +79,103 @@ def _format_move_pct(window: pd.DataFrame) -> str:
|
||||||
return f"{move:+.2f}%"
|
return f"{move:+.2f}%"
|
||||||
|
|
||||||
|
|
||||||
def _build_mini_chart(window: pd.DataFrame, timeframe: str) -> go.Figure:
|
def _build_dynamic_chart(history: pd.DataFrame, timeframe: str) -> tuple[go.Figure, str, str]:
|
||||||
template = get_plotly_template()
|
window = _slice_history_window(history, timeframe)
|
||||||
|
if window.empty:
|
||||||
|
return go.Figure(), "N/A", COLORS["text_muted"]
|
||||||
|
|
||||||
first_close = float(window["close"].iloc[0])
|
first_close = float(window["close"].iloc[0])
|
||||||
last_close = float(window["close"].iloc[-1])
|
last_close = float(window["close"].iloc[-1])
|
||||||
line_color = COLORS["green"] if last_close >= first_close else COLORS["red"]
|
line_color = COLORS["green"] if last_close >= first_close else COLORS["red"]
|
||||||
|
move_text = _format_move_pct(window)
|
||||||
|
|
||||||
|
template = dict(get_plotly_template())
|
||||||
|
|
||||||
fig = go.Figure()
|
fig = go.Figure()
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=history["date"],
|
||||||
|
y=history["close"],
|
||||||
|
mode="lines",
|
||||||
|
line=dict(color="rgba(148,163,184,0.22)", width=1.1),
|
||||||
|
hovertemplate="%{x|%b %d, %Y}<br>$%{y:.2f}<extra></extra>",
|
||||||
|
name="History",
|
||||||
|
)
|
||||||
|
)
|
||||||
fig.add_trace(
|
fig.add_trace(
|
||||||
go.Scatter(
|
go.Scatter(
|
||||||
x=window["date"],
|
x=window["date"],
|
||||||
y=window["close"],
|
y=window["close"],
|
||||||
mode="lines",
|
mode="lines",
|
||||||
line=dict(color=line_color, width=1.8),
|
line=dict(color=line_color, width=2.8),
|
||||||
fill="tozeroy",
|
fill="tozeroy",
|
||||||
fillcolor="rgba(34,197,94,0.08)" if line_color == COLORS["green"] else "rgba(239,68,68,0.08)",
|
fillcolor=(
|
||||||
|
"rgba(34,197,94,0.18)" if line_color == COLORS["green"] else "rgba(239,68,68,0.18)"
|
||||||
|
),
|
||||||
hovertemplate=f"{timeframe}<br>%{{x|%b %d, %Y}}<br>$%{{y:.2f}}<extra></extra>",
|
hovertemplate=f"{timeframe}<br>%{{x|%b %d, %Y}}<br>$%{{y:.2f}}<extra></extra>",
|
||||||
name=timeframe,
|
name=f"{timeframe} Focus",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
fig.update_layout(
|
|
||||||
**template,
|
# Override template keys before expansion to avoid duplicate keyword args.
|
||||||
height=140,
|
template["height"] = 210
|
||||||
showlegend=False,
|
template["showlegend"] = False
|
||||||
margin=dict(l=0, r=0, t=0, b=0),
|
template["margin"] = dict(l=0, r=0, t=10, b=0)
|
||||||
|
fig.update_layout(**template)
|
||||||
|
|
||||||
|
# Tighten Y-axis to selected timeframe range for better signal visibility.
|
||||||
|
y_min = float(window["close"].min())
|
||||||
|
y_max = float(window["close"].max())
|
||||||
|
if y_min == y_max:
|
||||||
|
pad = max(0.5, y_min * 0.01)
|
||||||
|
else:
|
||||||
|
pad = max((y_max - y_min) * 0.08, y_max * 0.01)
|
||||||
|
|
||||||
|
fig.update_xaxes(
|
||||||
|
showticklabels=False,
|
||||||
|
showgrid=False,
|
||||||
|
range=[window["date"].min(), history["date"].max()],
|
||||||
|
rangeslider=dict(visible=False),
|
||||||
)
|
)
|
||||||
fig.update_xaxes(showticklabels=False, showgrid=False)
|
fig.update_yaxes(
|
||||||
fig.update_yaxes(showgrid=True, gridcolor="rgba(42,53,72,0.28)", tickprefix="$", nticks=4)
|
showgrid=True,
|
||||||
return fig
|
gridcolor="rgba(42,53,72,0.28)",
|
||||||
|
tickprefix="$",
|
||||||
|
nticks=5,
|
||||||
|
range=[y_min - pad, y_max + pad],
|
||||||
|
)
|
||||||
|
return fig, move_text, line_color
|
||||||
|
|
||||||
|
|
||||||
def _render_multi_timeframe_charts(ticker: str, selected_timeframes: list[str]) -> None:
|
def _render_single_dynamic_chart(ticker: str, timeframe: str) -> None:
|
||||||
if not selected_timeframes:
|
|
||||||
return
|
|
||||||
|
|
||||||
base_history = _load_price_history(ticker, "1y")
|
base_history = _load_price_history(ticker, "1y")
|
||||||
if base_history.empty:
|
if base_history.empty:
|
||||||
|
st.caption("No price history available for this ticker.")
|
||||||
return
|
return
|
||||||
|
|
||||||
ordered_timeframes = [tf for tf in TIMEFRAME_LOOKBACK_DAYS.keys() if tf in selected_timeframes]
|
|
||||||
if not ordered_timeframes:
|
|
||||||
return
|
|
||||||
|
|
||||||
st.markdown(
|
|
||||||
f"""
|
|
||||||
<div style="margin-top:0.4rem;margin-bottom:0.45rem;padding:0.4rem 0.55rem;
|
|
||||||
border:1px solid {COLORS['border']};border-radius:8px;
|
|
||||||
background:linear-gradient(120deg, rgba(6,182,212,0.10), rgba(59,130,246,0.05));">
|
|
||||||
<span style="font-family:'JetBrains Mono',monospace;font-size:0.66rem;
|
|
||||||
text-transform:uppercase;letter-spacing:0.07em;color:{COLORS['text_secondary']};">
|
|
||||||
Multi-Timeframe Price Map
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
""",
|
|
||||||
unsafe_allow_html=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in range(0, len(ordered_timeframes), 2):
|
|
||||||
cols = st.columns(2)
|
|
||||||
for j, col in enumerate(cols):
|
|
||||||
idx = i + j
|
|
||||||
if idx >= len(ordered_timeframes):
|
|
||||||
continue
|
|
||||||
timeframe = ordered_timeframes[idx]
|
|
||||||
window = _slice_history_window(base_history, timeframe)
|
window = _slice_history_window(base_history, timeframe)
|
||||||
if window.empty:
|
if window.empty:
|
||||||
continue
|
st.caption(f"Not enough data to render {timeframe} window.")
|
||||||
first_close = float(window["close"].iloc[0])
|
return
|
||||||
last_close = float(window["close"].iloc[-1])
|
|
||||||
move_text = _format_move_pct(window)
|
|
||||||
move_color = COLORS["green"] if last_close >= first_close else COLORS["red"]
|
|
||||||
fig = _build_mini_chart(window, timeframe)
|
|
||||||
|
|
||||||
with col:
|
fig, move_text, move_color = _build_dynamic_chart(base_history, timeframe)
|
||||||
st.markdown(
|
st.markdown(
|
||||||
f"""
|
f"""
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;
|
<div style="margin-top:0.4rem;margin-bottom:0.45rem;padding:0.45rem 0.6rem;
|
||||||
margin:0.1rem 0 0.2rem 0;">
|
border:1px solid {COLORS['border']};border-radius:8px;
|
||||||
<span style="font-family:'JetBrains Mono',monospace;font-size:0.70rem;
|
background:linear-gradient(120deg, rgba(6,182,212,0.10), rgba(59,130,246,0.05));
|
||||||
color:{COLORS['text_secondary']};letter-spacing:0.05em;">{timeframe}</span>
|
display:flex;justify-content:space-between;align-items:center;">
|
||||||
<span style="font-family:'JetBrains Mono',monospace;font-size:0.70rem;
|
<span style="font-family:'JetBrains Mono',monospace;font-size:0.68rem;
|
||||||
font-weight:600;color:{move_color};">{move_text}</span>
|
text-transform:uppercase;letter-spacing:0.07em;color:{COLORS['text_secondary']};">
|
||||||
|
Dynamic Price View • {timeframe}
|
||||||
|
</span>
|
||||||
|
<span style="font-family:'JetBrains Mono',monospace;font-size:0.72rem;
|
||||||
|
font-weight:700;color:{move_color};">{move_text}</span>
|
||||||
</div>
|
</div>
|
||||||
""",
|
""",
|
||||||
unsafe_allow_html=True,
|
unsafe_allow_html=True,
|
||||||
)
|
)
|
||||||
st.plotly_chart(fig, width="stretch")
|
st.plotly_chart(fig, width="stretch", config={"displayModeBar": False})
|
||||||
|
|
||||||
|
|
||||||
def render():
|
def render():
|
||||||
|
|
@ -199,14 +210,6 @@ def render():
|
||||||
min_score = st.slider("Min Score", 0, 100, 0)
|
min_score = st.slider("Min Score", 0, 100, 0)
|
||||||
with ctrl_cols[3]:
|
with ctrl_cols[3]:
|
||||||
show_charts = st.checkbox("Price Charts", value=False)
|
show_charts = st.checkbox("Price Charts", value=False)
|
||||||
if show_charts:
|
|
||||||
selected_timeframes = st.multiselect(
|
|
||||||
"Timeframes",
|
|
||||||
list(TIMEFRAME_LOOKBACK_DAYS.keys()),
|
|
||||||
default=["1M", "3M", "6M", "1Y"],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
selected_timeframes = []
|
|
||||||
|
|
||||||
# Apply filters
|
# Apply filters
|
||||||
filtered = [
|
filtered = [
|
||||||
|
|
@ -259,7 +262,26 @@ def render():
|
||||||
)
|
)
|
||||||
|
|
||||||
if show_charts:
|
if show_charts:
|
||||||
_render_multi_timeframe_charts(ticker, selected_timeframes)
|
st.markdown(
|
||||||
|
f"""
|
||||||
|
<div style="margin-top:0.35rem;margin-bottom:0.25rem;
|
||||||
|
font-family:'JetBrains Mono',monospace;font-size:0.66rem;
|
||||||
|
text-transform:uppercase;letter-spacing:0.06em;
|
||||||
|
color:{COLORS['text_muted']};">
|
||||||
|
Chart Timeframe
|
||||||
|
</div>
|
||||||
|
""",
|
||||||
|
unsafe_allow_html=True,
|
||||||
|
)
|
||||||
|
chart_timeframe = st.radio(
|
||||||
|
f"Timeframe for {ticker}",
|
||||||
|
list(TIMEFRAME_LOOKBACK_DAYS.keys()),
|
||||||
|
index=2,
|
||||||
|
horizontal=True,
|
||||||
|
label_visibility="collapsed",
|
||||||
|
key=f"chart_tf_{ticker}_{idx}",
|
||||||
|
)
|
||||||
|
_render_single_dynamic_chart(ticker, chart_timeframe)
|
||||||
|
|
||||||
# Action buttons
|
# Action buttons
|
||||||
btn_cols = st.columns(2)
|
btn_cols = st.columns(2)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue