Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
A
ai-stock-analysis
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-solutions
ai-stock-analysis
Commits
1a941ef5
Commit
1a941ef5
authored
Aug 10, 2026
by
xeron56
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Limit CLI languages and remove legacy promotions
parent
0d1afdc8
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
94 additions
and
116 deletions
+94
-116
.env.example
.env.example
+1
-1
README.md
README.md
+1
-1
announcements.py
cli/announcements.py
+0
-52
config.py
cli/config.py
+0
-6
main.py
cli/main.py
+1
-8
utils.py
cli/utils.py
+4
-19
agent_utils.py
dohasecuritiesstockai/agents/utils/agent_utils.py
+8
-4
reddit.py
dohasecuritiesstockai/dataflows/reddit.py
+1
-1
stocktwits.py
dohasecuritiesstockai/dataflows/stocktwits.py
+1
-1
default_config.py
dohasecuritiesstockai/default_config.py
+6
-1
languages.py
dohasecuritiesstockai/languages.py
+25
-0
test_cli_env_skip.py
tests/test_cli_env_skip.py
+3
-9
test_env_overrides.py
tests/test_env_overrides.py
+15
-2
test_i18n_coverage.py
tests/test_i18n_coverage.py
+11
-3
test_openrouter_model_select.py
tests/test_openrouter_model_select.py
+17
-8
No files found.
.env.example
View file @
1a941ef5
...
...
@@ -66,7 +66,7 @@ DSE_PASSWORD=
#TRADINGAGENTS_DEEP_THINK_LLM=google/gemini-3.1-pro-preview
#TRADINGAGENTS_QUICK_THINK_LLM=google/gemini-3.5-flash
#TRADINGAGENTS_LLM_BACKEND_URL=
#TRADINGAGENTS_OUTPUT_LANGUAGE=English
#TRADINGAGENTS_OUTPUT_LANGUAGE=English
# Supported: English or Bangla
#TRADINGAGENTS_MAX_DEBATE_ROUNDS=1
#TRADINGAGENTS_MAX_RISK_ROUNDS=1
#TRADINGAGENTS_CHECKPOINT_ENABLED=false
...
...
README.md
View file @
1a941ef5
...
...
@@ -217,7 +217,7 @@ The CLI asks for:
1.
DSE ticker.
2.
Analysis date.
3.
Output language.
3.
Output language
: English or Bangla (
`বাংলা`
)
.
4.
Analyst selection.
5.
Research/debate depth.
6.
LLM provider and models.
...
...
cli/announcements.py
deleted
100644 → 0
View file @
0d1afdc8
import
getpass
import
requests
from
rich.console
import
Console
from
rich.panel
import
Panel
from
cli.config
import
CLI_CONFIG
def
fetch_announcements
(
url
:
str
=
None
,
timeout
:
float
=
None
)
->
dict
:
"""Fetch announcements from endpoint. Returns dict with announcements and settings."""
endpoint
=
url
or
CLI_CONFIG
[
"announcements_url"
]
timeout
=
timeout
or
CLI_CONFIG
[
"announcements_timeout"
]
fallback
=
CLI_CONFIG
[
"announcements_fallback"
]
try
:
response
=
requests
.
get
(
endpoint
,
timeout
=
timeout
)
response
.
raise_for_status
()
data
=
response
.
json
()
return
{
"announcements"
:
data
.
get
(
"announcements"
,
[
fallback
]),
"require_attention"
:
data
.
get
(
"require_attention"
,
False
),
}
except
Exception
:
return
{
"announcements"
:
[
fallback
],
"require_attention"
:
False
,
}
def
display_announcements
(
console
:
Console
,
data
:
dict
)
->
None
:
"""Display announcements panel. Prompts for Enter if require_attention is True."""
announcements
=
data
.
get
(
"announcements"
,
[])
require_attention
=
data
.
get
(
"require_attention"
,
False
)
if
not
announcements
:
return
content
=
"
\n
"
.
join
(
announcements
)
panel
=
Panel
(
content
,
border_style
=
"cyan"
,
padding
=
(
1
,
2
),
title
=
"Announcements"
,
)
console
.
print
(
panel
)
if
require_attention
:
getpass
.
getpass
(
"Press Enter to continue..."
)
else
:
console
.
print
()
cli/config.py
deleted
100644 → 0
View file @
0d1afdc8
CLI_CONFIG
=
{
# Announcements
"announcements_url"
:
"https://api.tauric.ai/v1/announcements"
,
"announcements_timeout"
:
1.0
,
"announcements_fallback"
:
"[cyan]For more information, please visit[/cyan] [link=https://github.com/TauricResearch]https://github.com/TauricResearch[/link]"
,
}
cli/main.py
View file @
1a941ef5
...
...
@@ -19,7 +19,6 @@ from rich.spinner import Spinner
from
rich.table
import
Table
from
rich.text
import
Text
from
cli.announcements
import
display_announcements
,
fetch_announcements
from
cli.stats_handler
import
StatsCallbackHandler
from
cli.utils
import
(
ask_anthropic_effort
,
...
...
@@ -523,8 +522,7 @@ def get_user_selections():
welcome_content
+=
f
"[bold green]{PRODUCT_DISPLAY_NAME}[/bold green]
\n
"
welcome_content
+=
f
"[dim]{PRODUCT_TAGLINE}[/dim]
\n\n
"
welcome_content
+=
"[bold]Workflow Steps:[/bold]
\n
"
welcome_content
+=
"I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management
\n\n
"
welcome_content
+=
"[dim]Powered by the TradingAgents framework[/dim]"
welcome_content
+=
"I. Analyst Team → II. Research Team → III. Trader → IV. Risk Management → V. Portfolio Management"
# Create and center the welcome box
welcome_box
=
Panel
(
...
...
@@ -536,11 +534,6 @@ def get_user_selections():
)
console
.
print
(
Align
.
center
(
welcome_box
))
console
.
print
()
console
.
print
()
# Add vertical space before announcements
# Fetch and display announcements (silent on failure)
announcements
=
fetch_announcements
()
display_announcements
(
console
,
announcements
)
# Create a boxed questionnaire for each step
def
create_question_box
(
title
,
prompt
,
default
=
None
):
...
...
cli/utils.py
View file @
1a941ef5
...
...
@@ -7,6 +7,7 @@ from rich.console import Console
from
cli.models
import
AnalystType
,
AssetType
from
dohasecuritiesstockai.default_config
import
DEFAULT_CONFIG
from
dohasecuritiesstockai.languages
import
OUTPUT_LANGUAGE_CHOICES
from
dohasecuritiesstockai.llm_clients.api_key_env
import
get_api_key_env
from
dohasecuritiesstockai.llm_clients.model_catalog
import
get_model_options
...
...
@@ -681,22 +682,12 @@ def ensure_api_key(provider: str) -> str | None:
def
ask_output_language
()
->
str
:
"""Ask for
report
output language."""
"""Ask for
the English or Bangla report-
output language."""
choice
=
questionary
.
select
(
"Select Output Language:"
,
choices
=
[
questionary
.
Choice
(
"English (default)"
,
"English"
),
questionary
.
Choice
(
"Chinese (中文)"
,
"Chinese"
),
questionary
.
Choice
(
"Japanese (日本語)"
,
"Japanese"
),
questionary
.
Choice
(
"Korean (한국어)"
,
"Korean"
),
questionary
.
Choice
(
"Hindi (हिन्दी)"
,
"Hindi"
),
questionary
.
Choice
(
"Spanish (Español)"
,
"Spanish"
),
questionary
.
Choice
(
"Portuguese (Português)"
,
"Portuguese"
),
questionary
.
Choice
(
"French (Français)"
,
"French"
),
questionary
.
Choice
(
"German (Deutsch)"
,
"German"
),
questionary
.
Choice
(
"Arabic (العربية)"
,
"Arabic"
),
questionary
.
Choice
(
"Russian (Русский)"
,
"Russian"
),
questionary
.
Choice
(
"Custom language"
,
"custom"
),
questionary
.
Choice
(
label
,
value
)
for
label
,
value
in
OUTPUT_LANGUAGE_CHOICES
],
style
=
questionary
.
Style
([
(
"selected"
,
"fg:yellow noinherit"
),
...
...
@@ -709,10 +700,4 @@ def ask_output_language() -> str:
# rather than exiting the run (unlike the required model/provider prompts).
if
choice
is
None
:
return
"English"
if
choice
==
"custom"
:
return
(
questionary
.
text
(
"Enter language name (e.g. Turkish, Vietnamese, Thai, Indonesian):"
,
validate
=
lambda
x
:
len
(
x
.
strip
())
>
0
or
"Please enter a language name."
,
)
.
ask
()
or
""
)
.
strip
()
or
"English"
return
choice
dohasecuritiesstockai/agents/utils/agent_utils.py
View file @
1a941ef5
...
...
@@ -61,10 +61,15 @@ def get_language_instruction() -> str:
report rather than a mix of languages.
"""
from
dohasecuritiesstockai.dataflows.config
import
get_config
lang
=
get_config
()
.
get
(
"output_language"
,
"English"
)
if
lang
.
strip
()
.
lower
()
==
"english"
:
from
dohasecuritiesstockai.languages
import
normalize_output_language
language
=
normalize_output_language
(
get_config
()
.
get
(
"output_language"
,
"English"
))
if
language
==
"English"
:
return
""
return
f
" Write your entire response in {lang}."
return
(
" Write your entire response in natural Bangla using বাংলা script."
" Keep ticker symbols, standard financial abbreviations, and numeric values unchanged."
)
def
_clean_identity_value
(
value
:
Any
)
->
str
|
None
:
...
...
@@ -219,4 +224,3 @@ def create_msg_delete():
return
{
"messages"
:
removal_operations
+
[
placeholder
]}
return
delete_messages
dohasecuritiesstockai/dataflows/reddit.py
View file @
1a941ef5
...
...
@@ -40,7 +40,7 @@ _RSS = "https://www.reddit.com/r/{sub}/search.rss?{qs}"
# blocks generic/anonymous tokens like bare "Mozilla/5.0" or "curl/…" but
# serves this one on both endpoints; the RSS feed accepts it even when the
# JSON search endpoint 403s, so no browser-spoofing is needed.
_UA
=
"dohasecuritiesstockai/0.2 (+https://github.com/
TauricResearch/TradingAgents
)"
_UA
=
"dohasecuritiesstockai/0.2 (+https://github.com/
xeron56/DohasecuritiesStockAi
)"
_ATOM_NS
=
{
"atom"
:
"http://www.w3.org/2005/Atom"
}
# Default subreddits ordered roughly by signal density for ticker-specific
...
...
dohasecuritiesstockai/dataflows/stocktwits.py
View file @
1a941ef5
...
...
@@ -24,7 +24,7 @@ from .symbol_utils import crypto_base
logger
=
logging
.
getLogger
(
__name__
)
_API
=
"https://api.stocktwits.com/api/2/streams/symbol/{ticker}.json"
_UA
=
"dohasecuritiesstockai/0.2 (+https://github.com/
TauricResearch/TradingAgents
)"
_UA
=
"dohasecuritiesstockai/0.2 (+https://github.com/
xeron56/DohasecuritiesStockAi
)"
def
_stocktwits_symbol
(
ticker
:
str
)
->
str
:
...
...
dohasecuritiesstockai/default_config.py
View file @
1a941ef5
import
os
from
dohasecuritiesstockai.languages
import
normalize_output_language
# Keep the established data directory so the package rename does not orphan
# existing reports, checkpoints, caches, or the trading-memory log.
_TRADINGAGENTS_HOME
=
os
.
path
.
join
(
os
.
path
.
expanduser
(
"~"
),
".tradingagents"
)
...
...
@@ -72,7 +74,10 @@ def _apply_env_overrides(config: dict) -> dict:
if
raw
is
None
or
raw
==
""
:
continue
try
:
config
[
key
]
=
_coerce
(
raw
,
config
.
get
(
key
))
value
=
_coerce
(
raw
,
config
.
get
(
key
))
if
key
==
"output_language"
:
value
=
normalize_output_language
(
value
)
config
[
key
]
=
value
except
ValueError
as
exc
:
raise
ValueError
(
f
"Invalid value for {env_var}: {exc}"
)
from
exc
return
config
...
...
dohasecuritiesstockai/languages.py
0 → 100644
View file @
1a941ef5
"""Supported report-output languages."""
OUTPUT_LANGUAGE_CHOICES
=
(
(
"English (default)"
,
"English"
),
(
"Bangla (বাংলা)"
,
"Bangla"
),
)
_OUTPUT_LANGUAGE_ALIASES
=
{
"english"
:
"English"
,
"en"
:
"English"
,
"bangla"
:
"Bangla"
,
"bengali"
:
"Bangla"
,
"বাংলা"
:
"Bangla"
,
"bn"
:
"Bangla"
,
}
def
normalize_output_language
(
value
:
str
|
None
)
->
str
:
"""Return the canonical supported language name or raise ``ValueError``."""
normalized
=
str
(
value
or
"English"
)
.
strip
()
.
casefold
()
language
=
_OUTPUT_LANGUAGE_ALIASES
.
get
(
normalized
)
if
language
is
None
:
raise
ValueError
(
"output language must be English or Bangla"
)
return
language
tests/test_cli_env_skip.py
View file @
1a941ef5
...
...
@@ -40,7 +40,7 @@ class TestCliSkipsPromptsFromEnv(unittest.TestCase):
"TRADINGAGENTS_DEEP_THINK_LLM"
:
"kimi-k2.5"
,
"TRADINGAGENTS_QUICK_THINK_LLM"
:
"deepseek-v4-pro"
,
"TRADINGAGENTS_LLM_BACKEND_URL"
:
"https://opencode.ai/zen/go/v1"
,
"TRADINGAGENTS_OUTPUT_LANGUAGE"
:
"
Japanese
"
,
"TRADINGAGENTS_OUTPUT_LANGUAGE"
:
"
Bangla
"
,
}
fake_cfg
=
dict
(
m
.
DEFAULT_CONFIG
)
fake_cfg
.
update
({
...
...
@@ -48,13 +48,11 @@ class TestCliSkipsPromptsFromEnv(unittest.TestCase):
"backend_url"
:
"https://opencode.ai/zen/go/v1"
,
"quick_think_llm"
:
"deepseek-v4-pro"
,
"deep_think_llm"
:
"kimi-k2.5"
,
"output_language"
:
"
Japanese
"
,
"output_language"
:
"
Bangla
"
,
})
with
mock
.
patch
.
dict
(
os
.
environ
,
env
,
clear
=
False
),
\
mock
.
patch
.
object
(
m
,
"DEFAULT_CONFIG"
,
fake_cfg
),
\
mock
.
patch
.
object
(
m
,
"fetch_announcements"
,
return_value
=
None
),
\
mock
.
patch
.
object
(
m
,
"display_announcements"
),
\
mock
.
patch
.
object
(
m
,
"get_ticker"
,
return_value
=
"AAPL"
),
\
mock
.
patch
.
object
(
m
,
"get_analysis_date"
,
return_value
=
"2026-05-29"
),
\
mock
.
patch
.
object
(
m
,
"select_analysts"
,
return_value
=
[]),
\
...
...
@@ -79,7 +77,7 @@ class TestCliSkipsPromptsFromEnv(unittest.TestCase):
self
.
assertEqual
(
sel
[
"backend_url"
],
"https://opencode.ai/zen/go/v1"
)
self
.
assertEqual
(
sel
[
"shallow_thinker"
],
"deepseek-v4-pro"
)
self
.
assertEqual
(
sel
[
"deep_thinker"
],
"kimi-k2.5"
)
self
.
assertEqual
(
sel
[
"output_language"
],
"
Japanese
"
)
self
.
assertEqual
(
sel
[
"output_language"
],
"
Bangla
"
)
@pytest.mark.unit
...
...
@@ -96,8 +94,6 @@ class TestResearchDepthSkippedFromEnv(unittest.TestCase):
with
mock
.
patch
.
dict
(
os
.
environ
,
env
,
clear
=
False
),
\
mock
.
patch
.
object
(
m
,
"DEFAULT_CONFIG"
,
fake_cfg
),
\
mock
.
patch
.
object
(
m
,
"fetch_announcements"
,
return_value
=
None
),
\
mock
.
patch
.
object
(
m
,
"display_announcements"
),
\
mock
.
patch
.
object
(
m
,
"get_ticker"
,
return_value
=
"AAPL"
),
\
mock
.
patch
.
object
(
m
,
"get_analysis_date"
,
return_value
=
"2026-05-29"
),
\
mock
.
patch
.
object
(
m
,
"select_analysts"
,
return_value
=
[]),
\
...
...
@@ -126,8 +122,6 @@ class TestReasoningEffortSkippedFromEnv(unittest.TestCase):
with
mock
.
patch
.
dict
(
os
.
environ
,
env
,
clear
=
False
),
\
mock
.
patch
.
object
(
m
,
"DEFAULT_CONFIG"
,
fake_cfg
),
\
mock
.
patch
.
object
(
m
,
"fetch_announcements"
,
return_value
=
None
),
\
mock
.
patch
.
object
(
m
,
"display_announcements"
),
\
mock
.
patch
.
object
(
m
,
"get_ticker"
,
return_value
=
"AAPL"
),
\
mock
.
patch
.
object
(
m
,
"get_analysis_date"
,
return_value
=
"2026-05-29"
),
\
mock
.
patch
.
object
(
m
,
"select_analysts"
,
return_value
=
[]),
\
...
...
tests/test_env_overrides.py
View file @
1a941ef5
...
...
@@ -35,13 +35,26 @@ def test_string_overrides(monkeypatch):
TRADINGAGENTS_DEEP_THINK_LLM
=
"gemini-3-pro-preview"
,
TRADINGAGENTS_QUICK_THINK_LLM
=
"gemini-3-flash-preview"
,
TRADINGAGENTS_LLM_BACKEND_URL
=
"https://example.invalid/v1"
,
TRADINGAGENTS_OUTPUT_LANGUAGE
=
"
Chinese
"
,
TRADINGAGENTS_OUTPUT_LANGUAGE
=
"
Bangla
"
,
)
assert
dc
.
DEFAULT_CONFIG
[
"llm_provider"
]
==
"google"
assert
dc
.
DEFAULT_CONFIG
[
"deep_think_llm"
]
==
"gemini-3-pro-preview"
assert
dc
.
DEFAULT_CONFIG
[
"quick_think_llm"
]
==
"gemini-3-flash-preview"
assert
dc
.
DEFAULT_CONFIG
[
"backend_url"
]
==
"https://example.invalid/v1"
assert
dc
.
DEFAULT_CONFIG
[
"output_language"
]
==
"Chinese"
assert
dc
.
DEFAULT_CONFIG
[
"output_language"
]
==
"Bangla"
@pytest.mark.parametrize
(
"alias"
,
[
"Bangla"
,
"Bengali"
,
"বাংলা"
,
"bn"
])
def
test_bangla_output_language_aliases
(
monkeypatch
,
alias
):
dc
=
_reload_with_env
(
monkeypatch
,
TRADINGAGENTS_OUTPUT_LANGUAGE
=
alias
)
assert
dc
.
DEFAULT_CONFIG
[
"output_language"
]
==
"Bangla"
def
test_unsupported_output_language_is_rejected
(
monkeypatch
):
with
pytest
.
raises
(
ValueError
,
match
=
"English or Bangla"
):
_reload_with_env
(
monkeypatch
,
TRADINGAGENTS_OUTPUT_LANGUAGE
=
"Chinese"
)
monkeypatch
.
delenv
(
"TRADINGAGENTS_OUTPUT_LANGUAGE"
,
raising
=
False
)
importlib
.
reload
(
default_config_module
)
def
test_int_coercion
(
monkeypatch
):
...
...
tests/test_i18n_coverage.py
View file @
1a941ef5
...
...
@@ -39,13 +39,21 @@ class TestLanguageInstruction:
set_config
({
"output_language"
:
"English"
})
assert
get_language_instruction
()
==
""
def
test_
non_english_emits
_directive
(
self
):
def
test_
bangla_emits_bangla_script
_directive
(
self
):
from
dohasecuritiesstockai.dataflows.config
import
set_config
set_config
({
"output_language"
:
"
中文
"
})
set_config
({
"output_language"
:
"
Bangla
"
})
out
=
get_language_instruction
()
assert
"中文"
in
out
assert
"Bangla"
in
out
assert
"বাংলা"
in
out
assert
"entire response"
in
out
def
test_unsupported_language_is_rejected
(
self
):
from
dohasecuritiesstockai.dataflows.config
import
set_config
set_config
({
"output_language"
:
"Chinese"
})
with
pytest
.
raises
(
ValueError
,
match
=
"English or Bangla"
):
get_language_instruction
()
@pytest.mark.unit
@pytest.mark.parametrize
(
"rel"
,
REPORT_AGENTS
)
...
...
tests/test_openrouter_model_select.py
View file @
1a941ef5
"""OpenRouter model selection: prompts are labeled by mode (#1000); required
prompts exit cleanly on cancel; the output-language prompt defaults to English
on cancel; and the OpenRouter list is newest-first."""
"""OpenRouter and output-language selection behavior."""
from
unittest
import
mock
...
...
@@ -111,12 +109,23 @@ class TestCancelExitsCleanly:
@pytest.mark.unit
class
Test
LanguageDefaultsToEnglish
:
class
Test
OutputLanguage
:
def
test_select_cancel_defaults_english
(
self
):
with
mock
.
patch
.
object
(
utils
.
questionary
,
"select"
,
return_value
=
_asks
(
None
)):
assert
utils
.
ask_output_language
()
==
"English"
def
test_custom_language_cancel_defaults_english
(
self
):
with
mock
.
patch
.
object
(
utils
.
questionary
,
"select"
,
return_value
=
_asks
(
"custom"
)),
\
mock
.
patch
.
object
(
utils
.
questionary
,
"text"
,
return_value
=
_asks
(
None
)):
assert
utils
.
ask_output_language
()
==
"English"
def
test_selector_only_offers_english_and_bangla
(
self
):
captured
=
{}
def
fake_select
(
message
,
**
kwargs
):
captured
[
"message"
]
=
message
captured
[
"labels"
]
=
[
choice
.
title
for
choice
in
kwargs
[
"choices"
]]
captured
[
"values"
]
=
[
choice
.
value
for
choice
in
kwargs
[
"choices"
]]
return
_asks
(
"Bangla"
)
with
mock
.
patch
.
object
(
utils
.
questionary
,
"select"
,
side_effect
=
fake_select
):
assert
utils
.
ask_output_language
()
==
"Bangla"
assert
captured
[
"message"
]
==
"Select Output Language:"
assert
captured
[
"values"
]
==
[
"English"
,
"Bangla"
]
assert
captured
[
"labels"
]
==
[
"English (default)"
,
"Bangla (বাংলা)"
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment