Skip to content

Commit 7ba20d0

Browse files
committed
Add search to client
1 parent a9be4ee commit 7ba20d0

File tree

3 files changed

+114
-70
lines changed

3 files changed

+114
-70
lines changed

quantflow/cli/app.py

Lines changed: 8 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,32 @@
1-
import asyncio
21
import os
32
from dataclasses import dataclass, field
4-
from typing import Any, Self
3+
from typing import Any
54

65
import click
7-
import dotenv
8-
import pandas as pd
9-
from asciichartpy import plot
10-
from ccy.cli.console import df_to_rich
116
from prompt_toolkit import PromptSession
127
from prompt_toolkit.history import FileHistory
138
from rich.console import Console
149
from rich.text import Text
1510

16-
from quantflow.data.fmp import FMP
1711

18-
from . import settings
19-
20-
dotenv.load_dotenv()
21-
22-
FREQUENCIES = tuple(FMP().historical_frequencies())
12+
from . import settings, commands
2313

2414

2515
@click.group()
2616
def qf() -> None:
2717
pass
2818

2919

30-
@qf.command()
31-
@click.argument("symbol")
32-
@click.pass_context
33-
def profile(ctx: click.Context, symbol: str) -> None:
34-
"""Company profile"""
35-
app = QfApp.from_context(ctx)
36-
data = asyncio.run(get_profile(symbol))[0]
37-
app.print(data.pop("description"))
38-
df = pd.DataFrame(data.items(), columns=["Key", "Value"])
39-
app.print(df_to_rich(df))
40-
41-
42-
@qf.command()
43-
@click.argument("symbol")
44-
@click.option(
45-
"-h",
46-
"--height",
47-
type=int,
48-
default=20,
49-
show_default=True,
50-
help="Chart height",
51-
)
52-
@click.option(
53-
"-l",
54-
"--length",
55-
type=int,
56-
default=100,
57-
show_default=True,
58-
help="Number of data points",
59-
)
60-
@click.option(
61-
"-f",
62-
"--frequency",
63-
type=click.Choice(FREQUENCIES),
64-
default="",
65-
help="Number of data points",
66-
)
67-
def chart(symbol: str, height: int, length: int, frequency: str) -> None:
68-
"""Symbol chart"""
69-
df = asyncio.run(get_prices(symbol, frequency))
70-
data = list(reversed(df["close"].tolist()[:length]))
71-
print(plot(data, {"height": height}))
72-
73-
74-
async def get_prices(symbol: str, frequency: str) -> pd.DataFrame:
75-
async with FMP() as cli:
76-
return await cli.prices(symbol, frequency)
77-
78-
79-
async def get_profile(symbol: str) -> list[dict]:
80-
async with FMP() as cli:
81-
return await cli.profile(symbol)
20+
qf.add_command(commands.exit)
21+
qf.add_command(commands.profile)
22+
qf.add_command(commands.search)
23+
qf.add_command(commands.chart)
8224

8325

8426
@dataclass
8527
class QfApp:
8628
console: Console = field(default_factory=Console)
8729

88-
@classmethod
89-
def from_context(cls, ctx: click.Context) -> Self:
90-
return ctx.obj # type: ignore
91-
9230
def __call__(self) -> None:
9331
os.makedirs(settings.SETTINGS_DIRECTORY, exist_ok=True)
9432
history = FileHistory(str(settings.HIST_FILE_PATH))
@@ -123,12 +61,12 @@ def handle_command(self, text: str) -> None:
12361
return
12462
elif text == "help":
12563
return qf.main(["--help"], standalone_mode=False, obj=self)
126-
elif text == "exit":
127-
raise click.Abort()
12864

12965
try:
13066
qf.main(text.split(), standalone_mode=False, obj=self)
13167
except click.exceptions.MissingParameter as e:
13268
self.error(e)
13369
except click.exceptions.NoSuchOption as e:
13470
self.error(e)
71+
except click.exceptions.UsageError as e:
72+
self.error(e)

quantflow/cli/commands.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from __future__ import annotations
2+
3+
import click
4+
import asyncio
5+
import pandas as pd
6+
from typing import TYPE_CHECKING
7+
from asciichartpy import plot
8+
from ccy.cli.console import df_to_rich
9+
from quantflow.data.fmp import FMP
10+
11+
FREQUENCIES = tuple(FMP().historical_frequencies())
12+
13+
if TYPE_CHECKING:
14+
from quantflow.cli.app import QfApp
15+
16+
17+
def from_context(ctx: click.Context) -> QfApp:
18+
return ctx.obj # type: ignore
19+
20+
21+
@click.command()
22+
def exit() -> None:
23+
"""Exit the program"""
24+
raise click.Abort()
25+
26+
27+
@click.command()
28+
@click.argument("symbol")
29+
@click.pass_context
30+
def profile(ctx: click.Context, symbol: str) -> None:
31+
"""Company profile"""
32+
app = from_context(ctx)
33+
data = asyncio.run(get_profile(symbol))
34+
if not data:
35+
app.error(f"Company {symbol} not found - try searching")
36+
else:
37+
d = data[0]
38+
app.print(d.pop("description") or "")
39+
df = pd.DataFrame(d.items(), columns=["Key", "Value"])
40+
app.print(df_to_rich(df))
41+
42+
43+
@click.command()
44+
@click.argument("text")
45+
@click.pass_context
46+
def search(ctx: click.Context, text: str) -> None:
47+
"""Search companies"""
48+
app = from_context(ctx)
49+
data = asyncio.run(search_company(text))
50+
df = pd.DataFrame(data, columns=["symbol", "name", "currency", "stockExchange"])
51+
app.print(df_to_rich(df))
52+
53+
54+
@click.command()
55+
@click.argument("symbol")
56+
@click.option(
57+
"-h",
58+
"--height",
59+
type=int,
60+
default=20,
61+
show_default=True,
62+
help="Chart height",
63+
)
64+
@click.option(
65+
"-l",
66+
"--length",
67+
type=int,
68+
default=100,
69+
show_default=True,
70+
help="Number of data points",
71+
)
72+
@click.option(
73+
"-f",
74+
"--frequency",
75+
type=click.Choice(FREQUENCIES),
76+
default="",
77+
help="Frequency of data - if not provided it is daily",
78+
)
79+
def chart(symbol: str, height: int, length: int, frequency: str) -> None:
80+
"""Symbol chart"""
81+
df = asyncio.run(get_prices(symbol, frequency))
82+
if df.empty:
83+
raise click.UsageError(
84+
f"No data for {symbol} - are you sure the symbol exists?"
85+
)
86+
data = list(reversed(df["close"].tolist()[:length]))
87+
print(plot(data, {"height": height}))
88+
89+
90+
async def get_prices(symbol: str, frequency: str) -> pd.DataFrame:
91+
async with FMP() as cli:
92+
return await cli.prices(symbol, frequency)
93+
94+
95+
async def get_profile(symbol: str) -> list[dict]:
96+
async with FMP() as cli:
97+
return await cli.profile(symbol)
98+
99+
100+
async def search_company(text: str) -> list[dict]:
101+
async with FMP() as cli:
102+
return await cli.search(text)

quantflow/cli/script.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import dotenv
2+
3+
dotenv.load_dotenv()
4+
15
try:
26
from .app import QfApp
37
except ImportError:

0 commit comments

Comments
 (0)