diff --git a/CASHAPP_INTEGRATION.md b/CASHAPP_INTEGRATION.md new file mode 100644 index 000000000..a56d78e9a --- /dev/null +++ b/CASHAPP_INTEGRATION.md @@ -0,0 +1,228 @@ +# Cash App Integration Guide + +This guide demonstrates how to use the Cash App integration features in yfinance. + +## Overview + +The Cash App integration module provides utilities for Cash App users to: +- Manage portfolios +- Format data for Cash App workflows +- Calculate dollar-cost averaging strategies +- Get investment recommendations +- Export portfolio data + +## Quick Start + +### Import the Module + +```python +import yfinance as yf +from yfinance.cashapp import ( + CashAppPortfolio, + format_for_cashapp, + get_cashapp_watchlist, + calculate_dollar_cost_average, + get_cashapp_recommendations +) +``` + +## Portfolio Management + +### Creating a Portfolio + +```python +# Create an empty portfolio +portfolio = CashAppPortfolio() + +# Or initialize with holdings +holdings = {'AAPL': 10, 'MSFT': 5, 'GOOGL': 2} +portfolio = CashAppPortfolio(holdings) +``` + +### Adding Positions + +```python +portfolio = CashAppPortfolio() +portfolio.add_position('AAPL', 10) +portfolio.add_position('MSFT', 5) +portfolio.add_position('TSLA', 3) +``` + +### Getting Portfolio Value + +```python +# Get detailed portfolio information +portfolio_data = portfolio.get_portfolio_value() +print(f"Total Value: ${portfolio_data['total_value']:.2f}") + +# View individual positions +for ticker, info in portfolio_data['positions'].items(): + print(f"{ticker}: {info['shares']} shares @ ${info['price']:.2f} = ${info['value']:.2f}") +``` + +### Portfolio Summary DataFrame + +```python +# Get a pandas DataFrame summary +df = portfolio.get_portfolio_summary() +print(df) +``` + +### Export Portfolio + +```python +# Export to CSV +portfolio.export_to_csv('my_portfolio.csv') + +# Export to JSON +portfolio.export_to_json('my_portfolio.json') +``` + +### Performance Analysis + +```python +# Get performance metrics for different periods +performance_1d = portfolio.get_performance('1d') +performance_1mo = portfolio.get_performance('1mo') +performance_1y = portfolio.get_performance('1y') + +for ticker, perf in performance_1mo.items(): + if 'error' not in perf: + print(f"{ticker}: {perf['change_pct']:.2f}% change") +``` + +## Data Formatting + +### Format Quote Data + +```python +# Get Cash App-friendly quote format +quote = format_for_cashapp('AAPL', 'quote') +print(f"{quote['name']}: ${quote['price']:.2f} ({quote['change_percent']:.2f}%)") +``` + +### Format Historical Data + +```python +# Get historical price data +history = format_for_cashapp('AAPL', 'history') +print(f"Price history: {len(history['prices'])} data points") +``` + +### Format Company Info + +```python +# Get company information +info = format_for_cashapp('AAPL', 'info') +print(f"Sector: {info['sector']}") +print(f"Industry: {info['industry']}") +``` + +## Watchlist Management + +### Create a Watchlist + +```python +# Create a watchlist from multiple tickers +tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA'] +watchlist = get_cashapp_watchlist(tickers) + +# Display the watchlist +print(watchlist[['symbol', 'name', 'price', 'change_percent']]) +``` + +## Dollar-Cost Averaging (DCA) + +### Calculate DCA Strategy + +```python +# Calculate DCA results for investing $100 monthly +dca_results = calculate_dollar_cost_average( + ticker='AAPL', + amount=100, + frequency='monthly', + start_date='2023-01-01', + end_date='2024-01-01' +) + +print(f"Total Invested: ${dca_results['total_invested']:.2f}") +print(f"Total Shares: {dca_results['total_shares']:.2f}") +print(f"Average Cost Basis: ${dca_results['average_cost_basis']:.2f}") +print(f"Current Value: ${dca_results['current_value']:.2f}") +print(f"Total Return: {dca_results['total_return_pct']:.2f}%") +``` + +### Different Frequencies + +```python +# Daily DCA +dca_daily = calculate_dollar_cost_average('AAPL', 10, 'daily') + +# Weekly DCA +dca_weekly = calculate_dollar_cost_average('AAPL', 50, 'weekly') + +# Monthly DCA (default) +dca_monthly = calculate_dollar_cost_average('AAPL', 200, 'monthly') +``` + +## Investment Recommendations + +### Get Recommendations + +```python +# Get investment recommendations +recommendations = get_cashapp_recommendations('AAPL') + +print(f"Current Price: ${recommendations['current_price']:.2f}") +print(f"Target Price: ${recommendations['target_price']:.2f}") +print(f"Upside Potential: {recommendations['upside_potential_pct']:.2f}%") +print(f"Recommendation: {recommendations['recommendation']}") +``` + +## Complete Example + +```python +import yfinance as yf +from yfinance.cashapp import CashAppPortfolio, format_for_cashapp, get_cashapp_watchlist + +# Create and manage a portfolio +portfolio = CashAppPortfolio({ + 'AAPL': 10, + 'MSFT': 5, + 'GOOGL': 2 +}) + +# Add more positions +portfolio.add_position('TSLA', 3) + +# Get portfolio summary +summary = portfolio.get_portfolio_summary() +print("Portfolio Summary:") +print(summary) + +# Get portfolio value +value = portfolio.get_portfolio_value() +print(f"\nTotal Portfolio Value: ${value['total_value']:.2f}") + +# Export portfolio +portfolio.export_to_csv('cashapp_portfolio.csv') + +# Create a watchlist +watchlist = get_cashapp_watchlist(['AAPL', 'MSFT', 'GOOGL', 'AMZN']) +print("\nWatchlist:") +print(watchlist[['symbol', 'price', 'change_percent']]) +``` + +## Notes + +- Cash App integration uses yfinance data sources (Yahoo Finance) +- All prices and data are fetched in real-time +- Portfolio calculations are based on current market prices +- Export formats are compatible with common spreadsheet applications +- Dollar-cost averaging calculations use historical price data + +## Limitations + +- Cash App doesn't provide a public API for trading, so this integration focuses on data analysis and portfolio management +- All data is sourced from Yahoo Finance via yfinance +- Portfolio tracking is local and not synced with Cash App accounts diff --git a/tests/test_cashapp.py b/tests/test_cashapp.py new file mode 100644 index 000000000..e660663ff --- /dev/null +++ b/tests/test_cashapp.py @@ -0,0 +1,168 @@ +""" +Tests for Cash App Integration + +To run all tests in suite from commandline: + python -m unittest tests.test_cashapp + +Specific test class: + python -m unittest tests.test_cashapp.TestCashAppPortfolio +""" + +import unittest +import pandas as pd +from datetime import datetime + +from tests.context import yfinance as yf +from yfinance.cashapp import ( + CashAppPortfolio, + format_for_cashapp, + get_cashapp_watchlist, + calculate_dollar_cost_average, + get_cashapp_recommendations +) + + +class TestCashAppPortfolio(unittest.TestCase): + """Test CashAppPortfolio class""" + + def test_init_empty(self): + """Test initializing empty portfolio""" + portfolio = CashAppPortfolio() + self.assertEqual(portfolio.holdings, {}) + self.assertIsNone(portfolio._tickers) + + def test_init_with_holdings(self): + """Test initializing portfolio with holdings""" + holdings = {'AAPL': 10, 'MSFT': 5} + portfolio = CashAppPortfolio(holdings) + self.assertEqual(portfolio.holdings, holdings) + + def test_add_position(self): + """Test adding a position""" + portfolio = CashAppPortfolio() + portfolio.add_position('AAPL', 10) + self.assertEqual(portfolio.holdings['AAPL'], 10) + + def test_add_position_update(self): + """Test updating existing position""" + portfolio = CashAppPortfolio({'AAPL': 10}) + portfolio.add_position('AAPL', 5) + self.assertEqual(portfolio.holdings['AAPL'], 15) + + def test_remove_position(self): + """Test removing a position""" + portfolio = CashAppPortfolio({'AAPL': 10, 'MSFT': 5}) + portfolio.remove_position('AAPL') + self.assertNotIn('AAPL', portfolio.holdings) + self.assertIn('MSFT', portfolio.holdings) + + def test_get_portfolio_value_structure(self): + """Test portfolio value structure""" + portfolio = CashAppPortfolio({'AAPL': 10}) + result = portfolio.get_portfolio_value() + + self.assertIn('total_value', result) + self.assertIn('positions', result) + self.assertIn('last_updated', result) + self.assertIsInstance(result['positions'], dict) + + def test_get_portfolio_summary(self): + """Test portfolio summary DataFrame""" + portfolio = CashAppPortfolio({'AAPL': 10}) + df = portfolio.get_portfolio_summary() + + self.assertIsInstance(df, pd.DataFrame) + if not df.empty: + self.assertIn('Ticker', df.columns) + self.assertIn('Shares', df.columns) + self.assertIn('Price', df.columns) + self.assertIn('Value', df.columns) + self.assertIn('Weight %', df.columns) + + +class TestFormatForCashApp(unittest.TestCase): + """Test format_for_cashapp function""" + + def test_format_quote(self): + """Test formatting quote data""" + result = format_for_cashapp('AAPL', 'quote') + + self.assertIsInstance(result, dict) + self.assertEqual(result['symbol'], 'AAPL') + self.assertIn('name', result) + self.assertIn('price', result) + self.assertIn('change', result) + self.assertIn('change_percent', result) + + def test_format_history(self): + """Test formatting history data""" + result = format_for_cashapp('AAPL', 'history') + + self.assertIsInstance(result, dict) + if result: # May be empty if no data + self.assertEqual(result['symbol'], 'AAPL') + self.assertIn('dates', result) + self.assertIn('prices', result) + + def test_format_info(self): + """Test formatting info data""" + result = format_for_cashapp('AAPL', 'info') + + self.assertIsInstance(result, dict) + self.assertEqual(result['symbol'], 'AAPL') + self.assertIn('name', result) + + def test_format_invalid_type(self): + """Test invalid data type""" + with self.assertRaises(ValueError): + format_for_cashapp('AAPL', 'invalid') + + +class TestCashAppWatchlist(unittest.TestCase): + """Test get_cashapp_watchlist function""" + + def test_watchlist_basic(self): + """Test creating a watchlist""" + tickers = ['AAPL', 'MSFT'] + df = get_cashapp_watchlist(tickers) + + self.assertIsInstance(df, pd.DataFrame) + self.assertEqual(len(df), len(tickers)) + if not df.empty: + self.assertIn('symbol', df.columns) + self.assertIn('price', df.columns) + + +class TestDollarCostAverage(unittest.TestCase): + """Test calculate_dollar_cost_average function""" + + def test_dca_structure(self): + """Test DCA result structure""" + result = calculate_dollar_cost_average('AAPL', 100, 'monthly') + + self.assertIsInstance(result, dict) + if 'error' not in result: + self.assertIn('ticker', result) + self.assertIn('strategy', result) + self.assertIn('frequency', result) + self.assertIn('total_invested', result) + self.assertIn('total_shares', result) + self.assertIn('transactions', result) + + +class TestCashAppRecommendations(unittest.TestCase): + """Test get_cashapp_recommendations function""" + + def test_recommendations_structure(self): + """Test recommendations result structure""" + result = get_cashapp_recommendations('AAPL') + + self.assertIsInstance(result, dict) + self.assertIn('ticker', result) + if 'error' not in result: + self.assertIn('current_price', result) + self.assertIn('recommendation_summary', result) + + +if __name__ == '__main__': + unittest.main() diff --git a/yfinance/__init__.py b/yfinance/__init__.py index 279c3b9f2..8244b1b97 100644 --- a/yfinance/__init__.py +++ b/yfinance/__init__.py @@ -37,6 +37,14 @@ from .screener.query import EquityQuery, FundQuery from .screener.screener import screen, PREDEFINED_SCREENER_QUERIES +from .cashapp import ( + CashAppPortfolio, + format_for_cashapp, + get_cashapp_watchlist, + calculate_dollar_cost_average, + get_cashapp_recommendations +) + __version__ = version.version __author__ = "Ran Aroussi" @@ -46,6 +54,8 @@ __all__ = ['download', 'Market', 'Search', 'Lookup', 'Ticker', 'Tickers', 'enable_debug_mode', 'set_tz_cache_location', 'Sector', 'Industry', 'WebSocket', 'AsyncWebSocket', 'Calendars'] # screener stuff: __all__ += ['EquityQuery', 'FundQuery', 'screen', 'PREDEFINED_SCREENER_QUERIES'] +# Cash App integration: +__all__ += ['CashAppPortfolio', 'format_for_cashapp', 'get_cashapp_watchlist', 'calculate_dollar_cost_average', 'get_cashapp_recommendations'] # Config stuff: _NOTSET=object() diff --git a/yfinance/cashapp.py b/yfinance/cashapp.py new file mode 100644 index 000000000..aee0e227d --- /dev/null +++ b/yfinance/cashapp.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# yfinance - Cash App integration +# https://github.com/ranaroussi/yfinance +# +# Copyright 2017-2019 Ran Aroussi +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Cash App Integration Module + +This module provides utilities for Cash App users to work with yfinance data, +including portfolio management, data formatting, and export functionality. +""" + +from __future__ import print_function + +import pandas as _pd +import json as _json +from typing import Dict, List, Optional, Union +from datetime import datetime, timedelta + +from .ticker import Ticker +from .tickers import Tickers + + +class CashAppPortfolio: + """ + A portfolio manager for Cash App users that integrates with yfinance. + + This class helps manage and analyze portfolios in a format compatible + with Cash App's investing features. + """ + + def __init__(self, holdings: Optional[Dict[str, Union[int, float]]] = None): + """ + Initialize a Cash App portfolio. + + Args: + holdings: Dictionary mapping ticker symbols to number of shares. + Example: {'AAPL': 10, 'MSFT': 5, 'GOOGL': 2} + """ + self.holdings = holdings or {} + self._tickers = None + self._last_update = None + + def add_position(self, ticker: str, shares: Union[int, float]): + """ + Add or update a position in the portfolio. + + Args: + ticker: Stock ticker symbol + shares: Number of shares + """ + ticker = ticker.upper() + if ticker in self.holdings: + self.holdings[ticker] += shares + else: + self.holdings[ticker] = shares + + def remove_position(self, ticker: str): + """ + Remove a position from the portfolio. + + Args: + ticker: Stock ticker symbol to remove + """ + ticker = ticker.upper() + if ticker in self.holdings: + del self.holdings[ticker] + + def update_prices(self): + """ + Update current prices for all holdings. + + Returns: + Dictionary mapping tickers to current prices + """ + if not self.holdings: + return {} + + tickers = Tickers(list(self.holdings.keys())) + self._tickers = tickers + self._last_update = datetime.now() + + prices = {} + for ticker in self.holdings.keys(): + try: + ticker_obj = Ticker(ticker) + info = ticker_obj.fast_info + prices[ticker] = info.get('lastPrice', 0) + except Exception: + prices[ticker] = 0 + + return prices + + def get_portfolio_value(self) -> Dict[str, Union[float, Dict]]: + """ + Calculate total portfolio value and individual position values. + + Returns: + Dictionary with portfolio summary and position details + """ + prices = self.update_prices() + + positions = {} + total_value = 0.0 + + for ticker, shares in self.holdings.items(): + price = prices.get(ticker, 0) + value = shares * price + total_value += value + + positions[ticker] = { + 'shares': shares, + 'price': price, + 'value': value, + 'weight': 0.0 # Will be calculated after total_value + } + + # Calculate weights + if total_value > 0: + for ticker in positions: + positions[ticker]['weight'] = (positions[ticker]['value'] / total_value) * 100 + + return { + 'total_value': total_value, + 'positions': positions, + 'last_updated': self._last_update.isoformat() if self._last_update else None + } + + def get_portfolio_summary(self) -> _pd.DataFrame: + """ + Get a pandas DataFrame summary of the portfolio. + + Returns: + DataFrame with ticker, shares, price, value, and weight columns + """ + portfolio_data = self.get_portfolio_value() + positions = portfolio_data['positions'] + + if not positions: + return _pd.DataFrame() + + data = [] + for ticker, info in positions.items(): + data.append({ + 'Ticker': ticker, + 'Shares': info['shares'], + 'Price': info['price'], + 'Value': info['value'], + 'Weight %': info['weight'] + }) + + df = _pd.DataFrame(data) + df = df.sort_values('Value', ascending=False) + return df + + def export_to_csv(self, filename: str): + """ + Export portfolio to CSV file. + + Args: + filename: Output CSV filename + """ + df = self.get_portfolio_summary() + df.to_csv(filename, index=False) + + def export_to_json(self, filename: str): + """ + Export portfolio to JSON file. + + Args: + filename: Output JSON filename + """ + portfolio_data = self.get_portfolio_value() + with open(filename, 'w') as f: + _json.dump(portfolio_data, f, indent=2) + + def get_performance(self, period: str = '1d') -> Dict[str, Dict]: + """ + Get performance metrics for portfolio holdings. + + Args: + period: Time period ('1d', '5d', '1mo', '3mo', '6mo', '1y', 'ytd', 'max') + + Returns: + Dictionary with performance data for each holding + """ + performance = {} + + for ticker in self.holdings.keys(): + try: + ticker_obj = Ticker(ticker) + hist = ticker_obj.history(period=period) + + if not hist.empty: + current_price = hist['Close'].iloc[-1] + start_price = hist['Close'].iloc[0] + change = current_price - start_price + change_pct = (change / start_price) * 100 if start_price > 0 else 0 + + performance[ticker] = { + 'start_price': start_price, + 'current_price': current_price, + 'change': change, + 'change_pct': change_pct, + 'shares': self.holdings[ticker], + 'position_change': change * self.holdings[ticker], + 'position_change_pct': change_pct + } + except Exception as e: + performance[ticker] = {'error': str(e)} + + return performance + + +def format_for_cashapp(ticker: str, data_type: str = 'quote') -> Dict: + """ + Format yfinance data in a Cash App-friendly format. + + Args: + ticker: Stock ticker symbol + data_type: Type of data to format ('quote', 'history', 'info') + + Returns: + Dictionary with formatted data + """ + ticker_obj = Ticker(ticker) + + if data_type == 'quote': + info = ticker_obj.fast_info + quote_data = ticker_obj.info + + return { + 'symbol': ticker.upper(), + 'name': quote_data.get('longName', ticker.upper()), + 'price': info.get('lastPrice', 0), + 'change': info.get('regularMarketChange', 0), + 'change_percent': info.get('regularMarketChangePercent', 0), + 'volume': info.get('regularMarketVolume', 0), + 'market_cap': quote_data.get('marketCap', 0), + 'currency': quote_data.get('currency', 'USD') + } + + elif data_type == 'history': + hist = ticker_obj.history(period='1mo') + if hist.empty: + return {} + + return { + 'symbol': ticker.upper(), + 'dates': [d.isoformat() for d in hist.index], + 'prices': hist['Close'].tolist(), + 'volumes': hist['Volume'].tolist() + } + + elif data_type == 'info': + info = ticker_obj.info + return { + 'symbol': ticker.upper(), + 'name': info.get('longName', ticker.upper()), + 'sector': info.get('sector', ''), + 'industry': info.get('industry', ''), + 'description': info.get('longBusinessSummary', ''), + 'website': info.get('website', ''), + 'employees': info.get('fullTimeEmployees', 0) + } + + else: + raise ValueError(f"Unknown data_type: {data_type}. Use 'quote', 'history', or 'info'") + + +def get_cashapp_watchlist(tickers: List[str]) -> _pd.DataFrame: + """ + Create a watchlist DataFrame formatted for Cash App users. + + Args: + tickers: List of ticker symbols + + Returns: + DataFrame with watchlist data + """ + data = [] + + for ticker in tickers: + try: + formatted = format_for_cashapp(ticker, 'quote') + data.append(formatted) + except Exception as e: + data.append({ + 'symbol': ticker.upper(), + 'name': ticker.upper(), + 'price': 0, + 'change': 0, + 'change_percent': 0, + 'volume': 0, + 'market_cap': 0, + 'currency': 'USD', + 'error': str(e) + }) + + df = _pd.DataFrame(data) + return df + + +def calculate_dollar_cost_average(ticker: str, + amount: float, + frequency: str = 'monthly', + start_date: Optional[str] = None, + end_date: Optional[str] = None) -> Dict: + """ + Calculate dollar-cost averaging (DCA) strategy results for Cash App users. + + Args: + ticker: Stock ticker symbol + amount: Dollar amount to invest per period + frequency: Investment frequency ('daily', 'weekly', 'monthly') + start_date: Start date (YYYY-MM-DD format) + end_date: End date (YYYY-MM-DD format) + + Returns: + Dictionary with DCA results + """ + ticker_obj = Ticker(ticker) + + # Determine period based on dates + if start_date and end_date: + hist = ticker_obj.history(start=start_date, end=end_date) + else: + # Default to 1 year if no dates provided + hist = ticker_obj.history(period='1y') + + if hist.empty: + return {'error': 'No historical data available'} + + # Map frequency to pandas resample frequency + freq_map = { + 'daily': 'D', + 'weekly': 'W', + 'monthly': 'M' + } + + resample_freq = freq_map.get(frequency.lower(), 'M') + + # Resample to get period-end prices + resampled = hist['Close'].resample(resample_freq).last() + + total_invested = 0 + total_shares = 0 + transactions = [] + + for date, price in resampled.items(): + shares_bought = amount / price + total_invested += amount + total_shares += shares_bought + + transactions.append({ + 'date': date.isoformat(), + 'price': price, + 'amount': amount, + 'shares': shares_bought, + 'cumulative_shares': total_shares, + 'cumulative_invested': total_invested + }) + + current_price = hist['Close'].iloc[-1] + current_value = total_shares * current_price + total_return = current_value - total_invested + total_return_pct = (total_return / total_invested) * 100 if total_invested > 0 else 0 + + # Calculate average cost basis + avg_cost = total_invested / total_shares if total_shares > 0 else 0 + + return { + 'ticker': ticker.upper(), + 'strategy': 'Dollar-Cost Averaging', + 'frequency': frequency, + 'amount_per_period': amount, + 'start_date': resampled.index[0].isoformat() if not resampled.empty else None, + 'end_date': resampled.index[-1].isoformat() if not resampled.empty else None, + 'total_periods': len(transactions), + 'total_invested': total_invested, + 'total_shares': total_shares, + 'average_cost_basis': avg_cost, + 'current_price': current_price, + 'current_value': current_value, + 'total_return': total_return, + 'total_return_pct': total_return_pct, + 'transactions': transactions + } + + +def get_cashapp_recommendations(ticker: str) -> Dict: + """ + Get investment recommendations formatted for Cash App users. + + Args: + ticker: Stock ticker symbol + + Returns: + Dictionary with recommendations and analysis + """ + ticker_obj = Ticker(ticker) + + try: + info = ticker_obj.info + recommendations = ticker_obj.recommendations + + # Get analyst recommendations + rec_summary = {} + if recommendations is not None and not recommendations.empty: + rec_summary = { + 'strong_buy': len(recommendations[recommendations == 'Strong Buy']), + 'buy': len(recommendations[recommendations == 'Buy']), + 'hold': len(recommendations[recommendations == 'Hold']), + 'sell': len(recommendations[recommendations == 'Sell']), + 'strong_sell': len(recommendations[recommendations == 'Strong Sell']) + } + + # Get price targets + targets = ticker_obj.analyst_price_targets + target_price = targets.get('targetMeanPrice', [0])[0] if targets else 0 + current_price = ticker_obj.fast_info.get('lastPrice', 0) + + upside_potential = ((target_price - current_price) / current_price * 100) if current_price > 0 else 0 + + return { + 'ticker': ticker.upper(), + 'current_price': current_price, + 'target_price': target_price, + 'upside_potential_pct': upside_potential, + 'recommendation_summary': rec_summary, + 'recommendation': info.get('recommendationKey', 'N/A'), + 'recommendation_mean': info.get('recommendationMean', {}), + 'risk_level': 'Moderate', # Placeholder - Cash App doesn't provide risk ratings + 'suitable_for': ['Long-term investors', 'DCA strategy'] + } + except Exception as e: + return {'error': str(e), 'ticker': ticker.upper()}