Overall Statistics
Total Orders
288
Average Win
0.47%
Average Loss
-0.38%
Compounding Annual Return
29.702%
Drawdown
5.700%
Expectancy
0.165
Start Equity
100000
End Equity
109060.67
Net Profit
9.061%
Sharpe Ratio
1.362
Sortino Ratio
1.334
Probabilistic Sharpe Ratio
67.537%
Loss Rate
48%
Win Rate
52%
Profit-Loss Ratio
1.24
Alpha
0.132
Beta
0.372
Annual Standard Deviation
0.11
Annual Variance
0.012
Information Ratio
0.814
Tracking Error
0.123
Treynor Ratio
0.404
Total Fees
$633.92
Estimated Strategy Capacity
$24000000.00
Lowest Capacity Asset
AAPL R735QTJ8XC9X
Portfolio Turnover
235.15%
Drawdown Recovery
73
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Portfolio.SignalExports;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Algorithm.Selection;
    using QuantConnect.Api;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Commands;
    using QuantConnect.Configuration;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Auxiliary;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.Data.Custom.IconicTypes;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.Shortable;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.OptionExercise;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Orders.TimeInForces;
    using QuantConnect.Python;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Positions;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;
    using QuantConnect.Securities.CryptoFuture;
    using QuantConnect.Securities.IndexOption;
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Securities.Volatility;
    using QuantConnect.Storage;
    using QuantConnect.Statistics;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
    using Calendar = QuantConnect.Data.Consolidators.Calendar;
#endregion
using System.Text.RegularExpressions;


namespace QuantConnect.Algorithm.CSharp
{
    public class BenzingaNewsDataAlgorithm : QCAlgorithm
    {
        public override void Initialize()
        {
            SetStartDate(2024, 9, 1);
            SetEndDate(2024, 12, 31);
            SetCash(100000);
            
            AddUniverseSelection(
                new ManualUniverseSelectionModel(
                    QuantConnect.Symbol.Create("AAPL", SecurityType.Equity, Market.USA)
            ));
            
            AddAlpha(new BenzingaAlphaModel());
            
            SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel());
            
            AddRiskManagement(new NullRiskManagementModel());
            
            SetExecution(new ImmediateExecutionModel());
        }
    }

    public class BenzingaAlphaModel : AlphaModel
    {
        private List<Security> _securities = [];
        // A custom word-score map for calculating the total sentiment score
        private readonly Dictionary<string, int> _wordScores = new(){
            {"good", 1}, {"great", 1}, {"best", 1}, {"growth", 1},
            {"bad", -1}, {"terrible", -1}, {"worst", -1}, {"loss", -1}
        };

        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice slice)
        {
            var insights = new List<Insight>();

            foreach (var (benzingaSymbol, newsItem) in slice.Get<BenzingaNews>())
            {               
                var security = algorithm.Securities[benzingaSymbol.Underlying];
                var contentWords = newsItem.Contents.ToLower();
                var score = 0;

                foreach (var entry in _wordScores)
                {
                    score += Regex.Matches(contentWords, entry.Key).Count * entry.Value;
                }
                var targetDirection = score > 0 ? InsightDirection.Up : InsightDirection.Flat;
                security.Set("TargetDirection", targetDirection);
            }
            
            // Generate insights based on direction changes
            foreach (var security in _securities)
            {
                if (!(slice.ContainsKey(security) && slice[security] != null && !slice[security].IsFillForward)) continue;
                
                var targetDirection = security.Get<InsightDirection>("TargetDirection");
                
                // Buy or sell if the sentiment has changed from our current holdings
                if (security.Get<InsightDirection>("CurrentDirection") != targetDirection)
                {
                    insights.Add(Insight.Price(security, TimeSpan.FromDays(14), targetDirection));
                    security.Set("CurrentDirection", targetDirection);
                }
            }
            return insights;
        }

        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var security in changes.AddedSecurities)
            {
                // Requesting data to obtain the updated news for sentiment score calculation
                var benzingaSymbol = algorithm.AddData<BenzingaNews>(security.Symbol).Symbol;
                security.Set("CurrentDirection", InsightDirection.Flat);
                security.Set("TargetDirection", InsightDirection.Flat);

                // Historical data
                var history = algorithm.History<BenzingaNews>(benzingaSymbol, 14, Resolution.Daily);
                algorithm.Debug($"We got {history.Count()} items from our history request");
                _securities.Add(security);
                security.Set("BenzingaSymbol", benzingaSymbol);
            }

            foreach (var security in changes.RemovedSecurities)
            {
                if (_securities.Contains(security))
                {
                    algorithm.RemoveSecurity(security.Get<Symbol>("BenzingaSymbol"));
                    _securities.Remove(security);
                }
            }
        }
    }
}