Overall Statistics
Total Trades
1358
Average Win
0.32%
Average Loss
-0.05%
Compounding Annual Return
7.660%
Drawdown
11.000%
Expectancy
0.351
Net Profit
18.058%
Sharpe Ratio
0.853
Loss Rate
83%
Win Rate
17%
Profit-Loss Ratio
7.06
Alpha
0.085
Beta
-0.292
Annual Standard Deviation
0.074
Annual Variance
0.005
Information Ratio
-0.066
Tracking Error
0.173
Treynor Ratio
-0.216
Total Fees
$2073.73
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using System.Collections.Generic;
using System.Linq;

namespace QuantConnect.Algorithm.CSharp
{
    public sealed class FactorLongShortStrategy : QCAlgorithm
    {
        private const int NumCoarse = 250;
        private const int NumFine = 20; //actually this is num long positions (same number of shorts)
        private const decimal MaxLeverage = 2; //IB overnight

        private bool _rebalance = true;
        private bool _firstMonth = true;
        private Symbol[] _long;
        private Symbol[] _short;

        public override void Initialize()
        {
            SetStartDate(2015, 1, 1);
            SetEndDate(2017, 4, 1);
            SetCash(100000);

            ActualInitialization();
        }

        private void ActualInitialization()
        {
            //SetBrokerageModel(Brokerages.BrokerageName.InteractiveBrokersBrokerage);

            var res = Resolution.Daily;

            var spy = AddSecurity(SecurityType.Equity, "SPY", res, true, MaxLeverage, false);
            Schedule.On(DateRules.MonthStart(spy.Symbol), TimeRules.AfterMarketOpen(spy.Symbol, 5), Rebalance);

            UniverseSettings.Leverage = MaxLeverage;
            UniverseSettings.Resolution = res;
            AddUniverse(CoarseSelection, FineSelection);
        }

        private IEnumerable<Symbol> CoarseSelection(IEnumerable<CoarseFundamental> coarse)
        {
            if (!_rebalance)
                return new List<Symbol>();

            var universe = coarse
                .Where(x => x.HasFundamentalData)
                .Where(x => x.Price > 5)
                .OrderByDescending(x => x.DollarVolume)
                ;

            return universe.Select(x => x.Symbol).Take(NumCoarse);
        }

        private sealed class SymbolRanking
        {
            public readonly Symbol Symbol;
            public int Rank1, Rank2, Rank3;
            public SymbolRanking(Symbol s)
            {
                Symbol = s;
            }

            public double GetScore()
            {
                return Rank1 * 0.2 + Rank2 * 0.4 + Rank3 * 0.4;
            }
        }

        private IEnumerable<Symbol> FineSelection(IEnumerable<FineFundamental> fine)
        {
            if (!_rebalance)
                return new List<Symbol>();
            _rebalance = false;

            var filteredFine = fine
                .Where(x => x.OperationRatios.OperationMargin.Value != 0)
                .Where(x => x.ValuationRatios.PriceChange1M != 0)
                .Where(x => x.ValuationRatios.BookValuePerShare != 0)
                ;

            Log("Remained to select: " + filteredFine.Count());

            var sortedByFactor1 = filteredFine.OrderByDescending(
                x => x.OperationRatios.OperationMargin.Value);
            var sortedByFactor2 = filteredFine.OrderByDescending(
                x => x.ValuationRatios.PriceChange1M);
            var sortedByFactor3 = filteredFine.OrderByDescending(
                x => x.ValuationRatios.BookValuePerShare);

            var rankings = new Dictionary<Symbol, SymbolRanking>();
            foreach (var f in fine)
                rankings[f.Symbol] = new SymbolRanking(f.Symbol);

            int index = 0;
            foreach (var f in sortedByFactor1)
                rankings[f.Symbol].Rank1 = index++;

            index = 0;
            foreach (var f in sortedByFactor2)
                rankings[f.Symbol].Rank2 = index++;

            index = 0;
            foreach (var f in sortedByFactor3)
                rankings[f.Symbol].Rank3 = index++;

            var sortedRankings = rankings.Values.OrderBy(r => r.GetScore());
            if (sortedRankings.Count() < NumFine * 2)
            {
                //TODO: this case should probably be handled with more than a warning...
                Log("Warning: Not enough symbols from fine filtered list");
            }

            _long = sortedRankings.Reverse()
                .Take(NumFine).Select(x => x.Symbol).ToArray();
            _short = sortedRankings
                .Take(NumFine).Select(x => x.Symbol).ToArray();

            return _long.Union(_short);
        }

        private void Rebalance()
        {
            if (_firstMonth)
            {
                _firstMonth = false;
                return;
            }

            var longShort = _long.Union(_short).ToArray();
            foreach (var security in Portfolio.Values)
                if (security.Invested && !longShort.Contains(security.Symbol))
                    Liquidate(security.Symbol);

            foreach (var symbol in _long)
                SetHoldings(symbol, 0.5 / NumFine);

            foreach (var symbol in _long)
                SetHoldings(symbol, -0.5 / NumFine);

            _rebalance = true;
        }
    }
}