def test_index_single_data(self): # Auto broadcast for scalar sd = idd.SingleData(0, index=["foo", "bar"]) print(sd) # Support empty value sd = idd.SingleData() print(sd) # Bad case: the input is not aligned with self.assertRaises(ValueError): idd.SingleData(range(10), index=["foo", "bar"]) # test indexing sd = idd.SingleData([1, 2, 3, 4], index=["foo", "bar", "f", "g"]) print(sd) print(sd.iloc[1]) # get second row # Bad case: it is not in the index with self.assertRaises(KeyError): print(sd.loc[1]) print(sd.loc["foo"]) # Test slicing print(sd.loc[:"bar"]) print(sd.iloc[:3])
def test_ops(self): sd1 = idd.SingleData([1, 2, 3, 4], index=["foo", "bar", "f", "g"]) sd2 = idd.SingleData([1, 2, 3, 4], index=["foo", "bar", "f", "g"]) print(sd1 + sd2) new_sd = sd2 * 2 self.assertTrue(new_sd.index == sd2.index) sd1 = idd.SingleData([1, 2, None, 4], index=["foo", "bar", "f", "g"]) sd2 = idd.SingleData([1, 2, 3, None], index=["foo", "bar", "f", "g"]) self.assertTrue(np.isnan((sd1 + sd2).iloc[3])) self.assertTrue(sd1.add(sd2).sum() == 13) self.assertTrue(idd.sum_by_index([sd1, sd2], sd1.index, fill_value=0.0).sum() == 13)
def test_squeeze(self): sd1 = idd.SingleData([1, 2, 3, 4], index=["foo", "bar", "f", "g"]) # automatically squeezing self.assertTrue(not isinstance(np.nansum(sd1), idd.IndexData)) self.assertTrue(not isinstance(np.sum(sd1), idd.IndexData)) self.assertTrue(not isinstance(sd1.sum(), idd.IndexData)) self.assertEqual(np.nansum(sd1), 10) self.assertEqual(np.sum(sd1), 10) self.assertEqual(sd1.sum(), 10) self.assertEqual(np.nanmean(sd1), 2.5) self.assertEqual(np.mean(sd1), 2.5) self.assertEqual(sd1.mean(), 2.5)
def test_corner_cases(self): sd = idd.MultiData([[1, 2], [3, np.NaN]], index=["foo", "bar"], columns=["f", "g"]) print(sd) self.assertTrue(np.isnan(sd.loc["bar", "g"])) # support slicing print(sd.loc[~sd.loc[:, "g"].isna().data.astype(np.bool)]) print( self.assertTrue(idd.SingleData().index == idd.SingleData().index)) # empty dict print(idd.SingleData({})) print(idd.SingleData(pd.Series())) sd = idd.SingleData() with self.assertRaises(KeyError): sd.loc["foo"] # replace sd = idd.SingleData([1, 2, 3, 4], index=["foo", "bar", "f", "g"]) sd = sd.replace(dict(zip(range(1, 5), range(2, 6)))) print(sd) self.assertTrue(sd.iloc[0] == 2)
def get_data(self, stock_id, start_time, end_time, field, method=None): if method == "ts_data_last": method = ts_data_last stock_data = resam_ts_data(self.data[stock_id][field], start_time, end_time, method=method) if stock_data is None: return None elif isinstance(stock_data, (bool, np.bool_, int, float, np.number)): return stock_data elif isinstance(stock_data, pd.Series): return idd.SingleData(stock_data) else: raise ValueError( f"stock data from resam_ts_data must be a number, pd.Series or pd.DataFrame" )
def _agg_base_price( self, inner_order_indicators: List[Dict[str, Union[SingleMetric, idd.SingleData]]], decision_list: List[Tuple[BaseTradeDecision, pd.Timestamp, pd.Timestamp]], trade_exchange: Exchange, pa_config: dict = {}, ): """ # NOTE:!!!! # Strong assumption!!!!!! # the correctness of the base_price relies on that the **same** exchange is used Parameters ---------- inner_order_indicators : List[Dict[str, pd.Series]] the indicators of account of inner executor decision_list: List[Tuple[BaseTradeDecision, pd.Timestamp, pd.Timestamp]], a list of decisions according to inner_order_indicators trade_exchange : Exchange for retrieving trading price pa_config : dict For example { "agg": "twap", # "vwap" "price": "$close", # TODO: this is not supported now!!!!! # default to use deal price of the exchange } """ # TODO: I think there are potentials to be optimized trade_dir = self.order_indicator.get_index_data("trade_dir") if len(trade_dir) > 0: bp_all, bv_all = [], [] # <step, inst, (base_volume | base_price)> for oi, (dec, start, end) in zip(inner_order_indicators, decision_list): bp_s = oi.get_index_data("base_price").reindex(trade_dir.index) bv_s = oi.get_index_data("base_volume").reindex( trade_dir.index) bp_new, bv_new = {}, {} for pr, v, (inst, direction) in zip( bp_s.data, bv_s.data, zip(trade_dir.index, trade_dir.data)): if np.isnan(pr): bp_tmp, bv_tmp = self._get_base_vol_pri( inst, start, end, decision=dec, direction=direction, trade_exchange=trade_exchange, pa_config=pa_config, ) if (bp_tmp is not None) and (bv_tmp is not None): bp_new[inst], bv_new[inst] = bp_tmp, bv_tmp else: bp_new[inst], bv_new[inst] = pr, v bp_new = idd.SingleData(bp_new) bv_new = idd.SingleData(bv_new) bp_all.append(bp_new) bv_all.append(bv_new) bp_all = idd.concat(bp_all, axis=1) bv_all = idd.concat(bv_all, axis=1) base_volume = bv_all.sum(axis=1) self.order_indicator.assign("base_volume", base_volume.to_dict()) self.order_indicator.assign("base_price", ((bp_all * bv_all).sum(axis=1) / base_volume).to_dict())
def _get_base_vol_pri( self, inst: str, trade_start_time: pd.Timestamp, trade_end_time: pd.Timestamp, direction: OrderDir, decision: BaseTradeDecision, trade_exchange: Exchange, pa_config: dict = {}, ): """ Get the base volume and price information All the base price values are rooted from this function """ agg = pa_config.get("agg", "twap").lower() price = pa_config.get("price", "deal_price").lower() if decision.trade_range is not None: trade_start_time, trade_end_time = decision.trade_range.clip_time_range( start_time=trade_start_time, end_time=trade_end_time) if price == "deal_price": price_s = trade_exchange.get_deal_price(inst, trade_start_time, trade_end_time, direction=direction, method=None) else: raise NotImplementedError(f"This type of input is not supported") # if there is no stock data during the time period if price_s is None: return None, None if isinstance(price_s, (int, float, np.number)): price_s = idd.SingleData(price_s, [trade_start_time]) elif isinstance(price_s, idd.SingleData): pass else: raise NotImplementedError(f"This type of input is not supported") # NOTE: there are some zeros in the trading price. These cases are known meaningless # for aligning the previous logic, remove it. # remove zero and negative values. price_s = price_s.loc[(price_s > 1e-08).data.astype(np.bool)] # NOTE ~(price_s < 1e-08) is different from price_s >= 1e-8 # ~(np.NaN < 1e-8) -> ~(False) -> True if agg == "vwap": volume_s = trade_exchange.get_volume(inst, trade_start_time, trade_end_time, method=None) if isinstance(volume_s, (int, float, np.number)): volume_s = idd.SingleData(volume_s, [trade_start_time]) volume_s = volume_s.reindex(price_s.index) elif agg == "twap": volume_s = idd.SingleData(1, price_s.index) else: raise NotImplementedError(f"This type of input is not supported") base_volume = volume_s.sum() base_price = (price_s * volume_s).sum() / base_volume return base_price, base_volume
def get_index_data(self, metric: str) -> SingleData: if metric in self.data: return self.data[metric] else: return idd.SingleData()
def assign(self, col: str, metric: dict) -> None: self.data[col] = idd.SingleData(metric)
def get_index_data(self, metric): if metric in self.data: return idd.SingleData(self.data[metric].metric) else: return idd.SingleData()