HamSession commited on
Commit
b0cb774
·
verified ·
1 Parent(s): 5e77863

Add adjust_ohlcv.py

Browse files
Files changed (1) hide show
  1. adjust_ohlcv.py +39 -19
adjust_ohlcv.py CHANGED
@@ -1,7 +1,7 @@
1
  """Split/dividend adjustment for a user's raw 1-minute OHLCV bars.
2
 
3
- Reproduces the convention the model was trained under (see
4
- data_pull/data/adjustments.py and preprocessing/quality.py in the source repo):
5
 
6
  * factors are cumulative and backward-looking, anchored at 1.0 on the latest bar date;
7
  * a split with ex-date D multiplies price by denominator/numerator and volume by
@@ -9,7 +9,8 @@ data_pull/data/adjustments.py and preprocessing/quality.py in the source repo):
9
  * a cash dividend with ex-date D multiplies price by (close_prior - amount) / close_prior
10
  on every bar dated strictly before D, where close_prior is the regular-session close on
11
  the last bar date before D; volume is unchanged;
12
- * dividends with amount >= close_prior are skipped (price left under-adjusted).
 
13
 
14
  Only pandas and numpy are required.
15
  """
@@ -29,32 +30,43 @@ def clean_bars(bars: pd.DataFrame) -> pd.DataFrame:
29
  """Apply the training pipeline's raw-bar quality checks.
30
 
31
  Drops duplicate timestamps (keeps first), bars with high < low, bars whose close lies
32
- outside [low, high], and bars with a non-positive price; negative volume is set to 0.
 
33
  """
34
  df = bars.drop_duplicates(subset="timestamp", keep="first")
35
  ok = (df["high"] >= df["low"]) & (df["close"] <= df["high"]) & (df["close"] >= df["low"])
36
  ok &= (df[["open", "high", "low", "close"]] > 0).all(axis=1)
37
  df = df[ok.fillna(False)].copy()
38
- df["volume"] = df["volume"].clip(lower=0)
39
  return df.sort_values("timestamp").reset_index(drop=True)
40
 
41
 
42
  def _eastern(ts: pd.Series) -> pd.Series:
43
- ts = pd.to_datetime(ts)
 
 
 
 
 
44
  return ts.dt.tz_localize(EASTERN) if ts.dt.tz is None else ts.dt.tz_convert(EASTERN)
45
 
46
 
47
  def _split_events(splits: pd.DataFrame | None) -> dict[dt.date, tuple[float, float]]:
48
- """{ex_date: (numerator, denominator)}; accepts `ratio` (4.0 for 4:1, 0.5 for 1:2)."""
 
 
 
49
  if splits is None or len(splits) == 0:
50
  return {}
51
  s = splits.copy()
52
  if "numerator" not in s:
53
  s["numerator"], s["denominator"] = s["ratio"].astype(float), 1.0
54
- return {
55
- pd.Timestamp(d).date(): (float(n), float(m))
56
- for d, n, m in zip(s["date"], s["numerator"], s["denominator"], strict=True)
57
- }
 
 
58
 
59
 
60
  def _close_prior(df: pd.DataFrame, ex_date: dt.date) -> float | None:
@@ -67,14 +79,20 @@ def _close_prior(df: pd.DataFrame, ex_date: dt.date) -> float | None:
67
 
68
 
69
  def _dividend_events(df: pd.DataFrame, dividends: pd.DataFrame | None) -> dict[dt.date, tuple[float, float]]:
70
- """{ex_date: (amount, close_prior)}; close_prior is taken from the bars unless given."""
 
 
 
71
  if dividends is None or len(dividends) == 0:
72
  return {}
73
- events = {}
74
  given = dividends["close_prior"] if "close_prior" in dividends else [None] * len(dividends)
75
  for d, amount, cp in zip(dividends["date"], dividends["amount"], given, strict=True):
76
  ex_date = pd.Timestamp(d).date()
77
- close_prior = cp if cp is not None and not pd.isna(cp) else _close_prior(df, ex_date)
 
 
 
78
  if close_prior is not None and close_prior > 0:
79
  events[ex_date] = (float(amount), float(close_prior))
80
  return events
@@ -120,10 +138,12 @@ def adjust_ohlcv(
120
  """Return cleaned bars with adjusted prices ready for `make_features`.
121
 
122
  Args:
123
- bars: one security; columns `timestamp` (tz-aware, or naive = Eastern), `open`,
124
- `high`, `low`, `close`, `volume`, all unadjusted.
125
- splits: columns `date` (ex-date) and either `ratio` or `numerator`+`denominator`.
126
- dividends: columns `date` (ex-date), `amount`, optional `close_prior`.
 
 
127
 
128
  Returns:
129
  DataFrame with `timestamp_eastern`, `open_adj`, `high_adj`, `low_adj`, `close_adj`,
@@ -150,4 +170,4 @@ def adjust_ohlcv(
150
  "adj_factor_price",
151
  "adj_factor_volume",
152
  ]
153
- return df[keep].reset_index(drop=True)
 
1
  """Split/dividend adjustment for a user's raw 1-minute OHLCV bars.
2
 
3
+ Reproduces the convention the model was trained under (ported from the authors' training
4
+ pipeline):
5
 
6
  * factors are cumulative and backward-looking, anchored at 1.0 on the latest bar date;
7
  * a split with ex-date D multiplies price by denominator/numerator and volume by
 
9
  * a cash dividend with ex-date D multiplies price by (close_prior - amount) / close_prior
10
  on every bar dated strictly before D, where close_prior is the regular-session close on
11
  the last bar date before D; volume is unchanged;
12
+ * dividends with amount >= close_prior are skipped (price left under-adjusted);
13
+ * two actions on the same ex-date are combined: amounts summed, split ratios multiplied.
14
 
15
  Only pandas and numpy are required.
16
  """
 
30
  """Apply the training pipeline's raw-bar quality checks.
31
 
32
  Drops duplicate timestamps (keeps first), bars with high < low, bars whose close lies
33
+ outside [low, high], and bars with a non-positive or missing price; negative or
34
+ missing volume is set to 0.
35
  """
36
  df = bars.drop_duplicates(subset="timestamp", keep="first")
37
  ok = (df["high"] >= df["low"]) & (df["close"] <= df["high"]) & (df["close"] >= df["low"])
38
  ok &= (df[["open", "high", "low", "close"]] > 0).all(axis=1)
39
  df = df[ok.fillna(False)].copy()
40
+ df["volume"] = df["volume"].fillna(0).clip(lower=0)
41
  return df.sort_values("timestamp").reset_index(drop=True)
42
 
43
 
44
  def _eastern(ts: pd.Series) -> pd.Series:
45
+ """Parse if needed, then localize naive as Eastern or convert aware to Eastern."""
46
+ if not pd.api.types.is_datetime64_any_dtype(ts):
47
+ try:
48
+ ts = pd.to_datetime(ts)
49
+ except ValueError: # strings with mixed UTC offsets, e.g. across a DST change
50
+ ts = pd.to_datetime(ts, utc=True)
51
  return ts.dt.tz_localize(EASTERN) if ts.dt.tz is None else ts.dt.tz_convert(EASTERN)
52
 
53
 
54
  def _split_events(splits: pd.DataFrame | None) -> dict[dt.date, tuple[float, float]]:
55
+ """{ex_date: (numerator, denominator)}; accepts `ratio` (10.0 = 10-for-1, 0.5 = 1-for-2).
56
+
57
+ Same-date splits are multiplied together.
58
+ """
59
  if splits is None or len(splits) == 0:
60
  return {}
61
  s = splits.copy()
62
  if "numerator" not in s:
63
  s["numerator"], s["denominator"] = s["ratio"].astype(float), 1.0
64
+ events: dict[dt.date, tuple[float, float]] = {}
65
+ for d, n, m in zip(s["date"], s["numerator"], s["denominator"], strict=True):
66
+ ex_date = pd.Timestamp(d).date()
67
+ n0, m0 = events.get(ex_date, (1.0, 1.0))
68
+ events[ex_date] = (n0 * float(n), m0 * float(m))
69
+ return events
70
 
71
 
72
  def _close_prior(df: pd.DataFrame, ex_date: dt.date) -> float | None:
 
79
 
80
 
81
  def _dividend_events(df: pd.DataFrame, dividends: pd.DataFrame | None) -> dict[dt.date, tuple[float, float]]:
82
+ """{ex_date: (amount, close_prior)}; close_prior is taken from the bars unless given.
83
+
84
+ Same-date dividends are summed against one close_prior.
85
+ """
86
  if dividends is None or len(dividends) == 0:
87
  return {}
88
+ events: dict[dt.date, tuple[float, float]] = {}
89
  given = dividends["close_prior"] if "close_prior" in dividends else [None] * len(dividends)
90
  for d, amount, cp in zip(dividends["date"], dividends["amount"], given, strict=True):
91
  ex_date = pd.Timestamp(d).date()
92
+ if ex_date in events:
93
+ events[ex_date] = (events[ex_date][0] + float(amount), events[ex_date][1])
94
+ continue
95
+ close_prior = cp if not pd.isna(cp) else _close_prior(df, ex_date)
96
  if close_prior is not None and close_prior > 0:
97
  events[ex_date] = (float(amount), float(close_prior))
98
  return events
 
138
  """Return cleaned bars with adjusted prices ready for `make_features`.
139
 
140
  Args:
141
+ bars: one security; columns `timestamp` (bar start; tz-aware, or naive taken as
142
+ Eastern), `open`, `high`, `low`, `close`, `volume`, all unadjusted.
143
+ splits: columns `date` (ex-date) and either `ratio` or `numerator`+`denominator`
144
+ (numerator = shares after the split).
145
+ dividends: columns `date` (ex-date), `amount` (cash per share as declared),
146
+ optional `close_prior`.
147
 
148
  Returns:
149
  DataFrame with `timestamp_eastern`, `open_adj`, `high_adj`, `low_adj`, `close_adj`,
 
170
  "adj_factor_price",
171
  "adj_factor_volume",
172
  ]
173
+ return df[keep]