AJAYKASU commited on
Commit
53fdf44
·
verified ·
1 Parent(s): af6337d

Feature: IPO Discount, Greenshoe, & Ownership Logic

Browse files
Files changed (1) hide show
  1. main.py +76 -3
main.py CHANGED
@@ -309,6 +309,52 @@ def generate_advisory(signals, macro, fundamentals, last_private_price):
309
  'risk_matrix': risk_matrix
310
  }
311
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  # ==============================================================================
313
  # ROUTES
314
  # ==============================================================================
@@ -324,7 +370,10 @@ async def health_check():
324
  @app.post("/analyze")
325
  async def analyze(request: Request,
326
  query: str = Form(...),
327
- last_private: str = Form(None)):
 
 
 
328
 
329
  # 1. Determine Sector
330
  sector_key = 'SaaS'
@@ -347,9 +396,31 @@ async def analyze(request: Request,
347
  fundamentals = get_fundamentals(target_tickers)
348
 
349
  # 4. The Advisor Engine
 
350
  advisory = generate_advisory(signals, macro, fundamentals, last_private)
351
 
352
- # 5. Chart Data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  normalized = prices / prices.iloc[0] * 100
354
  chart_data = []
355
  for col in normalized.columns:
@@ -362,10 +433,12 @@ async def analyze(request: Request,
362
  'mode': 'lines'
363
  })
364
 
365
- # 6. Response
366
  response_data = {
367
  'sector': sector_key,
368
  'advisory': advisory,
 
 
369
  'macro': macro,
370
  'metrics': {
371
  'avg_momentum': np.mean([s['momentum'] for s in signals.values()]) if signals else 0,
 
309
  'risk_matrix': risk_matrix
310
  }
311
 
312
+ # ==============================================================================
313
+ # IPO STRUCTURING LOGIC (Professional)
314
+ # ==============================================================================
315
+
316
+ def calculate_ipo_structure(implied_price, discount_pct, greenshoe_active, existing_shares_m):
317
+ """
318
+ Calculates final deal structure based on banking levers.
319
+ Assumption: Target Capital Raise = $250M (Standard for this segment)
320
+ """
321
+ target_raise = 250.0 # $M
322
+
323
+ # 1. Apply Discount
324
+ discount_factor = (100 - discount_pct) / 100
325
+ final_price = implied_price * discount_factor
326
+
327
+ if final_price <= 0: final_price = 1.0 # Safety
328
+
329
+ # 2. Calculate Issuance
330
+ new_shares_m = target_raise / final_price
331
+
332
+ # 3. Greenshoe Adjustment (Standard 15% Over-allotment)
333
+ greenshoe_shares_m = 0.0
334
+ if greenshoe_active:
335
+ greenshoe_shares_m = new_shares_m * 0.15
336
+ new_shares_m += greenshoe_shares_m
337
+ target_raise += (greenshoe_shares_m * final_price)
338
+
339
+ # 4. Dilution & Ownership
340
+ total_shares_m = existing_shares_m + new_shares_m
341
+ dilution_pct = (new_shares_m / total_shares_m) * 100
342
+
343
+ # Ownership Split
344
+ ownership = {
345
+ "Existing Shareholders": round(existing_shares_m, 2),
346
+ "New Public Investors": round(new_shares_m, 2)
347
+ }
348
+
349
+ return {
350
+ "final_price": round(final_price, 2),
351
+ "capital_raised": round(target_raise, 2),
352
+ "new_shares": round(new_shares_m, 2),
353
+ "total_shares": round(total_shares_m, 2),
354
+ "dilution": round(dilution_pct, 1),
355
+ "ownership": ownership
356
+ }
357
+
358
  # ==============================================================================
359
  # ROUTES
360
  # ==============================================================================
 
370
  @app.post("/analyze")
371
  async def analyze(request: Request,
372
  query: str = Form(...),
373
+ last_private: str = Form(None),
374
+ ipo_discount: float = Form(15.0), # Default 15%
375
+ greenshoe: bool = Form(False), # Default Off
376
+ primary_shares: float = Form(100.0)): # Default 100M shares
377
 
378
  # 1. Determine Sector
379
  sector_key = 'SaaS'
 
396
  fundamentals = get_fundamentals(target_tickers)
397
 
398
  # 4. The Advisor Engine
399
+ # Get base advisory first to get the 'High' implied price
400
  advisory = generate_advisory(signals, macro, fundamentals, last_private)
401
 
402
+ # 5. IPO Structuring (The Pro Layer)
403
+ # We use the midpoint of the implied range as the base for discounting
404
+ implied_midpoint = (advisory['low'] + advisory['high']) / 2
405
+ structure = calculate_ipo_structure(implied_midpoint, ipo_discount, greenshoe, primary_shares)
406
+
407
+ # Update Advisory with Final Price Context
408
+ final_price = structure['final_price']
409
+
410
+ # Re-Run Down-Round Logic on FINAL PRICE
411
+ down_round_alert = False
412
+ dr_text = ""
413
+ if last_private:
414
+ try:
415
+ lpp = float(last_private)
416
+ if final_price < lpp:
417
+ down_round_alert = True
418
+ diff = ((final_price - lpp) / lpp) * 100
419
+ dr_text = f"🚨 <b>DOWN-ROUND ALERT:</b> Final IPO Price (${final_price}) is {diff:.1f}% below Last Private Round (${lpp})."
420
+ except:
421
+ pass
422
+
423
+ # 6. Chart Data
424
  normalized = prices / prices.iloc[0] * 100
425
  chart_data = []
426
  for col in normalized.columns:
 
433
  'mode': 'lines'
434
  })
435
 
436
+ # 7. Response
437
  response_data = {
438
  'sector': sector_key,
439
  'advisory': advisory,
440
+ 'structure': structure, # NEW
441
+ 'down_round': {'is_active': down_round_alert, 'text': dr_text}, # NEW
442
  'macro': macro,
443
  'metrics': {
444
  'avg_momentum': np.mean([s['momentum'] for s in signals.values()]) if signals else 0,