Compare commits

..

No commits in common. "ba2a6cd2ebb2b84fa02dd35314fcde8171604141" and "e97f76222cb7b0090407d02a216321fbbfbc3769" have entirely different histories.

6 changed files with 117 additions and 61 deletions

View File

@ -15,6 +15,9 @@
], ],
"python.envFile": "${workspaceFolder}/.env", "python.envFile": "${workspaceFolder}/.env",
"python.testing.debugPort": 3000, "python.testing.debugPort": 3000,
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.mypyEnabled": true,
"files.associations": { "files.associations": {
"*.py": "python" "*.py": "python"
}, },

View File

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio from typing import Dict, Any, List, Optional
from typing import Callable, Dict, Any, List, Optional
import time import time
import requests import requests
@ -64,6 +63,7 @@ class RESTSender(NamedObject):
f"Failed to send status={excpt.response.status_code} {excpt.response.text}" # type: ignore f"Failed to send status={excpt.response.status_code} {excpt.response.text}" # type: ignore
) from excpt ) from excpt
class MdSummary(HistMdBar): class MdSummary(HistMdBar):
def __init__( def __init__(
self, self,
@ -105,7 +105,6 @@ class MdSummary(HistMdBar):
) )
return res return res
MdSummaryCallbackT = Callable[[List[MdSummary]], None]
class MdSummaryCollector(NamedObject): class MdSummaryCollector(NamedObject):
sender_: RESTSender sender_: RESTSender
@ -115,9 +114,8 @@ class MdSummaryCollector(NamedObject):
history_depth_sec_: int history_depth_sec_: int
history_: List[MdSummary] history_: List[MdSummary]
callbacks_: List[MdSummaryCallbackT]
timer_: Optional[Timer] timer_: Optional[Timer]
def __init__( def __init__(
self, self,
sender: RESTSender, sender: RESTSender,
@ -132,12 +130,8 @@ class MdSummaryCollector(NamedObject):
self.interval_sec_ = interval_sec self.interval_sec_ = interval_sec
self.history_depth_sec_ = history_depth_sec self.history_depth_sec_ = history_depth_sec
self.history_ = [] self.history_depth_sec_ = []
self.callbacks_ = []
self.timer_ = None self.timer_ = None
def add_callback(self, cb: MdSummaryCallbackT) -> None:
self.callbacks_.append(cb)
def rqst_data(self) -> Dict[str, Any]: def rqst_data(self) -> Dict[str, Any]:
return { return {
@ -151,32 +145,22 @@ class MdSummaryCollector(NamedObject):
response: requests.Response = self.sender_.send_post( response: requests.Response = self.sender_.send_post(
endpoint="md_summary", post_body=self.rqst_data() endpoint="md_summary", post_body=self.rqst_data()
) )
if response.status_code not in (200, 201):
Log.error(f"{self.fname()}: Received error: {response.status_code} - {response.text}")
return []
return MdSummary.from_REST_response(response=response) return MdSummary.from_REST_response(response=response)
def get_last(self) -> Optional[MdSummary]: def get_last(self) -> Optional[MdSummary]:
rqst_data = self.rqst_data() rqst_data = self.rqst_data()
rqst_data["history_depth_sec"] = self.interval_sec_ * 2 rqst_data["history_depth_sec"] = self.interval_sec_
response: requests.Response = self.sender_.send_post( response: requests.Response = self.sender_.send_post(
endpoint="md_summary", post_body=rqst_data endpoint="md_summary", post_body=rqst_data
) )
if response.status_code not in (200, 201):
Log.error(f"{self.fname()}: Received error: {response.status_code} - {response.text}")
return None
res = MdSummary.from_REST_response(response=response) res = MdSummary.from_REST_response(response=response)
return None if len(res) == 0 else res[-1] return None if len(res) == 0 else res[-1]
def is_empty(self) -> bool:
return len(self.history_) == 0
async def start(self) -> None: async def start(self) -> None:
if self.timer_: if self.timer_:
Log.error(f"{self.fname()}: Timer is already started") Log.error(f"{self.fname()}: Timer is already started")
return return
self.history_ = self.get_history() self.history_ = self.get_history()
self.run_callbacks()
self.timer_ = Timer( self.timer_ = Timer(
start_in_sec=self.interval_sec_, start_in_sec=self.interval_sec_,
is_periodic=True, is_periodic=True,
@ -185,19 +169,15 @@ class MdSummaryCollector(NamedObject):
) )
async def _load_new(self) -> None: async def _load_new(self) -> None:
last: Optional[MdSummary] = self.get_last() last: Optional[MdSummary] = self.get_last()
if not last: if not last:
Log.warning(f"{self.fname()}: did not get last update") # URGENT logging
return return
if not self.is_empty() and last.ts_ns_ <= self.history_[-1].ts_ns_: if last.ts_ns_ <= self.history_[-1].ts_ns_:
Log.info(f"{self.fname()}: Received {last}. Already Have: {self.history_[-1]}") # URGENT logging
return return
self.history_.append(last) self.history_.append(last)
self.run_callbacks() # URGENT implement notification
def run_callbacks(self) -> None:
[cb(self.history_) for cb in self.callbacks_]
def stop(self) -> None: def stop(self) -> None:
if self.timer_: if self.timer_:
@ -217,7 +197,6 @@ class CvttRESTClient(NamedObject):
if __name__ == "__main__": if __name__ == "__main__":
config = Config(json_src={"cvtt_base_url": "http://cvtt-tester-01.cvtt.vpn:23456"}) config = Config(json_src={"cvtt_base_url": "http://cvtt-tester-01.cvtt.vpn:23456"})
# config = Config(json_src={"cvtt_base_url": "http://dev-server-02.cvtt.vpn:23456"})
cvtt_client = CvttRESTClient(config) cvtt_client = CvttRESTClient(config)
@ -229,16 +208,6 @@ if __name__ == "__main__":
history_depth_sec=24 * 3600, history_depth_sec=24 * 3600,
) )
def _calback(history: List[MdSummary]) -> None: hist = mdsc.get_history()
Log.info(f"MdSummary Hist Length is {len(history)}. Last summary: {history[-1] if len(history) > 0 else '[]'}") last = mdsc.get_last()
mdsc.add_callback(_calback)
async def __run() -> None:
Log.info("Starting...")
await mdsc.start()
while True:
await asyncio.sleep(5)
asyncio.run(__run())
pass pass

View File

@ -12,11 +12,11 @@ from cvttpy_tools.settings.cvtt_types import JsonDictT
# --- # ---
from cvttpy_trading.trading.instrument import ExchangeInstrument from cvttpy_trading.trading.instrument import ExchangeInstrument
# --- # ---
from pairs_trading.lib.pt_strategy.live.ti_sender import TradingInstructionsSender from pt_strategy.live.ti_sender import TradingInstructionsSender
from pairs_trading.lib.pt_strategy.model_data_policy import ModelDataPolicy from pt_strategy.model_data_policy import ModelDataPolicy
from pairs_trading.lib.pt_strategy.pt_market_data import RealTimeMarketData from pt_strategy.pt_market_data import RealTimeMarketData
from pairs_trading.lib.pt_strategy.pt_model import Prediction from pt_strategy.pt_model import Prediction
from pairs_trading.lib.pt_strategy.trading_pair import PairState, TradingPair from pt_strategy.trading_pair import PairState, TradingPair
""" """
--config=pair.cfg --config=pair.cfg
@ -104,9 +104,9 @@ class PtLiveStrategy(NamedObject):
pass pass
async def _send_trading_instructions( async def _send_trading_instructions(
self, trading_instructions: List[TradingInstruction] self, trading_instructions: pd.DataFrame
) -> None: ) -> None:
pass # URGENT implement _send_trading_instructions pass
def _create_trading_instructions( def _create_trading_instructions(
self, prediction: Prediction, last_row: pd.Series self, prediction: Prediction, last_row: pd.Series
@ -186,11 +186,6 @@ class PtLiveStrategy(NamedObject):
} }
return df return df
def _create_close_trade_instructions(
self, pair: TradingPair, row: pd.Series #, prediction: Prediction
) -> List[TradingInstruction]:
return [] # URGENT implement _create_close_trade_instructions
def _handle_outstanding_positions(self) -> Optional[pd.DataFrame]: def _handle_outstanding_positions(self) -> Optional[pd.DataFrame]:
trades = None trades = None
pair = self.trading_pair_ pair = self.trading_pair_

View File

@ -1,19 +1,18 @@
```python
from __future__ import annotations from __future__ import annotations
from functools import partial from functools import partial
from typing import Dict, List from typing import Dict, List
# from cvtt_client.mkt_data import (CvttPricerWebSockClient, from cvtt_client.mkt_data import (CvttPricerWebSockClient,
# CvttPricesSubscription, MessageTypeT, CvttPricesSubscription, MessageTypeT,
# SubscriptionIdT) SubscriptionIdT)
from cvttpy_tools.app import App from cvttpy_tools.app import App
from cvttpy_tools.base import NamedObject from cvttpy_tools.base import NamedObject
from cvttpy_tools.config import Config from cvttpy_tools.config import Config
from cvttpy_tools.logger import Log from cvttpy_tools.logger import Log
from cvttpy_tools.settings.cvtt_types import JsonDictT from cvttpy_tools.settings.cvtt_types import JsonDictT
from pairs_trading.lib.pt_strategy.live.live_strategy import PtLiveStrategy from pt_strategy.live.live_strategy import PtLiveStrategy
from pairs_trading.lib.pt_strategy.trading_pair import TradingPair from pt_strategy.trading_pair import TradingPair
""" """
--config=pair.cfg --config=pair.cfg
@ -84,4 +83,3 @@ class PtMktDataClient(NamedObject):
await self._subscribe() await self._subscribe()
await self.pricer_client_.run() await self.pricer_client_.run()
```

66
pyproject.toml Normal file
View File

@ -0,0 +1,66 @@
[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "pairs-trading"
version = "0.1.0"
description = "Pairs Trading Backtesting Framework"
requires-python = ">=3.8"
[tool.black]
line-length = 88
target-version = ['py38']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
)/
'''
[tool.flake8]
max-line-length = 88
extend-ignore = ["E203", "W503"]
exclude = [
".git",
"__pycache__",
"build",
"dist",
".venv",
".mypy_cache",
".tox"
]
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
[[tool.mypy.overrides]]
module = [
"numpy.*",
"pandas.*",
"matplotlib.*",
"seaborn.*",
"scipy.*",
"sklearn.*"
]
ignore_missing_imports = true

25
pyrightconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"include": [
"lib"
],
"exclude": [
"**/node_modules",
"**/__pycache__",
"**/.*",
"results",
"data"
],
"ignore": [],
"defineConstant": {},
"typeCheckingMode": "basic",
"useLibraryCodeForTypes": true,
"autoImportCompletions": true,
"autoSearchPaths": true,
"extraPaths": [
"lib",
".."
],
"stubPath": "./typings",
"venvPath": ".",
"venv": "python3.12-venv"
}