Skip to content

Commit a9be4ee

Browse files
authored
Working on client (#7)
1 parent 238061a commit a9be4ee

File tree

7 files changed

+329
-337
lines changed

7 files changed

+329
-337
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dist
2727
.venv
2828
.mypy_cache
2929
.pytest_cache
30+
.ruff_cache
3031
.python-version
3132

3233
# Jupyter

poetry.lock

Lines changed: 171 additions & 199 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ Documentation = "https://quantmind.github.io/quantflow/"
1515
python = ">=3.11"
1616
scipy = "^1.14.1"
1717
pydantic = "^2.0.2"
18-
ccy = {version="1.6.0", extras=["cli"]}
19-
asciichart = "^0.1"
18+
ccy = {version="1.6.0"}
2019
python-dotenv = "^1.0.1"
21-
asciichartpy = "^1.5.25"
22-
prompt-toolkit = "^3.0.43"
2320
polars = {version = "^1.11.0", extras=["pandas", "pyarrow"]}
21+
asciichartpy = { version = "^1.5.25", optional = true }
22+
prompt-toolkit = { version = "^3.0.43", optional = true }
2423
aio-fluid = {version = "^1.2.1", extras=["http"], optional = true}
24+
rich = {version = "^13.9.4", optional = true}
25+
click = {version = "^8.1.7", optional = true}
2526

2627
[tool.poetry.group.dev.dependencies]
2728
black = "^24.1.1"
@@ -34,6 +35,7 @@ pytest-asyncio = "^0.24.0"
3435

3536
[tool.poetry.extras]
3637
data = ["aio-fluid"]
38+
cli = ["asciichartpy", "prompt-toolkit", "rich", "click"]
3739

3840
[tool.poetry.group.book]
3941
optional = true
@@ -48,7 +50,7 @@ sympy = "^1.12"
4850
ipywidgets = "^8.0.7"
4951

5052
[tool.poetry.scripts]
51-
qf = "quantflow.cli:main"
53+
qf = "quantflow.cli.script:main"
5254

5355
[build-system]
5456
requires = ["poetry-core>=1.0.0"]
@@ -68,7 +70,6 @@ profile = "black"
6870

6971
[tool.ruff]
7072
lint.select = ["E", "F"]
71-
extend-exclude = ["fluid_apps/db/migrations"]
7273
line-length = 88
7374

7475
[tool.hatch.version]

quantflow/cli/__init__.py

Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +0,0 @@
1-
import asyncio
2-
import os
3-
from dataclasses import dataclass, field
4-
from typing import Any
5-
6-
import click
7-
import dotenv
8-
import pandas as pd
9-
from asciichartpy import plot
10-
from ccy.cli.console import df_to_rich
11-
from prompt_toolkit import PromptSession
12-
from prompt_toolkit.history import FileHistory
13-
from rich.console import Console
14-
from rich.text import Text
15-
16-
from quantflow.data.fmp import FMP
17-
18-
from . import settings
19-
20-
dotenv.load_dotenv()
21-
22-
FREQUENCIES = tuple(FMP().historical_frequencies())
23-
24-
25-
@click.group()
26-
def qf() -> None:
27-
pass
28-
29-
30-
@qf.command()
31-
@click.argument("symbol")
32-
def profile(symbol: str) -> None:
33-
"""Company profile"""
34-
data = asyncio.run(get_profile(symbol))[0]
35-
main.print(data.pop("description"))
36-
df = pd.DataFrame(data.items(), columns=["Key", "Value"])
37-
main.print(df_to_rich(df))
38-
39-
40-
@qf.command()
41-
@click.argument("symbol")
42-
@click.option(
43-
"-h",
44-
"--height",
45-
type=int,
46-
default=20,
47-
show_default=True,
48-
help="Chart height",
49-
)
50-
@click.option(
51-
"-l",
52-
"--length",
53-
type=int,
54-
default=100,
55-
show_default=True,
56-
help="Number of data points",
57-
)
58-
@click.option(
59-
"-f",
60-
"--frequency",
61-
type=click.Choice(FREQUENCIES),
62-
default="",
63-
help="Number of data points",
64-
)
65-
def chart(symbol: str, height: int, length: int, frequency: str) -> None:
66-
"""Symbol chart"""
67-
df = asyncio.run(get_prices(symbol, frequency))
68-
data = list(reversed(df["close"].tolist()[:length]))
69-
print(plot(data, {"height": height}))
70-
71-
72-
async def get_prices(symbol: str, frequency: str) -> pd.DataFrame:
73-
async with FMP() as cli:
74-
return await cli.prices(symbol, frequency)
75-
76-
77-
async def get_profile(symbol: str) -> list[dict]:
78-
async with FMP() as cli:
79-
return await cli.profile(symbol)
80-
81-
82-
@dataclass
83-
class App:
84-
console: Console = field(default_factory=Console)
85-
86-
def __call__(self) -> None:
87-
os.makedirs(settings.SETTINGS_DIRECTORY, exist_ok=True)
88-
history = FileHistory(str(settings.HIST_FILE_PATH))
89-
session: PromptSession = PromptSession(history=history)
90-
91-
self.print("Welcome to QuantFlow!", style="bold green")
92-
self.handle_command("help")
93-
94-
try:
95-
while True:
96-
try:
97-
text = session.prompt("quantflow> ")
98-
except KeyboardInterrupt:
99-
break
100-
else:
101-
self.handle_command(text)
102-
except click.Abort:
103-
self.console.print(Text("Bye!", style="bold magenta"))
104-
105-
def print(self, text_alike: Any, style: str = "") -> None:
106-
if isinstance(text_alike, str):
107-
style = style or "cyan"
108-
text_alike = Text(f"\n{text_alike}\n", style="cyan")
109-
self.console.print(text_alike)
110-
111-
def error(self, err: str | Exception) -> None:
112-
self.console.print(Text(f"\n{err}\n", style="bold red"))
113-
114-
def handle_command(self, text: str) -> None:
115-
self.current_command = text.split(" ")[0].strip()
116-
if not text:
117-
return
118-
elif text == "help":
119-
return qf.main(["--help"], standalone_mode=False)
120-
elif text == "exit":
121-
raise click.Abort()
122-
123-
try:
124-
qf.main(text.split(), standalone_mode=False)
125-
except click.exceptions.MissingParameter as e:
126-
self.error(e)
127-
except click.exceptions.NoSuchOption as e:
128-
self.error(e)
129-
130-
131-
main = App()

quantflow/cli/app.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import asyncio
2+
import os
3+
from dataclasses import dataclass, field
4+
from typing import Any, Self
5+
6+
import click
7+
import dotenv
8+
import pandas as pd
9+
from asciichartpy import plot
10+
from ccy.cli.console import df_to_rich
11+
from prompt_toolkit import PromptSession
12+
from prompt_toolkit.history import FileHistory
13+
from rich.console import Console
14+
from rich.text import Text
15+
16+
from quantflow.data.fmp import FMP
17+
18+
from . import settings
19+
20+
dotenv.load_dotenv()
21+
22+
FREQUENCIES = tuple(FMP().historical_frequencies())
23+
24+
25+
@click.group()
26+
def qf() -> None:
27+
pass
28+
29+
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)
82+
83+
84+
@dataclass
85+
class QfApp:
86+
console: Console = field(default_factory=Console)
87+
88+
@classmethod
89+
def from_context(cls, ctx: click.Context) -> Self:
90+
return ctx.obj # type: ignore
91+
92+
def __call__(self) -> None:
93+
os.makedirs(settings.SETTINGS_DIRECTORY, exist_ok=True)
94+
history = FileHistory(str(settings.HIST_FILE_PATH))
95+
session: PromptSession = PromptSession(history=history)
96+
97+
self.print("Welcome to QuantFlow!", style="bold green")
98+
self.handle_command("help")
99+
100+
try:
101+
while True:
102+
try:
103+
text = session.prompt("quantflow> ")
104+
except KeyboardInterrupt:
105+
break
106+
else:
107+
self.handle_command(text)
108+
except click.Abort:
109+
self.console.print(Text("Bye!", style="bold magenta"))
110+
111+
def print(self, text_alike: Any, style: str = "") -> None:
112+
if isinstance(text_alike, str):
113+
style = style or "cyan"
114+
text_alike = Text(f"\n{text_alike}\n", style="cyan")
115+
self.console.print(text_alike)
116+
117+
def error(self, err: str | Exception) -> None:
118+
self.console.print(Text(f"\n{err}\n", style="bold red"))
119+
120+
def handle_command(self, text: str) -> None:
121+
self.current_command = text.split(" ")[0].strip()
122+
if not text:
123+
return
124+
elif text == "help":
125+
return qf.main(["--help"], standalone_mode=False, obj=self)
126+
elif text == "exit":
127+
raise click.Abort()
128+
129+
try:
130+
qf.main(text.split(), standalone_mode=False, obj=self)
131+
except click.exceptions.MissingParameter as e:
132+
self.error(e)
133+
except click.exceptions.NoSuchOption as e:
134+
self.error(e)

quantflow/cli/script.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
try:
2+
from .app import QfApp
3+
except ImportError:
4+
raise ImportError(
5+
"Cannot run qf command line, "
6+
"quantflow needs to be installed with cli & data extras, "
7+
"pip install quantflow[cli, data]"
8+
) from None
9+
10+
main = QfApp()

readme.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,10 @@ pip install quantflow
2828

2929
## Command line tools
3030

31-
When installing with the extra `data` dependencies, it is possible to use the command line tool `qf`
31+
The command line tools are available when installing with the extra `cli` and `data` dependencies.
3232

33+
```bash
34+
pip install quantflow[cli,data]
35+
```
36+
37+
It is possible to use the command line tool `qf` to download data and run pricing and calibration scripts.

0 commit comments

Comments
 (0)