pfund-plot 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pfund_plot/__init__.py +183 -0
- pfund_plot/__main__.py +9 -0
- pfund_plot/cli/__init__.py +3 -0
- pfund_plot/cli/commands/gallery/__init__.py +15 -0
- pfund_plot/cli/commands/gallery/gallery_marimo.py +462 -0
- pfund_plot/cli/commands/serve.py +21 -0
- pfund_plot/cli/main.py +20 -0
- pfund_plot/config.py +109 -0
- pfund_plot/enums/__init__.py +16 -0
- pfund_plot/enums/dataframe_backend.py +6 -0
- pfund_plot/enums/display_mode.py +7 -0
- pfund_plot/enums/panel_design.py +8 -0
- pfund_plot/enums/panel_theme.py +6 -0
- pfund_plot/enums/plotting_backend.py +12 -0
- pfund_plot/js_tap/components/candlestick.js +9566 -0
- pfund_plot/mixins/streaming_market_feed_mixin.py +162 -0
- pfund_plot/plots/altair.py +32 -0
- pfund_plot/plots/area/__init__.py +82 -0
- pfund_plot/plots/area/bokeh.py +151 -0
- pfund_plot/plots/bar/__init__.py +80 -0
- pfund_plot/plots/bar/bokeh.py +128 -0
- pfund_plot/plots/bokeh.py +32 -0
- pfund_plot/plots/candlestick/__init__.py +77 -0
- pfund_plot/plots/candlestick/bokeh.py +124 -0
- pfund_plot/plots/candlestick/svelte.py +161 -0
- pfund_plot/plots/holoviews.py +32 -0
- pfund_plot/plots/label/__init__.py +43 -0
- pfund_plot/plots/label/bokeh.py +89 -0
- pfund_plot/plots/layout/__init__.py +98 -0
- pfund_plot/plots/layout/layout.py +116 -0
- pfund_plot/plots/layout/panel.py +51 -0
- pfund_plot/plots/layout/tabs/__init__.py +36 -0
- pfund_plot/plots/layout/tabs/panel.py +51 -0
- pfund_plot/plots/lazy.py +408 -0
- pfund_plot/plots/line/__init__.py +37 -0
- pfund_plot/plots/line/bokeh.py +137 -0
- pfund_plot/plots/matplotlib.py +32 -0
- pfund_plot/plots/plot.py +1131 -0
- pfund_plot/plots/plotly.py +32 -0
- pfund_plot/plots/scatter/__init__.py +62 -0
- pfund_plot/plots/scatter/bokeh.py +158 -0
- pfund_plot/plots/scatter/marker.py +107 -0
- pfund_plot/plots/ta.py +6 -0
- pfund_plot/renderers/base.py +84 -0
- pfund_plot/renderers/browser.py +28 -0
- pfund_plot/renderers/desktop.py +109 -0
- pfund_plot/renderers/notebook.py +92 -0
- pfund_plot/typing.py +29 -0
- pfund_plot/utils/__init__.py +176 -0
- pfund_plot/utils/bokeh.py +177 -0
- pfund_plot/widgets/base.py +76 -0
- pfund_plot/widgets/datetime_widget.py +221 -0
- pfund_plot/widgets/ticker_widget.py +82 -0
- pfund_plot-0.0.1.dist-info/METADATA +148 -0
- pfund_plot-0.0.1.dist-info/RECORD +57 -0
- pfund_plot-0.0.1.dist-info/WHEEL +4 -0
- pfund_plot-0.0.1.dist-info/entry_points.txt +6 -0
pfund_plot/__init__.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from pfund_plot.plots.altair import (
|
|
7
|
+
Altair as altair,
|
|
8
|
+
)
|
|
9
|
+
from pfund_plot.plots.altair import (
|
|
10
|
+
Altair as vega,
|
|
11
|
+
)
|
|
12
|
+
from pfund_plot.plots.area import (
|
|
13
|
+
Area as area,
|
|
14
|
+
)
|
|
15
|
+
from pfund_plot.plots.bar import (
|
|
16
|
+
Bar as bar,
|
|
17
|
+
)
|
|
18
|
+
from pfund_plot.plots.bokeh import (
|
|
19
|
+
Bokeh as bokeh,
|
|
20
|
+
)
|
|
21
|
+
from pfund_plot.plots.candlestick import (
|
|
22
|
+
Candlestick as candlestick,
|
|
23
|
+
)
|
|
24
|
+
from pfund_plot.plots.candlestick import (
|
|
25
|
+
Candlestick as kline,
|
|
26
|
+
)
|
|
27
|
+
from pfund_plot.plots.candlestick import (
|
|
28
|
+
Candlestick as ohlc,
|
|
29
|
+
)
|
|
30
|
+
from pfund_plot.plots.holoviews import (
|
|
31
|
+
Holoviews as holoviews,
|
|
32
|
+
)
|
|
33
|
+
from pfund_plot.plots.holoviews import (
|
|
34
|
+
Holoviews as hv,
|
|
35
|
+
)
|
|
36
|
+
from pfund_plot.plots.label import (
|
|
37
|
+
Label as label,
|
|
38
|
+
)
|
|
39
|
+
from pfund_plot.plots.layout import (
|
|
40
|
+
Layout as layout,
|
|
41
|
+
)
|
|
42
|
+
from pfund_plot.plots.layout.tabs import (
|
|
43
|
+
Tabs as tabs,
|
|
44
|
+
)
|
|
45
|
+
from pfund_plot.plots.line import (
|
|
46
|
+
Line as line,
|
|
47
|
+
)
|
|
48
|
+
from pfund_plot.plots.matplotlib import (
|
|
49
|
+
Matplotlib as matplotlib,
|
|
50
|
+
)
|
|
51
|
+
from pfund_plot.plots.matplotlib import (
|
|
52
|
+
Matplotlib as mpl,
|
|
53
|
+
)
|
|
54
|
+
from pfund_plot.plots.plotly import (
|
|
55
|
+
Plotly as plotly,
|
|
56
|
+
)
|
|
57
|
+
from pfund_plot.plots.scatter import (
|
|
58
|
+
Scatter as scatter,
|
|
59
|
+
)
|
|
60
|
+
from pfund_plot.plots.scatter.marker import (
|
|
61
|
+
Marker as marker,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# NOTE: data update in anywidget (backend=svelte) may have issues (especially in marimo) after loading panel extensions
|
|
65
|
+
# if anywidget+svelte backend is not working, try to comment this out
|
|
66
|
+
#
|
|
67
|
+
# plotly/vega resolve to panel.models.* (bundled with Panel) — they don't import the
|
|
68
|
+
# plotly/altair libs, so they're safe even when those optional extras aren't installed.
|
|
69
|
+
# "ipywidgets" pulls in panel.io.ipywidget -> ipywidgets_bokeh, which is absent in WASM
|
|
70
|
+
# (and where the anywidget/svelte backend isn't available anyway), so guard only that one.
|
|
71
|
+
import importlib.util
|
|
72
|
+
|
|
73
|
+
import panel as pn
|
|
74
|
+
|
|
75
|
+
from pfund_plot.config import configure, get_config
|
|
76
|
+
|
|
77
|
+
_panel_extensions = ["plotly", "vega"]
|
|
78
|
+
if importlib.util.find_spec("ipywidgets_bokeh") is not None:
|
|
79
|
+
_panel_extensions.append("ipywidgets")
|
|
80
|
+
pn.extension(*_panel_extensions)
|
|
81
|
+
# NOTE: this MUST be True, otherwise, some widgets won't work properly, e.g. candlestick widgets, slider and input will both trigger each other due to panel's async update, which leads to infinite loop.
|
|
82
|
+
pn.config.throttled = (
|
|
83
|
+
True # If panel sliders and inputs should be throttled until release of mouse.
|
|
84
|
+
)
|
|
85
|
+
# NOTE: /assets can only be recognized when setting pn.serve(static_dirs=pfund_plot.config.static_dirs)
|
|
86
|
+
# see static_dirs in config.py
|
|
87
|
+
# pn.config.js_files = {
|
|
88
|
+
# "your_custom_js_file": "/assets/your_custom_js_file.js",
|
|
89
|
+
# }
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def __getattr__(name: str):
|
|
93
|
+
if name == "__version__":
|
|
94
|
+
from importlib.metadata import version
|
|
95
|
+
|
|
96
|
+
return version("pfund_plot")
|
|
97
|
+
elif name == "plotly":
|
|
98
|
+
from pfund_plot.plots.plotly import Plotly
|
|
99
|
+
|
|
100
|
+
return Plotly
|
|
101
|
+
elif name in ("candlestick", "ohlc", "kline"):
|
|
102
|
+
from pfund_plot.plots.candlestick import Candlestick
|
|
103
|
+
|
|
104
|
+
return Candlestick
|
|
105
|
+
elif name == "line":
|
|
106
|
+
from pfund_plot.plots.line import Line
|
|
107
|
+
|
|
108
|
+
return Line
|
|
109
|
+
elif name == "area":
|
|
110
|
+
from pfund_plot.plots.area import Area
|
|
111
|
+
|
|
112
|
+
return Area
|
|
113
|
+
elif name == "layout":
|
|
114
|
+
from pfund_plot.plots.layout import Layout
|
|
115
|
+
|
|
116
|
+
return Layout
|
|
117
|
+
elif name == "tabs":
|
|
118
|
+
from pfund_plot.plots.layout.tabs import Tabs
|
|
119
|
+
|
|
120
|
+
return Tabs
|
|
121
|
+
elif name == "scatter":
|
|
122
|
+
from pfund_plot.plots.scatter import Scatter
|
|
123
|
+
|
|
124
|
+
return Scatter
|
|
125
|
+
elif name == "marker":
|
|
126
|
+
from pfund_plot.plots.scatter.marker import Marker
|
|
127
|
+
|
|
128
|
+
return Marker
|
|
129
|
+
elif name == "label":
|
|
130
|
+
from pfund_plot.plots.label import Label
|
|
131
|
+
|
|
132
|
+
return Label
|
|
133
|
+
elif name == "bar":
|
|
134
|
+
from pfund_plot.plots.bar import Bar
|
|
135
|
+
|
|
136
|
+
return Bar
|
|
137
|
+
elif name in ("altair", "vega"):
|
|
138
|
+
from pfund_plot.plots.altair import Altair
|
|
139
|
+
|
|
140
|
+
return Altair
|
|
141
|
+
elif name in ("matplotlib", "mpl"):
|
|
142
|
+
from pfund_plot.plots.matplotlib import Matplotlib
|
|
143
|
+
|
|
144
|
+
return Matplotlib
|
|
145
|
+
elif name == "bokeh":
|
|
146
|
+
from pfund_plot.plots.bokeh import Bokeh
|
|
147
|
+
|
|
148
|
+
return Bokeh
|
|
149
|
+
elif name in ("holoviews", "hv"):
|
|
150
|
+
from pfund_plot.plots.holoviews import Holoviews
|
|
151
|
+
|
|
152
|
+
return Holoviews
|
|
153
|
+
else:
|
|
154
|
+
raise AttributeError(f"'{__name__}' has no attribute '{name}'")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
__all__ = (
|
|
158
|
+
"altair",
|
|
159
|
+
"area",
|
|
160
|
+
"bar",
|
|
161
|
+
"bokeh",
|
|
162
|
+
"candlestick",
|
|
163
|
+
"configure",
|
|
164
|
+
"get_config",
|
|
165
|
+
"holoviews",
|
|
166
|
+
"hv",
|
|
167
|
+
"kline",
|
|
168
|
+
"label",
|
|
169
|
+
"layout",
|
|
170
|
+
"line",
|
|
171
|
+
"marker",
|
|
172
|
+
"matplotlib",
|
|
173
|
+
"mpl",
|
|
174
|
+
"ohlc",
|
|
175
|
+
"plotly",
|
|
176
|
+
"scatter",
|
|
177
|
+
"tabs",
|
|
178
|
+
"vega",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def __dir__():
|
|
183
|
+
return sorted(__all__)
|
pfund_plot/__main__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
GALLERY_DIR = Path(__file__).parent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.command(hidden=True)
|
|
11
|
+
def gallery():
|
|
12
|
+
"""Open the gallery to visually verify all supported plots."""
|
|
13
|
+
script = GALLERY_DIR / "gallery_marimo.py"
|
|
14
|
+
result = subprocess.run(["marimo", "edit", str(script)], check=False)
|
|
15
|
+
sys.exit(result.returncode)
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# ruff: noqa
|
|
2
|
+
|
|
3
|
+
import marimo
|
|
4
|
+
|
|
5
|
+
__generated_with = "0.23.4"
|
|
6
|
+
app = marimo.App(width="columns")
|
|
7
|
+
|
|
8
|
+
with app.setup:
|
|
9
|
+
from threading import Thread
|
|
10
|
+
|
|
11
|
+
import altair as alt
|
|
12
|
+
import holoviews as hv
|
|
13
|
+
import marimo as mo
|
|
14
|
+
import matplotlib.pyplot as mpl
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pfeed as pe
|
|
17
|
+
import plotly.graph_objects as go
|
|
18
|
+
import polars as pl
|
|
19
|
+
from bokeh.plotting import figure
|
|
20
|
+
|
|
21
|
+
import pfund_plot as plt
|
|
22
|
+
|
|
23
|
+
def stop_later(thread, delay):
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
time.sleep(delay)
|
|
27
|
+
thread.stop()
|
|
28
|
+
print("Stopped the streaming thread")
|
|
29
|
+
|
|
30
|
+
def prepare_streaming_feed():
|
|
31
|
+
# separate this streaming feed from the non-streaming one on purpose
|
|
32
|
+
streaming_feed = pe.Bybit(pipeline_mode=True).market_feed
|
|
33
|
+
streaming_resolution = "1s"
|
|
34
|
+
for streaming_product in products:
|
|
35
|
+
streaming_feed.stream(
|
|
36
|
+
product=streaming_product,
|
|
37
|
+
resolution=streaming_resolution,
|
|
38
|
+
)
|
|
39
|
+
return streaming_feed
|
|
40
|
+
|
|
41
|
+
feed = pe.Bybit(pipeline_mode=True).market_feed
|
|
42
|
+
products = ["ETH_USDT_PERP", "BTC_USDT_PERP"]
|
|
43
|
+
date = "2026-03-01"
|
|
44
|
+
resolution = "1h"
|
|
45
|
+
storage = "local"
|
|
46
|
+
|
|
47
|
+
# FIXME: use pfund-sampledata when its ready
|
|
48
|
+
for product in products:
|
|
49
|
+
for func in [feed.retrieve, feed.download]:
|
|
50
|
+
df = func(
|
|
51
|
+
product=product,
|
|
52
|
+
resolution=resolution,
|
|
53
|
+
start_date=date,
|
|
54
|
+
end_date=date,
|
|
55
|
+
storage_config=pe.StorageConfig(
|
|
56
|
+
storage=storage,
|
|
57
|
+
),
|
|
58
|
+
).run()
|
|
59
|
+
if df is not None:
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
# create some fake columns for plt.marker and plt.label
|
|
63
|
+
df = df.collect()
|
|
64
|
+
df = df.with_columns(
|
|
65
|
+
pl.Series("trade", np.random.choice([1, -1], size=len(df))),
|
|
66
|
+
).with_columns(
|
|
67
|
+
pl.when(pl.col("trade") == 1)
|
|
68
|
+
.then(pl.lit("buy"))
|
|
69
|
+
.otherwise(pl.lit("sell"))
|
|
70
|
+
.alias("label")
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.cell
|
|
75
|
+
def _():
|
|
76
|
+
mo.md("""
|
|
77
|
+
# Gallery
|
|
78
|
+
This is a gallery of plots supported by `pfund-plot` for maintainers to quickly visualize them to make sure they are working correctly. Press `Cmd + .` to toggle the app view to see all the plots nicely.
|
|
79
|
+
> WARNING: somehow some widgets don't work in app view or even after switching back from the app view. So you might want to manually test the widgets a bit before toggling it.
|
|
80
|
+
""")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.cell(hide_code=True)
|
|
85
|
+
def _():
|
|
86
|
+
mo.md(r"""
|
|
87
|
+
# Data Used in the Gallery
|
|
88
|
+
""")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.cell
|
|
93
|
+
def _():
|
|
94
|
+
df.head()
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.cell(hide_code=True)
|
|
99
|
+
def _():
|
|
100
|
+
mo.md(r"""
|
|
101
|
+
# 1. Candlestick
|
|
102
|
+
""")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.cell
|
|
107
|
+
def _():
|
|
108
|
+
candlestick = plt.ohlc(df).backend("bokeh")
|
|
109
|
+
candlestick
|
|
110
|
+
return (candlestick,)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.cell(hide_code=True)
|
|
114
|
+
def _():
|
|
115
|
+
mo.md(r"""
|
|
116
|
+
# 2. Line
|
|
117
|
+
""")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@app.cell
|
|
122
|
+
def _():
|
|
123
|
+
line = (
|
|
124
|
+
plt.line(df, x="date", y="close").style(color="orange").control(widgets=False)
|
|
125
|
+
)
|
|
126
|
+
line
|
|
127
|
+
return (line,)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.cell(hide_code=True)
|
|
131
|
+
def _():
|
|
132
|
+
mo.md(r"""
|
|
133
|
+
# 3. Scatter
|
|
134
|
+
""")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@app.cell
|
|
139
|
+
def _():
|
|
140
|
+
scatter = plt.scatter(df, x="open", y="close").control(widgets=False)
|
|
141
|
+
scatter
|
|
142
|
+
return (scatter,)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.cell(hide_code=True)
|
|
146
|
+
def _():
|
|
147
|
+
mo.md(r"""
|
|
148
|
+
# 4. Marker
|
|
149
|
+
""")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@app.cell
|
|
154
|
+
def _():
|
|
155
|
+
marker = plt.marker(df, x="date", y="close", signal="trade")
|
|
156
|
+
marker
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@app.cell(hide_code=True)
|
|
161
|
+
def _():
|
|
162
|
+
mo.md(r"""
|
|
163
|
+
# 5. Label
|
|
164
|
+
""")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@app.cell
|
|
169
|
+
def _():
|
|
170
|
+
label = plt.label(df, x="date", y="close", text="label")
|
|
171
|
+
label
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.cell(hide_code=True)
|
|
176
|
+
def _():
|
|
177
|
+
mo.md(r"""
|
|
178
|
+
# 6. Area
|
|
179
|
+
""")
|
|
180
|
+
return
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@app.cell
|
|
184
|
+
def _():
|
|
185
|
+
area = plt.area(df, x="date", y="close").style(marker="x")
|
|
186
|
+
area
|
|
187
|
+
return (area,)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@app.cell(hide_code=True)
|
|
191
|
+
def _():
|
|
192
|
+
mo.md(r"""
|
|
193
|
+
# 7. Bar
|
|
194
|
+
""")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@app.cell
|
|
199
|
+
def _():
|
|
200
|
+
bar = plt.bar(df, x="date", y=["high", "low"]).style(color=["steelblue", "coral"])
|
|
201
|
+
bar
|
|
202
|
+
return (bar,)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.cell(column=1, hide_code=True)
|
|
206
|
+
def _():
|
|
207
|
+
mo.md(r"""
|
|
208
|
+
# Operators: "+" and "|"
|
|
209
|
+
""")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@app.cell
|
|
214
|
+
def _(candlestick, line, scatter):
|
|
215
|
+
(candlestick + line) | scatter
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@app.cell(hide_code=True)
|
|
220
|
+
def _():
|
|
221
|
+
mo.md(r"""
|
|
222
|
+
# Overlays: "*"
|
|
223
|
+
""")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@app.cell
|
|
228
|
+
def _():
|
|
229
|
+
plt.ohlc(df).backend("bokeh") * plt.label(
|
|
230
|
+
df, x="date", y="close", text="label"
|
|
231
|
+
) * plt.marker(df, x="date", y="close", signal="trade")
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@app.cell(hide_code=True)
|
|
236
|
+
def _():
|
|
237
|
+
mo.md(r"""
|
|
238
|
+
# Plotly Wrapper: `plt.plotly`
|
|
239
|
+
""")
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@app.cell
|
|
244
|
+
def _():
|
|
245
|
+
plotly_fig = go.Figure()
|
|
246
|
+
plotly_fig.add_trace(go.Scatter(x=df["date"], y=df["close"], mode="lines"))
|
|
247
|
+
plotly_fig.update_layout(title="Price", xaxis_title="date", yaxis_title="close")
|
|
248
|
+
plotly_fig = plt.plotly(plotly_fig, sizing_mode="stretch_width")
|
|
249
|
+
plotly_fig
|
|
250
|
+
return (plotly_fig,)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@app.cell(hide_code=True)
|
|
254
|
+
def _():
|
|
255
|
+
mo.md(r"""
|
|
256
|
+
# Altair Wrapper: `plt.altair`
|
|
257
|
+
""")
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.cell
|
|
262
|
+
def _():
|
|
263
|
+
altair_fig = plt.altair(
|
|
264
|
+
alt.Chart(df)
|
|
265
|
+
.mark_line()
|
|
266
|
+
.encode(
|
|
267
|
+
x="date:T",
|
|
268
|
+
y=alt.Y("close:Q").scale(zero=False),
|
|
269
|
+
tooltip=["date:T", "close:Q"],
|
|
270
|
+
)
|
|
271
|
+
.properties(
|
|
272
|
+
title="Price",
|
|
273
|
+
width=600,
|
|
274
|
+
height=400,
|
|
275
|
+
),
|
|
276
|
+
sizing_mode="stretch_width",
|
|
277
|
+
)
|
|
278
|
+
altair_fig
|
|
279
|
+
return (altair_fig,)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@app.cell(hide_code=True)
|
|
283
|
+
def _():
|
|
284
|
+
mo.md(r"""
|
|
285
|
+
# Matplotlib Wrapper: `plt.matplotlib`
|
|
286
|
+
""")
|
|
287
|
+
return
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@app.cell
|
|
291
|
+
def _():
|
|
292
|
+
matplotlib_fig, ax = mpl.subplots()
|
|
293
|
+
ax.plot(df["date"], df["close"])
|
|
294
|
+
ax.set_title("Price")
|
|
295
|
+
ax.set_xlabel("date")
|
|
296
|
+
ax.set_ylabel("close")
|
|
297
|
+
matplotlib_fig = plt.matplotlib(
|
|
298
|
+
matplotlib_fig, height=500, sizing_mode="stretch_width"
|
|
299
|
+
)
|
|
300
|
+
matplotlib_fig
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@app.cell(hide_code=True)
|
|
305
|
+
def _():
|
|
306
|
+
mo.md(r"""
|
|
307
|
+
# Bokeh Wrapper: `plt.bokeh`
|
|
308
|
+
""")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@app.cell
|
|
313
|
+
def _():
|
|
314
|
+
bokeh_fig = figure(
|
|
315
|
+
title="Price",
|
|
316
|
+
x_axis_label="date",
|
|
317
|
+
y_axis_label="close",
|
|
318
|
+
x_axis_type="datetime",
|
|
319
|
+
height=400,
|
|
320
|
+
width=700,
|
|
321
|
+
)
|
|
322
|
+
bokeh_fig.line(df["date"], df["close"])
|
|
323
|
+
bokeh_fig = plt.bokeh(bokeh_fig, sizing_mode="stretch_width")
|
|
324
|
+
bokeh_fig
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@app.cell(hide_code=True)
|
|
329
|
+
def _():
|
|
330
|
+
mo.md(r"""
|
|
331
|
+
# Holoviews Wrapper: `plt.holoviews`
|
|
332
|
+
""")
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@app.cell
|
|
337
|
+
def _():
|
|
338
|
+
holoviews_fig = hv.Curve(df, kdims="date", vdims="close").opts(title="Price")
|
|
339
|
+
holoviews_fig = plt.holoviews(holoviews_fig, sizing_mode="stretch_width")
|
|
340
|
+
holoviews_fig
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@app.cell(column=2, hide_code=True)
|
|
345
|
+
def _():
|
|
346
|
+
mo.md(r"""
|
|
347
|
+
# Reactive Widgets
|
|
348
|
+
""")
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@app.cell
|
|
353
|
+
def _():
|
|
354
|
+
def get_df(product, resolution):
|
|
355
|
+
return feed.retrieve(
|
|
356
|
+
product=product,
|
|
357
|
+
resolution=resolution,
|
|
358
|
+
start_date=date,
|
|
359
|
+
end_date=date,
|
|
360
|
+
storage_config=pe.StorageConfig(
|
|
361
|
+
storage=storage,
|
|
362
|
+
),
|
|
363
|
+
).run()
|
|
364
|
+
|
|
365
|
+
reactive_candlestick = plt.ohlc(
|
|
366
|
+
get_df(products[0], resolution),
|
|
367
|
+
callback=get_df,
|
|
368
|
+
product=products,
|
|
369
|
+
resolution=[resolution],
|
|
370
|
+
)
|
|
371
|
+
reactive_candlestick
|
|
372
|
+
return (reactive_candlestick,)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@app.cell(hide_code=True)
|
|
376
|
+
def _():
|
|
377
|
+
mo.md(r"""
|
|
378
|
+
# Tabs
|
|
379
|
+
""")
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@app.cell
|
|
384
|
+
def _(altair_fig, area, bar, line, plotly_fig, reactive_candlestick):
|
|
385
|
+
tabs = plt.tabs(
|
|
386
|
+
reactive_candlestick,
|
|
387
|
+
line,
|
|
388
|
+
area,
|
|
389
|
+
bar,
|
|
390
|
+
plotly_fig,
|
|
391
|
+
altair_fig,
|
|
392
|
+
)
|
|
393
|
+
tabs
|
|
394
|
+
return (tabs,)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@app.cell(hide_code=True)
|
|
398
|
+
def _():
|
|
399
|
+
mo.md(r"""
|
|
400
|
+
# Layout + Streaming
|
|
401
|
+
> put streaming plot inside layout so that we can stop them both at the same time
|
|
402
|
+
""")
|
|
403
|
+
return
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@app.cell
|
|
407
|
+
def _(altair_fig, plotly_fig, tabs):
|
|
408
|
+
browser_thread = (
|
|
409
|
+
plt.layout(
|
|
410
|
+
plt.ohlc(prepare_streaming_feed())
|
|
411
|
+
.control(update_interval=1000)
|
|
412
|
+
.mode("browser"),
|
|
413
|
+
tabs,
|
|
414
|
+
plt.line(prepare_streaming_feed(), x="date", y="close")
|
|
415
|
+
.control(update_interval=1000)
|
|
416
|
+
.mode("browser"),
|
|
417
|
+
plt.area(prepare_streaming_feed(), x="date", y="close")
|
|
418
|
+
.style(marker="x")
|
|
419
|
+
.control(update_interval=1000)
|
|
420
|
+
.mode("browser"),
|
|
421
|
+
plt.bar(prepare_streaming_feed(), x="date", y="close")
|
|
422
|
+
.control(update_interval=1000)
|
|
423
|
+
.mode("browser"),
|
|
424
|
+
plotly_fig,
|
|
425
|
+
altair_fig,
|
|
426
|
+
)
|
|
427
|
+
.mode("browser")
|
|
428
|
+
.control(allow_drag=False, linked_axes=False)
|
|
429
|
+
.show()
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# stop in background without blocking
|
|
433
|
+
Thread(target=stop_later, args=(browser_thread, 20)).start()
|
|
434
|
+
return
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@app.cell
|
|
438
|
+
def _():
|
|
439
|
+
desktop_thread = (
|
|
440
|
+
plt.line(df, x="date", y="close")
|
|
441
|
+
.style(color="orange")
|
|
442
|
+
.control(widgets=False)
|
|
443
|
+
.mode("desktop")
|
|
444
|
+
.show()
|
|
445
|
+
)
|
|
446
|
+
# desktop_thread = plt.layout(
|
|
447
|
+
# plt.ohlc(prepare_streaming_feed()).control(update_interval=1000).mode('desktop'),
|
|
448
|
+
# tabs,
|
|
449
|
+
# plt.line(prepare_streaming_feed(), x='date', y='close').control(update_interval=1000).mode('desktop'),
|
|
450
|
+
# plt.area(prepare_streaming_feed(), x='date', y='close').style(marker='x').control(update_interval=1000).mode('desktop'),
|
|
451
|
+
# plt.bar(prepare_streaming_feed(), x='date', y='close').control(update_interval=1000).mode('desktop'),
|
|
452
|
+
# plotly_fig,
|
|
453
|
+
# altair_fig,
|
|
454
|
+
# ).mode('desktop').control(allow_drag=False, linked_axes=False).show()
|
|
455
|
+
|
|
456
|
+
# stop in background without blocking
|
|
457
|
+
Thread(target=stop_later, args=(desktop_thread, 3)).start()
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
if __name__ == "__main__":
|
|
462
|
+
app.run()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.command(
|
|
8
|
+
add_help_option=False,
|
|
9
|
+
context_settings=dict(
|
|
10
|
+
ignore_unknown_options=True,
|
|
11
|
+
allow_extra_args=True,
|
|
12
|
+
),
|
|
13
|
+
)
|
|
14
|
+
@click.pass_context
|
|
15
|
+
def serve(ctx):
|
|
16
|
+
"""Serve a Panel application.
|
|
17
|
+
|
|
18
|
+
Passes all arguments directly to 'panel serve'.
|
|
19
|
+
"""
|
|
20
|
+
result = subprocess.run(["panel", "serve", *ctx.args], check=False)
|
|
21
|
+
sys.exit(result.returncode)
|
pfund_plot/cli/main.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pfund_kit.cli import create_cli_group
|
|
2
|
+
from pfund_kit.cli.commands import config, docker_compose, remove
|
|
3
|
+
|
|
4
|
+
from pfund_plot.cli.commands.gallery import gallery
|
|
5
|
+
from pfund_plot.cli.commands.serve import serve
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def init_context(ctx):
|
|
9
|
+
"""Initialize pfund_plot-specific context"""
|
|
10
|
+
from pfund_plot.config import get_config
|
|
11
|
+
|
|
12
|
+
ctx.obj["config"] = get_config()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
pfund_plot_group = create_cli_group("pfund_plot", init_context=init_context)
|
|
16
|
+
pfund_plot_group.add_command(config)
|
|
17
|
+
pfund_plot_group.add_command(docker_compose)
|
|
18
|
+
pfund_plot_group.add_command(remove)
|
|
19
|
+
pfund_plot_group.add_command(serve)
|
|
20
|
+
pfund_plot_group.add_command(gallery)
|