Plugins¶
Optopsy supports extending its functionality through Python entry points. Plugins are discovered automatically at runtime — install a plugin package and it is available immediately.
Entry Point Groups¶
There are five plugin groups:
| Group | Purpose |
|---|---|
optopsy.strategies |
Add custom strategy functions |
optopsy.signals |
Add custom entry/exit signal factories |
optopsy.providers |
Add data provider backends |
optopsy.tools |
Add Chat UI tools |
optopsy.auth |
Add Chat UI authentication |
All discovery is lazy and cached. A broken plugin is logged and skipped — it cannot crash the host application.
Registering Plugins¶
Plugins are registered via [project.entry-points] in your package's pyproject.toml:
[project.entry-points."optopsy.strategies"]
my_strategy = "my_package:register_strategies"
[project.entry-points."optopsy.signals"]
my_signals = "my_package:register_signals"
[project.entry-points."optopsy.providers"]
my_provider = "my_package:MyProvider"
[project.entry-points."optopsy.tools"]
my_tools = "my_package:register_tools"
[project.entry-points."optopsy.auth"]
my_auth = "my_package:register_auth"
Strategy Plugins¶
Each entry point must resolve to a callable (registrar) that returns a dict:
def register_strategies():
return {
"my_custom_spread": (
my_custom_spread_func, # The strategy function
"My custom spread strategy", # Description
False, # is_calendar (True for calendar/diagonal)
None, # option_type: "call", "put", or None
),
}
The strategy function should follow the same signature as built-in strategies (accept a DataFrame and **kwargs).
Signal Plugins¶
Each entry point must resolve to a callable returning a dict of signal factory lambdas:
The shape matches the internal SIGNAL_REGISTRY used by the Chat UI.
Provider Plugins¶
Each entry point must resolve to a DataProvider subclass (the class itself, not an instance):
from optopsy.ui.providers.base import DataProvider
class MyProvider(DataProvider):
name = "my_provider"
env_key = "MY_PROVIDER_API_KEY"
def get_tool_schemas(self): ...
def get_tool_names(self): ...
async def execute(self, tool_name, arguments): ...
The provider is auto-detected if its env_key environment variable is set.
Tool Plugins¶
Each entry point must resolve to a callable returning:
def register_tools():
return {
"schemas": [
# OpenAI-compatible tool schema dicts
{"type": "function", "function": {"name": "my_tool", ...}},
],
"handlers": {"my_tool": handler_callable},
"models": {"my_tool": PydanticModel},
"descriptions": {"my_tool": "Description of my tool"},
}
Auth Plugins¶
Each entry point must resolve to a callable returning an auth configuration dict:
def register_auth():
return {
"type": "password", # "password", "oauth", or "header"
"callback": my_auth_callback,
}
Only the first discovered auth plugin is used.
Auth Types¶
| Type | Callback Signature |
|---|---|
password |
(username: str, password: str) -> cl.User \| None |
oauth |
(provider_id, token, raw_user_data, default_user, id_token) -> cl.User \| None |
header |
(headers) -> cl.User \| None |
When no auth plugin is installed, the Chat UI defaults to local header-based auto-auth for single-user access.