Skip to content

RemKamal/Awesome-LLM

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

1 ChatGPT You’re right: the whole game here is sign conventions. If you build P&L as -markout (flipped) instead of using the real economic direction (buy vs sell), you’re effectively pretending you traded the opposite way, which can flip the sign of correlation with SPY. Below is a simple, linear pipeline in pandas that: Starts from a forward markout (T→T+1 return).

Builds flipped reversion, P&L, and signed notional at child/position level.

Aggregates to daily gross/net notional and daily P&L.

Merges with SPY, builds SPY hedge P&L, and runs correlations.

All important column names are parameters at the top so you can rename as needed. import numpy as np import pandas as pd

=========================

1. Column name parameters

=========================

subset (per-position / per-child) columns

date_col = 'trade_date' # trading date for each position markout_col = 'markout_ret' # forward T->T+1 markout return for the stock flip_ret_col = 'flipped_reversion' # name for flipped reversion (=-markout) pnl_col = 'CAPT_facilitated_PNL' # name for strategy P&L column (per position) signed_ntnl_col = 'signed_CAPT_facilitated_NTNL' # signed notional (per position) gross_ntnl_col = 'CAPT_facilitated_NTNL' # gross notional (per position, abs) qty_col = 'exec_qty' # executed quantity for this child/position price_col = 'price' # price at which markout is measured (near close) is_buy_col = 'is_buy' # boolean: True for buy, False for sell

daily aggregates

daily_pnl_col = 'daily_strategy_PNL' daily_net_ntnl_col = 'daily_net_notional' daily_gross_ntnl_col= 'daily_gross_notional'

SPY dataframe columns

spy_date_col = 'trade_date' # date column in df_spy (align with subset) spy_price_col = 'ClosePrice' # SPY close price column spy_ret_col = 'SPY_ret' # name for SPY close-to-next-close return

=========================

2. Prepare per-position data

=========================

2.1 flipped reversion = -markout (this is your original "reversion" convention)

NOTE: if I want economically correct P&L, I should base it on markout_col.

subset[flip_ret_col] = -subset[markout_col]

2.2 trade side: +1 for buy, -1 for sell

This captures the actual direction of the position.

subset['side'] = np.where(subset[is_buy_col], 1.0, -1.0)

2.3 signed notional in dollars = side * qty * price

This is what I will hedge with SPY: positive = net long, negative = net short.

subset[signed_ntnl_col] = subset['side'] * subset[qty_col] * subset[price_col]

2.4 gross notional (absolute value) per position

subset[gross_ntnl_col] = subset[signed_ntnl_col].abs()

2.5 P&L per position

Version A (economically correct): P&L = signed notional * markout

subset[pnl_col] = subset[signed_ntnl_col] * subset[markout_col]

Version B (your "flipped reversion" convention):

If I want to test how flipping affects correlation:

subset[pnl_col + '_flipped'] = subset[signed_ntnl_col] * subset[flip_ret_col]

I can compare pnl_col vs pnl_col + '_flipped' later.

For hedging analysis, I should usually stick to pnl_col (non-flipped).

=========================

3. Aggregate to daily level

=========================

3.1 daily net notional (signed), daily gross notional, daily P&L

daily = ( subset .groupby(date_col) .agg( **{ daily_net_ntnl_col: (signed_ntnl_col, 'sum'), daily_gross_ntnl_col:(gross_ntnl_col, 'sum'), daily_pnl_col: (pnl_col, 'sum'), # optional: keep flipped-P&L daily as well daily_pnl_col + '_flipped': (pnl_col + '_flipped', 'sum') } ) .reset_index() )

=========================

4. Prepare SPY returns

=========================

4.1 sort SPY by date just in case

df_spy = df_spy.sort_values(spy_date_col)

4.2 forward close-to-next-close return: from T to T+1

For date T, this is (Close_{T+1} - Close_T) / Close_T.

df_spy[spy_ret_col] = df_spy[spy_price_col].shift(-1) / df_spy[spy_price_col] - 1.0

=========================

5. Merge daily strategy data with SPY and build hedge

=========================

5.1 merge on date

daily = daily.merge( df_spy[[spy_date_col, spy_ret_col]], left_on=date_col, right_on=spy_date_col, how='left' )

5.2 hedge notional: opposite of daily net notional

If strategy is net long +X, hedge is short -X, and vice versa.

daily['hedge_notional'] = -daily[daily_net_ntnl_col]

5.3 hedge P&L = hedge_notional * SPY return

Positive hedge_notional * positive SPY_ret => positive hedge P&L, etc.

daily['hedge_PNL'] = daily['hedge_notional'] * daily[spy_ret_col]

5.4 combined (hedged) strategy P&L

daily['hedged_strategy_PNL'] = daily[daily_pnl_col] + daily['hedge_PNL']

5.5 (optional) combined P&L if I used flipped P&L instead

daily['hedged_strategy_PNL_flipped'] = daily[daily_pnl_col + '_flipped'] + daily['hedge_PNL']

=========================

6. Sanity checks on notionals

=========================

hedge_notional should be almost perfectly negatively correlated with strategy net notional

ntnl_corr = daily[[daily_net_ntnl_col, 'hedge_notional']].corr()

total notional (strategy + hedge) should be ~0 each day if hedge is exact

daily['total_notional_after_hedge'] = daily[daily_net_ntnl_col] + daily['hedge_notional']

=========================

7. Correlation checks on P&L

=========================

7.1 correlation between strategy and hedge P&L

pnl_corr = daily[[daily_pnl_col, 'hedge_PNL', 'hedged_strategy_PNL']].corr()

7.2 same but using flipped P&L for comparison

pnl_corr_flipped = daily[[daily_pnl_col + '_flipped', 'hedge_PNL', 'hedged_strategy_PNL_flipped']].corr()

print("Notional correlation (strategy vs hedge):") print(ntnl_corr)

print("\nP&L correlations (using normal markout-based P&L):") print(pnl_corr)

print("\nP&L correlations (using flipped reversion P&L):") print(pnl_corr_flipped)

Key sign points to keep in mind The direction should come from is_buy (side = +1/-1), not from arbitrarily flipping the return.

P&L should be signed_notional * markout_return. If you instead use signed_notional * flipped_reversion you’re effectively trading the opposite side of the realized move.

That inversion is exactly the kind of thing that can turn an expected negative corr between strategy P&L and hedge P&L into a positive one, even though the notionals are correctly anti-correlated.

You can now tweak: markout_col if your markout name changes,

pnl_col, signed_ntnl_col, etc. if you rename them in subset,

spy_ret_col if you want a different label for the SPY return.

======================================= 2 Claude Let me think through this carefully. The Core Logic: You have intraday trades benchmarked against T+1 close Your P&L uses "flipped reversion" = -1 * markout return (because you're fading the move?) Daily net notional can be long or short You hedge dollar-for-dollar with SPY (beta=1): if net notional is -10M, buy +10M SPY You expect negative correlation between strategy P&L and hedge P&L since they're opposite positions The Key Question: Should we flip the sign? If you're buying and price goes up (positive markout), you make money → positive P&L But you called it "reversion" and flipped the sign, suggesting you're betting against the move For hedging: if you're net long → you short SPY → SPY going up hurts the hedge Let me code this step by step with the flexibility to test both scenarios: import pandas as pd import numpy as np

============================================================================

PARAMETERS - change these if needed

============================================================================

spy_return_col = 'spy_ret' # name for SPY forward return column strategy_pnl_col = 'capt_facilitated_pnl' # daily strategy P&L strategy_net_notional_col = 'daily_net_notional' # daily signed notional strategy_gross_notional_col = 'daily_gross_notional' # daily absolute notional

============================================================================

STEP 1: Calculate flipped reversion for each position

markout = (T+1 close - current price) / current price

flipped_reversion = -1 * markout (betting against the move)

============================================================================

subset['markout'] = subset['markout'] # assuming this already exists subset['flipped_reversion'] = -1 * subset['markout']

============================================================================

STEP 2: Calculate P&L for each position

P&L = executed_qty * current_price * flipped_reversion

This gives us dollar P&L from fading the markout move

============================================================================

subset['capt_facilitated_pnl'] = ( subset['executed_qty'] * subset['current_end_of_day_price'] * subset['flipped_reversion'] )

============================================================================

STEP 3: Calculate notional for each position

notional = executed_qty * current_price (unsigned)

signed_notional = notional * (1 if buy else -1)

This tells us our directional exposure for each trade

============================================================================

subset['capt_facilitated_ntnl'] = ( abs(subset['executed_qty']) * subset['current_end_of_day_price'] )

subset['signed_capt_facilitated_ntnl'] = ( subset['capt_facilitated_ntnl'] * np.where(subset['is_underscore_buy'], 1, -1) )

============================================================================

STEP 4: Aggregate to daily level

net_notional = sum of signed notionals (can be +/-)

gross_notional = sum of absolute notionals (always +)

daily_pnl = sum of all position P&Ls

============================================================================

daily = subset.groupby('date').agg({ 'signed_capt_facilitated_ntnl': 'sum', # net exposure 'capt_facilitated_ntnl': 'sum', # gross exposure 'capt_facilitated_pnl': 'sum' # total strategy P&L }).reset_index()

rename for clarity

daily = daily.rename(columns={ 'signed_capt_facilitated_ntnl': strategy_net_notional_col, 'capt_facilitated_ntnl': strategy_gross_notional_col, 'capt_facilitated_pnl': strategy_pnl_col })

============================================================================

STEP 5: Merge with SPY data and calculate SPY returns

SPY return = (close_t+1 - close_t) / close_t

This is the forward return we'll use for hedge P&L

============================================================================

df_spy = df_spy.sort_values('date').copy() df_spy[spy_return_col] = df_spy['closePrice'].pct_change().shift(-1) # forward return

merge strategy daily data with SPY

daily = daily.merge(df_spy[['date', 'closePrice', spy_return_col]], on='date', how='inner')

============================================================================

STEP 6: Calculate hedge positions and hedge P&L

hedge_notional = -1 * strategy_net_notional (opposite position in SPY)

If we're net long +10M, we short -10M SPY to hedge

hedge_pnl = hedge_notional * spy_return

============================================================================

daily['hedge_notional'] = -1 * daily[strategy_net_notional_col]

hedge P&L: if we're short SPY (-10M) and SPY goes up (+1%), we lose -100K

daily['hedge_pnl'] = daily['hedge_notional'] * daily[spy_return_col]

============================================================================

STEP 7: Calculate combined hedged strategy P&L

This is our strategy P&L + hedge P&L (should be close to zero if perfect hedge)

============================================================================

daily['hedged_strategy_pnl'] = daily[strategy_pnl_col] + daily['hedge_pnl']

============================================================================

STEP 8: Correlation analysis

Check relationships between notionals and P&Ls

============================================================================

print("\n" + "="*70) print("NOTIONAL CORRELATIONS") print("="*70) corr_notionals = daily[[strategy_net_notional_col, 'hedge_notional']].corr() print(f"\nStrategy Net Notional vs Hedge Notional:") print(f"Correlation = {corr_notionals.iloc[0, 1]:.4f}") print("(Should be -1.00 by construction since hedge = -1 * strategy)")

print("\n" + "="*70) print("P&L CORRELATIONS") print("="*70) corr_pnl = daily[[strategy_pnl_col, 'hedge_pnl', 'hedged_strategy_pnl']].corr() print(f"\nStrategy P&L vs Hedge P&L:") print(f"Correlation = {corr_pnl.loc[strategy_pnl_col, 'hedge_pnl']:.4f}") print("(Expected: negative if strategy and market move same direction)")

print(f"\nStrategy P&L vs Hedged Strategy P&L:") print(f"Correlation = {corr_pnl.loc[strategy_pnl_col, 'hedged_strategy_pnl']:.4f}")

print(f"\nHedge P&L vs Hedged Strategy P&L:") print(f"Correlation = {corr_pnl.loc['hedge_pnl', 'hedged_strategy_pnl']:.4f}")

============================================================================

STEP 9: Diagnostics - check if hedge is actually working

If hedge is perfect and strategy has no alpha, hedged P&L should be ~0

============================================================================

print("\n" + "="*70) print("HEDGE EFFECTIVENESS DIAGNOSTICS") print("="*70) print(f"\nStrategy P&L: mean={daily[strategy_pnl_col].mean():.2f}, " f"std={daily[strategy_pnl_col].std():.2f}") print(f"Hedge P&L: mean={daily['hedge_pnl'].mean():.2f}, " f"std={daily['hedge_pnl'].std():.2f}") print(f"Hedged Strategy P&L: mean={daily['hedged_strategy_pnl'].mean():.2f}, " f"std={daily['hedged_strategy_pnl'].std():.2f}")

check if hedged P&L std is much smaller than strategy P&L std

hedge_reduction = 1 - (daily['hedged_strategy_pnl'].std() / daily[strategy_pnl_col].std()) print(f"\nVolatility reduction from hedging: {hedge_reduction*100:.1f}%")

============================================================================

POTENTIAL ISSUE: If correlation is POSITIVE instead of negative

This likely means the flipped_reversion sign is wrong

Try alternative: P&L = qty * price * markout (without the flip)

============================================================================

print("\n" + "="*70) print("TESTING ALTERNATIVE: P&L WITHOUT SIGN FLIP") print("="*70)

recalculate without the flip

subset['pnl_no_flip'] = ( subset['executed_qty'] * subset['current_end_of_day_price'] * subset['markout'] )

aggregate to daily

daily_alt = subset.groupby('date').agg({ 'signed_capt_facilitated_ntnl': 'sum', 'pnl_no_flip': 'sum' }).reset_index()

daily_alt = daily_alt.merge(df_spy[['date', spy_return_col]], on='date', how='inner') daily_alt['hedge_notional'] = -1 * daily_alt['signed_capt_facilitated_ntnl'] daily_alt['hedge_pnl'] = daily_alt['hedge_notional'] * daily_alt[spy_return_col]

corr_alt = np.corrcoef(daily_alt['pnl_no_flip'], daily_alt['hedge_pnl'])[0, 1] print(f"\nAlternative P&L (no flip) vs Hedge P&L correlation: {corr_alt:.4f}") print("If this is more negative, then the sign flip was the problem")

print("\n" + "="*70)

Key things to check: If you're seeing positive correlation between strategy P&L and hedge P&L, it means your strategy is actually moving WITH the market, not against it. The sign flip might be wrong.

The hedge notional should be perfectly negatively correlated (-1.00) with strategy notional by construction.

If hedged P&L volatility is not much smaller than strategy P&L volatility, either:

Your strategy has genuine alpha (good!) The beta isn't actually 1 (timing differences between your trades and T+1 close vs SPY daily returns) Does this match what you're trying to do? The diagnostics at the end should help figure out if the sign flip is causing issues.

============== 3 Gemini import pandas as pd import numpy as np

==========================================

Parameters & Column Names

==========================================

Define variable names here to allow easy refactoring later

COL_QTY = 'exec_qty' COL_PRICE = 'price' # Your strategy execution price or current close COL_NTNL_GROSS = 'CAPT_facilitated_NTNL' COL_NTNL_SIGNED = 'signed_CAPT_facilitated_NTNL' COL_PNL = 'CAPT_facilitated_PnL' COL_SPY_CLOSE = 'ClosePrice' COL_SPY_RET = 'SPY_Ret' # Parameter name as requested COL_DATE = 'date'

==========================================

1. Prepare Strategy Data (subset)

==========================================

Assuming 'subset' is your trade-level dataframe.

Calculating returns and notionals per your logic.

Sort to ensure T+1 shift works correctly if data isn't ordered

subset = subset.sort_values(by=[COL_DATE])

Calculate forward-looking return (Markout)

Taking (T+1 Price - T Price) / T Price

subset['fwd_ret'] = subset.groupby('symbol')[COL_PRICE].pct_change().shift(-1)

--- CRITICAL LOGIC START ---

Calculating the "Flipped Reversion"

NOTE: Doing this inverts the P&L profile.

A Long position will now profit when prices DROP.

This is likely why Strategy P&L correlates positively with Short Hedge P&L.

subset['flipped_reversion'] = subset['fwd_ret'] * -1

Calculate P&L using the flipped reversion

PnL = Signed Notional ($ Exposure) * Flipped Return

subset[COL_PNL] = subset[COL_NTNL_SIGNED] * subset['flipped_reversion']

--- CRITICAL LOGIC END ---

==========================================

2. Aggregate to Daily Level

==========================================

We need daily totals to calculate the daily hedge requirement

daily_agg = subset.groupby(COL_DATE).agg({ COL_NTNL_SIGNED: 'sum', # Net Notional (Longs + Shorts) COL_NTNL_GROSS: 'sum', # Gross Notional (Abs sum) COL_PNL: 'sum' # Daily Strategy P&L (Accumulated) }).reset_index()

Rename columns for clarity after aggregation

daily_agg = daily_agg.rename(columns={ COL_NTNL_SIGNED: 'Daily_Net_Notional', COL_NTNL_GROSS: 'Daily_Gross_Notional', COL_PNL: 'Daily_Strategy_PnL' })

==========================================

3. Prepare SPY Data (DF_SPY)

==========================================

Calculate SPY forward returns to match strategy markout timing

(T+1 Close / T Close) - 1

DF_SPY = DF_SPY.sort_values(by=COL_DATE) DF_SPY[COL_SPY_RET] = DF_SPY[COL_SPY_CLOSE].pct_change().shift(-1)

Clean up SPY df to just what we need for the merge

spy_clean = DF_SPY[[COL_DATE, COL_SPY_RET]].copy()

==========================================

4. Merge and Hedge Calculation

==========================================

Left join: We want to hedge our strategy days

merged_df = pd.merge(daily_agg, spy_clean, on=COL_DATE, how='left')

Calculate Dollar Neutral Hedge Notional

Logic: If Net Notional is +10M (Long), Hedge must be -10M (Short)

Beta is forced to 1.0 per requirements

merged_df['Hedge_Notional'] = merged_df['Daily_Net_Notional'] * -1

Calculate Hedge P&L

Hedge P&L = Hedge Notional * SPY Return

merged_df['Hedge_PnL'] = merged_df['Hedge_Notional'] * merged_df[COL_SPY_RET]

Calculate Total Hedged Strategy P&L

merged_df['Total_Hedged_PnL'] = merged_df['Daily_Strategy_PnL'] + merged_df['Hedge_PnL']

==========================================

5. Correlation Analysis

==========================================

Check 1: Notional Correlation

Expectation: -1.0 (Perfectly negatively correlated by definition)

corr_notional = merged_df['Daily_Net_Notional'].corr(merged_df['Hedge_Notional'])

Check 2: P&L Correlation

Expectation (Naive): Negative

Expectation (With Flipped Reversion): Positive

corr_pnl = merged_df['Daily_Strategy_PnL'].corr(merged_df['Hedge_PnL'])

print("--- Analysis Results ---") print(f"Correlation of Notionals (Should be -1): {corr_notional:.4f}") print(f"Correlation of P&Ls (Strategy vs Hedge): {corr_pnl:.4f}")

if corr_pnl > 0: print("\nWARNING: Positive P&L correlation detected.") print("Cause: 'flipped_reversion' inverted Strategy P&L.") print("Result: Strategy behaves like a Short; Hedge is Short.") print("Both profit from market drops -> Positive Correlation.")

Preview Data

print("\n--- Data Preview ---") print(merged_df[[COL_DATE, 'Daily_Net_Notional', 'Hedge_Notional', 'Daily_Strategy_PnL', 'Hedge_PnL']].head())

========================== 4 Groq

import pandas as pd

# Define column names as parameters so I can change them if needed
strategy_gross_notional_col = 'CAPT_facilitated_NTNL'  # this is the absolute notional per position
strategy_signed_notional_col = 'signed_CAPT_facilitated_NTNL'  # this is the signed net notional per position
strategy_pnl_col = 'CAPT_facilitated_P&L'  # this is the P&L per position, but might be flipped, so we'll handle it
spy_close_col = 'Close'  # SPY close price column
spy_ret_col = 'SPY_ret'  # we'll create this for SPY forward return
date_col = 'date'  # assuming this is the date column in both dataframes
strategy_return_col = 'markout_ret'  # assuming subset has this column for the markout return per position; if not, I need to add it or adjust

# First, calculate flipped reversion from the markout return
# I do this because the query mentions it, but I think we shouldn't use it for P&L if we want correct actual profits
# Flipping reverses the sign of the return, which would make P&L signs wrong for actual positions
# For example, if I'm long and price goes up (positive markout), P&L should be positive, not negative
# If the strategy is mean-reversion, the entry decision might expect opposite move, but actual P&L uses actual markout, not flipped
# Flipping would explain why correlation is positive instead of negative, because it inverts the market component in strategy P&L
# So, I'll calculate both flipped and actual P&L, but use actual for analysis to get the expected negative correlation
subset['flipped_reversion'] = -subset[strategy_return_col]  # flip the markout return
subset['pnl_flipped'] = subset[strategy_signed_notional_col] * subset['flipped_reversion']  # P&L using flipped, which might be what's in data
subset['pnl_actual'] = subset[strategy_signed_notional_col] * subset[strategy_return_col]  # actual P&L using unflipped markout; this should give negative corr

# If the existing strategy_pnl_col is the flipped one, we can verify by comparing to pnl_flipped, but for now, I'll use pnl_actual for aggregates
# If markout_ret isn't in data, maybe P&L is precomputed; but query says start with this, so assuming it's there

# Now, create daily aggregates: gross notional (sum of absolutes, but since gross col is given, sum it), net notional (sum signed), and P&L (sum actual)
# Gross notional is sum of the gross column, which I assume is abs per position
daily_df = subset.groupby(date_col).agg(
    strategy_gross_notional=(strategy_gross_notional_col, 'sum'),  # total gross notional for the day, sum of absolutes
    strategy_net_notional=(strategy_signed_notional_col, 'sum'),  # total net signed notional for the day
    strategy_pnl=('pnl_actual', 'sum')  # total strategy P&L using actual (unflipped); change to 'pnl_flipped' if I want the other
).reset_index()

# Next, prepare SPY dataframe: sort by date and calculate forward return shifted back to current day
# This is (t+1 close - t close) / t close, placed on day t
DF_SPY = DF_SPY.sort_values(date_col)
DF_SPY[spy_ret_col] = (DF_SPY[spy_close_col].shift(-1) - DF_SPY[spy_close_col]) / DF_SPY[spy_close_col]

# Merge daily_df with SPY returns on date; use left to keep strategy days
daily_df = daily_df.merge(DF_SPY[[date_col, spy_ret_col]], on=date_col, how='left')

# Now, calculate the hedge: hedge notional is -strategy net notional to make dollar neutral
# If net positive (net long), hedge short SPY; if negative (net short), hedge long SPY
daily_df['hedge_notional'] = -daily_df['strategy_net_notional']

# Calculate hedge P&L: hedge notional * SPY forward return
# Since hedge is set at end of t, P&L over t to t+1
daily_df['hedge_pnl'] = daily_df['hedge_notional'] * daily_df[spy_ret_col]

# Calculate combined hedged P&L: strategy + hedge
daily_df['hedged_pnl'] = daily_df['strategy_pnl'] + daily_df['hedge_pnl']

# Verify that total notional is zero: strategy net + hedge should be 0
daily_df['total_notional'] = daily_df['strategy_net_notional'] + daily_df['hedge_notional']  # should be zero

# Now, do simple correlation tests as requested
# Correlation between strategy net notional and hedge notional: should be -1 since hedge = -strategy
corr_notionals = daily_df['strategy_net_notional'].corr(daily_df['hedge_notional'])
print(f'Correlation between strategy net notional and hedge notional: {corr_notionals}')  # expect -1

# Correlation between facilitated (strategy) notional and hedge notional: but facilitated is gross? Query says facilitated notional of strategy and hedge are perfectly negatively correlated, but gross is positive, net is signed
# But since hedge is -net, corr(net, hedge) = -1, as above

# Correlation between strategy P&L and hedge P&L: with actual P&L, expect relatively large negative due to market offset
corr_pnls = daily_df['strategy_pnl'].corr(daily_df['hedge_pnl'])
print(f'Correlation between strategy P&L and hedge P&L: {corr_pnls}')  # expect negative; if positive, switch to pnl_flipped in agg to see

# Maybe also corr between hedged P&L and SPY ret: should be low if hedge works
corr_hedged_spy = daily_df['hedged_pnl'].corr(daily_df[spy_ret_col])
print(f'Correlation between hedged P&L and SPY return: {corr_hedged_spy}')  # expect close to 0

# If I want to see with flipped P&L, I can re-agg with pnl_flipped and re-run corrs
# But I think using actual is correct for proper hedging analysis

About

Awesome-LLM: a curated list of Large Language Model

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published