quant-ai / scripts /audit_execution_costs.py
Ypeng12's picture
feat(ai): integrate direction holding candidates and audited L1 research
84b5ace
Raw History Blame Contribute Delete
3.9 kB
#!/usr/bin/env python3
"""Read-only Paper fill/fee evidence and historical quote matching. Private output."""
import argparse,json,sys
from pathlib import Path
import pandas as pd
import requests
from dotenv import dotenv_values
ROOT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(ROOT/'backend'))
from app.broker.credentials import resolve_trading_credentials
from app.research.execution_cost_audit import audit_fills_against_quotes
def run(args):
credentials=resolve_trading_credentials(dotenv_values(args.env_file))
if credentials is None or not credentials.is_paper:raise ValueError('Paper-only audit')
output=Path(args.output);output.mkdir(parents=True,exist_ok=False)
session=requests.Session();session.headers.update({'APCA-API-KEY-ID':credentials.key,'APCA-API-SECRET-KEY':credentials.secret})
def get(url,params):
r=session.get(url,params=params,timeout=25)
if r.status_code!=200:raise ValueError(f'Endpoint HTTP {r.status_code}')
return r.json()
start=pd.Timestamp(args.date,tz='America/New_York');until=min(start+pd.Timedelta(days=1),pd.Timestamp.now(tz='UTC'))
activities=[];token=None;seen=set()
while True:
params=dict(after=start.isoformat(),until=until.isoformat(),direction='asc',page_size=100)
if token:params['page_token']=token
page=get(credentials.endpoint+'/v2/account/activities',params)
activities.extend(page)
if len(page)<100:break
token=page[-1]['id']
if token in seen:raise ValueError('Repeated activity page')
seen.add(token)
(output/'activities.json').write_text(json.dumps(activities,indent=2)+'\n')
fills=[];quotes=[];errors=[]
for a in activities:
if a.get('activity_type')!='FILL':continue
fill=dict(fill_id=a['id'],symbol=a['symbol'],side=a['side'],qty=float(a['qty']),price=float(a['price']),timestamp=a['transaction_time'])
fills.append(fill);t=pd.Timestamp(fill['timestamp'])
if t>pd.Timestamp.now(tz='UTC')-pd.Timedelta(minutes=15):
errors.append(dict(fill_id=a['id'],reason='Historical SIP query must be 15 minutes old'));continue
try:
raw=get('https://data.alpaca.markets/v2/stocks/'+a['symbol']+'/quotes',dict(start=(t-pd.Timedelta(seconds=1)).isoformat(),end=t.isoformat(),feed='sip',sort='desc',limit=10000))
for q in raw.get('quotes') or []:
quotes.append(dict(symbol=a['symbol'],timestamp=q['t'],bid=q['bp'],ask=q['ap'],feed='sip'))
except Exception as exc:errors.append(dict(fill_id=a['id'],reason=str(exc)))
diagnostic=audit_fills_against_quotes(fills,quotes)
fee_entries=[a for a in activities if a.get('activity_type') in {'FEE','CFEE'}]
report=dict(date=args.date,account_type='paper',read_only=True,broker_mutations=0,
coverage_start=start.isoformat(),coverage_end=until.isoformat(),activity_count=len(activities),
observed_activity_types=sorted({a.get('activity_type','unknown') for a in activities}),
observed_fee_entries=len(fee_entries),observed_fee_cash_amount=sum(float(a.get('net_amount') or 0) for a in fee_entries),
all_actual_fees_verified=False,fee_interpretation='No observed FEE rows does not establish absence of later charges or fees posted under other activity types.',
quote_errors=errors,execution_diagnostic=diagnostic)
(output/'report.json').write_text(json.dumps(report,indent=2,allow_nan=False)+'\n')
print(json.dumps({k:v for k,v in report.items() if k not in {'execution_diagnostic','quote_errors'}},indent=2))
print(json.dumps({k:v for k,v in diagnostic.items() if k!='rows'},indent=2))
if __name__=='__main__':
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--env-file',required=True);p.add_argument('--date',required=True);p.add_argument('--output',required=True)
run(p.parse_args())