This commit is contained in:
Oleg Sheynin
2026-01-12 21:26:15 +00:00
parent bd6cf1d4d0
commit c0fabcb429
15 changed files with 1205 additions and 568 deletions
+49 -27
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
from typing import Callable, Coroutine, List
import asyncio
from typing import Callable, Coroutine, Dict, List
import aiohttp.web as web
from cvttpy_tools.app import App
@@ -16,9 +17,7 @@ from cvttpy_trading.trading.instrument import ExchangeInstrument
from cvttpy_trading.trading.mkt_data.md_summary import MdTradesAggregate
from cvttpy_trading.trading.exchange_config import ExchangeAccounts
# ---
from pairs_trading.lib.pt_strategy.live.live_strategy import PtLiveStrategy
from pairs_trading.lib.live.mkt_data_client import CvttRestMktDataClient
from pairs_trading.lib.live.ti_sender import TradingInstructionsSender
'''
config http://cloud16.cvtt.vpn/apps/pairs_trading
@@ -27,27 +26,36 @@ config http://cloud16.cvtt.vpn/apps/pairs_trading
HistMdCbT = Callable[[List[MdTradesAggregate]], Coroutine]
UpdateMdCbT = Callable[[MdTradesAggregate], Coroutine]
class PairsTrader(NamedObject):
class PairTrader(NamedObject):
config_: CvttAppConfig
instruments_: List[ExchangeInstrument]
book_id_: BookIdT
live_strategy_: PtLiveStrategy
live_strategy_: "PtLiveStrategy" #type: ignore
ti_sender_: "TradingInstructionsSender" #type: ignore
pricer_client_: CvttRestMktDataClient
ti_sender_: TradingInstructionsSender
rest_service_: RestService
latest_history_: Dict[ExchangeInstrument, List[MdTradesAggregate]]
def __init__(self) -> None:
self.instruments_ = []
self.latest_history_ = {}
App.instance().add_cmdline_arg(
"--pair",
"--instrument_A",
type=str,
required=True,
help=(
"Comma-separated pair of instrument symbols"
" with exchange config name"
" (e.g., PAIR-BTC-USD:BNBSPOT,PAIR-ETH-USD:BNBSPOT)"
" Instrument A in pair (e.g., COINBASE_AT:PAIR-BTC-USD)"
),
)
App.instance().add_cmdline_arg(
"--instrument_B",
type=str,
required=True,
help=(
" Instrument B in pair (e.g., COINBASE_AT:PAIR-ETH-USD)"
),
)
@@ -65,21 +73,21 @@ class PairsTrader(NamedObject):
self.book_id_ = App.instance().get_argument(name="book_id")
# ------- PARSE INSTRUMENTS -------
instr_str = App.instance().get_argument("pair", "")
if not instr_str:
raise ValueError("Pair is required")
instr_list = instr_str.split(",")
assert len(instr_list) == 2, "Only two instruments are supported"
instr_list: List[str] = []
instr_str = App.instance().get_argument("instrument_A", "")
assert instr_str != "", "Missing insrument A"
instr_list.append(instr_str)
instr_str = App.instance().get_argument("instrument_B", "")
assert instr_str != "", "Missing insrument B"
instr_list.append(instr_str)
for instr in instr_list:
instr_parts = instr.split(":")
if len(instr_parts) != 2:
raise ValueError(f"Invalid pair format: {instr}")
instrument_id = instr_parts[0]
exch_acct = instr_parts[1]
exch_acct = instr_parts[0]
instrument_id = instr_parts[1]
exch_inst = ExchangeAccounts.instance().get_exchange_instrument(exch_acct=exch_acct, instrument_id=instrument_id)
assert exch_inst is not None, f"No ExchangeInstrument for {instr}"
exch_inst.user_data_["exch_acct"] = exch_acct
self.instruments_.append(exch_inst)
@@ -87,20 +95,24 @@ class PairsTrader(NamedObject):
Log.info(f"{self.fname()} Instruments: {self.instruments_[0].details_short()} <==> {self.instruments_[1].details_short()}")
# ------- CREATE STRATEGY -------
strategy_config = self.config_.get_subconfig("strategy_config", Config({}))
from pairs_trading.lib.pt_strategy.live.live_strategy import PtLiveStrategy
strategy_config = CvttAppConfig.instance() #self.config_.get_subconfig("strategy_config", Config({}))
self.live_strategy_ = PtLiveStrategy(
config=strategy_config,
pairs_trader=self,
)
Log.info(f"{self.fname()} Strategy created: {self.live_strategy_}")
model_name = self.config_.get_value("model/name", "?model/name?")
self.config_.set_value("strategy_id", f"{self.live_strategy_.__class__.__name__}:{model_name}")
# # ------- CREATE PRICER CLIENT -------
self.pricer_client_ = CvttRestMktDataClient(config=self.config_)
Log.info(f"{self.fname()} MD client created: {self.pricer_client_}")
# ------- CREATE TRADER CLIENT -------
from pairs_trading.lib.live.ti_sender import TradingInstructionsSender
self.ti_sender_ = TradingInstructionsSender(config=self.config_, pairs_trader=self)
Log.info(f"{self.fname()} TI sebder created: {self.ti_sender_}")
Log.info(f"{self.fname()} TI sender created: {self.ti_sender_}")
# # ------- CREATE REST SERVER -------
self.rest_service_ = RestService(
@@ -115,6 +127,7 @@ class PairsTrader(NamedObject):
)
async def subscribe_md(self) -> None:
from functools import partial
for exch_inst in self.instruments_:
exch_acct = exch_inst.user_data_.get("exch_acct", "?exch_acct?")
instrument_id = exch_inst.instrument_id()
@@ -124,12 +137,19 @@ class PairsTrader(NamedObject):
instrument_id=instrument_id,
interval_sec=self.live_strategy_.interval_sec(),
history_depth_sec=self.live_strategy_.history_depth_sec(),
callback=self._on_md_summary
callback=partial(self._on_md_summary, exch_inst=exch_inst)
)
async def _on_md_summary(self, history: List[MdTradesAggregate]) -> None:
# Snapshot or update?
await self.live_strategy_.on_mkt_data_hist_snapshot(hist_aggr=history)
async def _on_md_summary(self, history: List[MdTradesAggregate], exch_inst: ExchangeInstrument) -> None:
# URGENT before calling stragegy, make sure that **BOTH** instruments market data is combined.
Log.info(f"DEBUG got {exch_inst.details_short()} data")
self.latest_history_[exch_inst] = history
if len(self.latest_history_) == 2:
from itertools import chain
all_aggrs = sorted(list(chain.from_iterable(self.latest_history_.values())), key=lambda X: X.time_ns_)
await self.live_strategy_.on_mkt_data_hist_snapshot(hist_aggr=all_aggrs)
self.latest_history_ = {}
async def _on_api_request(self, request: web.Request) -> web.Response:
# TODO choose pair
@@ -139,10 +159,12 @@ class PairsTrader(NamedObject):
async def run(self) -> None:
Log.info(f"{self.fname()} ...")
while True:
await asyncio.sleep(0.1)
pass
if __name__ == "__main__":
App()
CvttAppConfig()
PairsTrader()
PairTrader()
App.instance().run()