progress
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pandas as pd
|
||||
from cvttpy_base.settings.cvtt_types import JsonDictT
|
||||
from cvttpy_base.tools.base import NamedObject
|
||||
from cvttpy_base.tools.logger import Log
|
||||
|
||||
from cvtt_client.mkt_data import CvttPricerWebSockClient, CvttPricesSubscription, MessageTypeT, SubscriptionIdT
|
||||
from pt_strategy.model_data_policy import ModelDataPolicy
|
||||
from pt_strategy.pt_market_data import PtMarketData, RealTimeMarketData
|
||||
from pt_strategy.pt_model import Prediction
|
||||
from pt_strategy.trading_pair import PairState, TradingPair
|
||||
|
||||
'''
|
||||
--config=pair.cfg
|
||||
--pair=PAIR-BTC-USDT:COINBASE_AT,PAIR-ETH-USDT:COINBASE_AT
|
||||
'''
|
||||
|
||||
|
||||
class PtMktDataClient(NamedObject):
|
||||
live_strategy_: PtLiveStrategy
|
||||
pricer_client_: CvttPricerWebSockClient
|
||||
subscriptions_: List[CvttPricesSubscription]
|
||||
|
||||
def __init__(self, live_strategy: PtLiveStrategy):
|
||||
self.live_strategy_ = live_strategy
|
||||
|
||||
async def start(self, subscription: CvttPricesSubscription) -> None:
|
||||
pricer_url = self.live_strategy_.config_.get("pricer_url", None) #, "ws://localhost:12346/ws")
|
||||
assert pricer_url is not None, "pricer_url is not found in config"
|
||||
self.pricer_client_ = CvttPricerWebSockClient(url=pricer_url)
|
||||
|
||||
await self._subscribe()
|
||||
|
||||
async def _subscribe(self) -> None:
|
||||
pair: TradingPair = self.live_strategy_.trading_pair_
|
||||
for instrument in pair.instruments_:
|
||||
await self.pricer_client_.subscribe(CvttPricesSubscription(
|
||||
exchange_config_name=instrument["exchange_config_name"],
|
||||
instrument_id=instrument["instrument_id"],
|
||||
interval_sec=60,
|
||||
history_depth_sec=60*60*24,
|
||||
callback=partial(self.on_message, instrument_id=instrument["instrument_id"])
|
||||
))
|
||||
|
||||
async def on_message(self, message_type: MessageTypeT, subscr_id: SubscriptionIdT, message: Dict, instrument_id: str) -> None:
|
||||
Log.info(f"{self.fname()}: {message_type=} {subscr_id=} {instrument_id}")
|
||||
aggr: JsonDictT
|
||||
if message_type == "md_aggregate":
|
||||
aggr = message.get("md_aggregate", {})
|
||||
await self.live_strategy_.on_mkt_data_update(aggr)
|
||||
# print(f"[{aggr['tstmp'][:19]}] *** RLTM *** {message}")
|
||||
elif message_type == "historical_md_aggregate":
|
||||
aggr = message.get("historical_data", {})
|
||||
await self.live_strategy_.on_mkt_data_hist_snapshot(aggr)
|
||||
# print(f"[{aggr['tstmp'][:19]}] *** HIST *** {aggr}")
|
||||
else:
|
||||
Log.info(f"Unknown message type: {message_type}")
|
||||
|
||||
async def run(self) -> None:
|
||||
await self.pricer_client_.run()
|
||||
|
||||
|
||||
|
||||
class PtLiveStrategy(NamedObject):
|
||||
config_: Dict[str, Any]
|
||||
trading_pair_: TradingPair
|
||||
model_data_policy_: ModelDataPolicy
|
||||
pt_mkt_data_: RealTimeMarketData
|
||||
pt_mkt_data_client_: PtMktDataClient
|
||||
|
||||
# for presentation: history of prediction values and trading signals
|
||||
predictions_: pd.DataFrame
|
||||
trading_signals_: pd.DataFrame
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Dict[str, Any],
|
||||
instruments: List[Dict[str, str]],
|
||||
):
|
||||
|
||||
self.config_ = config
|
||||
self.trading_pair_ = TradingPair(config=config, instruments=instruments)
|
||||
self.predictions_ = pd.DataFrame()
|
||||
self.trading_signals_ = pd.DataFrame()
|
||||
|
||||
import copy
|
||||
|
||||
# modified config must be passed to PtMarketData
|
||||
config_copy = copy.deepcopy(config)
|
||||
config_copy["instruments"] = instruments
|
||||
self.pt_mkt_data_ = RealTimeMarketData(config=config_copy)
|
||||
self.model_data_policy_ = ModelDataPolicy.create(
|
||||
config, is_real_time=True,pair=self.trading_pair_
|
||||
)
|
||||
|
||||
async def on_mkt_data_hist_snapshot(self, aggr: JsonDictT) -> None:
|
||||
Log.info(f"on_mkt_data_hist_snapshot: {aggr}")
|
||||
await self.pt_mkt_data_.on_mkt_data_hist_snapshot(snapshot=aggr)
|
||||
pass
|
||||
|
||||
async def on_mkt_data_update(self, aggr: JsonDictT) -> None:
|
||||
market_data_df = await self.pt_mkt_data_.on_mkt_data_update(update=aggr)
|
||||
if market_data_df is not None:
|
||||
self.trading_pair_.market_data_ = market_data_df
|
||||
self.model_data_policy_.advance()
|
||||
prediction = self.trading_pair_.run(market_data_df, self.model_data_policy_.advance())
|
||||
self.predictions_ = pd.concat([self.predictions_, prediction.to_df()], ignore_index=True)
|
||||
trades = self._create_trades(prediction=prediction, last_row=market_data_df.iloc[-1])
|
||||
# URGENT implement this
|
||||
pass
|
||||
|
||||
|
||||
async def run(self) -> None:
|
||||
await self.pt_mkt_data_client_.run()
|
||||
|
||||
def _create_trades(
|
||||
self, prediction: Prediction, last_row: pd.Series
|
||||
) -> Optional[pd.DataFrame]:
|
||||
pair = self.trading_pair_
|
||||
trades = None
|
||||
|
||||
open_threshold = self.config_["dis-equilibrium_open_trshld"]
|
||||
close_threshold = self.config_["dis-equilibrium_close_trshld"]
|
||||
scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
abs_scaled_disequilibrium = abs(scaled_disequilibrium)
|
||||
|
||||
if pair.user_data_["state"] in [
|
||||
PairState.INITIAL,
|
||||
PairState.CLOSE,
|
||||
PairState.CLOSE_POSITION,
|
||||
PairState.CLOSE_STOP_LOSS,
|
||||
PairState.CLOSE_STOP_PROFIT,
|
||||
]:
|
||||
if abs_scaled_disequilibrium >= open_threshold:
|
||||
trades = self._create_open_trades(
|
||||
pair, row=last_row, prediction=prediction
|
||||
)
|
||||
if trades is not None:
|
||||
trades["status"] = PairState.OPEN.name
|
||||
print(f"OPEN TRADES:\n{trades}")
|
||||
pair.user_data_["state"] = PairState.OPEN
|
||||
pair.on_open_trades(trades)
|
||||
|
||||
elif pair.user_data_["state"] == PairState.OPEN:
|
||||
if abs_scaled_disequilibrium <= close_threshold:
|
||||
trades = self._create_close_trades(
|
||||
pair, row=last_row, prediction=prediction
|
||||
)
|
||||
if trades is not None:
|
||||
trades["status"] = PairState.CLOSE.name
|
||||
print(f"CLOSE TRADES:\n{trades}")
|
||||
pair.user_data_["state"] = PairState.CLOSE
|
||||
pair.on_close_trades(trades)
|
||||
elif pair.to_stop_close_conditions(predicted_row=last_row):
|
||||
trades = self._create_close_trades(pair, row=last_row)
|
||||
if trades is not None:
|
||||
trades["status"] = pair.user_data_["stop_close_state"].name
|
||||
print(f"STOP CLOSE TRADES:\n{trades}")
|
||||
pair.user_data_["state"] = pair.user_data_["stop_close_state"]
|
||||
pair.on_close_trades(trades)
|
||||
|
||||
return trades
|
||||
|
||||
def _handle_outstanding_positions(self) -> Optional[pd.DataFrame]:
|
||||
trades = None
|
||||
pair = self.trading_pair_
|
||||
|
||||
# Outstanding positions
|
||||
if pair.user_data_["state"] == PairState.OPEN:
|
||||
print(f"{pair}: *** Position is NOT CLOSED. ***")
|
||||
# outstanding positions
|
||||
if self.config_["close_outstanding_positions"]:
|
||||
close_position_row = pd.Series(pair.market_data_.iloc[-2])
|
||||
# close_position_row["disequilibrium"] = 0.0
|
||||
# close_position_row["scaled_disequilibrium"] = 0.0
|
||||
# close_position_row["signed_scaled_disequilibrium"] = 0.0
|
||||
|
||||
trades = self._create_close_trades(
|
||||
pair=pair, row=close_position_row, prediction=None
|
||||
)
|
||||
if trades is not None:
|
||||
trades["status"] = PairState.CLOSE_POSITION.name
|
||||
print(f"CLOSE_POSITION TRADES:\n{trades}")
|
||||
pair.user_data_["state"] = PairState.CLOSE_POSITION
|
||||
pair.on_close_trades(trades)
|
||||
else:
|
||||
pair.add_outstanding_position(
|
||||
symbol=pair.symbol_a_,
|
||||
open_side=pair.user_data_["open_side_a"],
|
||||
open_px=pair.user_data_["open_px_a"],
|
||||
open_tstamp=pair.user_data_["open_tstamp"],
|
||||
last_mkt_data_row=pair.market_data_.iloc[-1],
|
||||
)
|
||||
pair.add_outstanding_position(
|
||||
symbol=pair.symbol_b_,
|
||||
open_side=pair.user_data_["open_side_b"],
|
||||
open_px=pair.user_data_["open_px_b"],
|
||||
open_tstamp=pair.user_data_["open_tstamp"],
|
||||
last_mkt_data_row=pair.market_data_.iloc[-1],
|
||||
)
|
||||
return trades
|
||||
|
||||
def _trades_df(self) -> pd.DataFrame:
|
||||
types = {
|
||||
"time": "datetime64[ns]",
|
||||
"action": "string",
|
||||
"symbol": "string",
|
||||
"side": "string",
|
||||
"price": "float64",
|
||||
"disequilibrium": "float64",
|
||||
"scaled_disequilibrium": "float64",
|
||||
"signed_scaled_disequilibrium": "float64",
|
||||
# "pair": "object",
|
||||
}
|
||||
columns = list(types.keys())
|
||||
return pd.DataFrame(columns=columns).astype(types)
|
||||
|
||||
def _create_open_trades(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Prediction
|
||||
) -> Optional[pd.DataFrame]:
|
||||
colname_a, colname_b = pair.exec_prices_colnames()
|
||||
|
||||
tstamp = row["tstamp"]
|
||||
diseqlbrm = prediction.disequilibrium_
|
||||
scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
px_a = row[f"{colname_a}"]
|
||||
px_b = row[f"{colname_b}"]
|
||||
|
||||
# creating the trades
|
||||
df = self._trades_df()
|
||||
|
||||
print(f"OPEN_TRADES: {row["tstamp"]} {scaled_disequilibrium=}")
|
||||
if diseqlbrm > 0:
|
||||
side_a = "SELL"
|
||||
side_b = "BUY"
|
||||
else:
|
||||
side_a = "BUY"
|
||||
side_b = "SELL"
|
||||
|
||||
# save closing sides
|
||||
pair.user_data_["open_side_a"] = side_a # used in oustanding positions
|
||||
pair.user_data_["open_side_b"] = side_b
|
||||
pair.user_data_["open_px_a"] = px_a
|
||||
pair.user_data_["open_px_b"] = px_b
|
||||
pair.user_data_["open_tstamp"] = tstamp
|
||||
|
||||
pair.user_data_["close_side_a"] = side_b # used for closing trades
|
||||
pair.user_data_["close_side_b"] = side_a
|
||||
|
||||
# create opening trades
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_a_,
|
||||
"side": side_a,
|
||||
"action": "OPEN",
|
||||
"price": px_a,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"signed_scaled_disequilibrium": scaled_disequilibrium,
|
||||
"scaled_disequilibrium": abs(scaled_disequilibrium),
|
||||
# "pair": pair,
|
||||
}
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_b_,
|
||||
"side": side_b,
|
||||
"action": "OPEN",
|
||||
"price": px_b,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"scaled_disequilibrium": abs(scaled_disequilibrium),
|
||||
"signed_scaled_disequilibrium": scaled_disequilibrium,
|
||||
# "pair": pair,
|
||||
}
|
||||
return df
|
||||
|
||||
def _create_close_trades(
|
||||
self, pair: TradingPair, row: pd.Series, prediction: Optional[Prediction] = None
|
||||
) -> Optional[pd.DataFrame]:
|
||||
colname_a, colname_b = pair.exec_prices_colnames()
|
||||
|
||||
tstamp = row["tstamp"]
|
||||
if prediction is not None:
|
||||
diseqlbrm = prediction.disequilibrium_
|
||||
signed_scaled_disequilibrium = prediction.scaled_disequilibrium_
|
||||
scaled_disequilibrium = abs(prediction.scaled_disequilibrium_)
|
||||
else:
|
||||
diseqlbrm = 0.0
|
||||
signed_scaled_disequilibrium = 0.0
|
||||
scaled_disequilibrium = 0.0
|
||||
px_a = row[f"{colname_a}"]
|
||||
px_b = row[f"{colname_b}"]
|
||||
|
||||
# creating the trades
|
||||
df = self._trades_df()
|
||||
|
||||
# create opening trades
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_a_,
|
||||
"side": pair.user_data_["close_side_a"],
|
||||
"action": "CLOSE",
|
||||
"price": px_a,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"scaled_disequilibrium": scaled_disequilibrium,
|
||||
"signed_scaled_disequilibrium": signed_scaled_disequilibrium,
|
||||
# "pair": pair,
|
||||
}
|
||||
df.loc[len(df)] = {
|
||||
"time": tstamp,
|
||||
"symbol": pair.symbol_b_,
|
||||
"side": pair.user_data_["close_side_b"],
|
||||
"action": "CLOSE",
|
||||
"price": px_b,
|
||||
"disequilibrium": diseqlbrm,
|
||||
"scaled_disequilibrium": scaled_disequilibrium,
|
||||
"signed_scaled_disequilibrium": signed_scaled_disequilibrium,
|
||||
# "pair": pair,
|
||||
}
|
||||
del pair.user_data_["close_side_a"]
|
||||
del pair.user_data_["close_side_b"]
|
||||
|
||||
del pair.user_data_["open_tstamp"]
|
||||
del pair.user_data_["open_px_a"]
|
||||
del pair.user_data_["open_px_b"]
|
||||
del pair.user_data_["open_side_a"]
|
||||
del pair.user_data_["open_side_b"]
|
||||
return df
|
||||
|
||||
Reference in New Issue
Block a user