# source: https://raw.githubusercontent.com/NTZ12345678/freqtrade_chinese_discord/b3a26ed121ec16f7ec3aa362a4cf4ca744cab55b/策略相关/马丁/SL.py
from datetime import datetime, timedelta
import talib.abstract as ta
import pandas_ta as pta
from freqtrade.strategy import merge_informative_pair
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
from freqtrade.strategy import DecimalParameter, IntParameter
from functools import reduce
import pandas as pd
import warnings
from freqtrade.exchange import date_minus_candles

import logging

logger = logging.getLogger(__name__)


# 纯山寨币做空策略 策略风险较大，适合大牛市开始回撤时使用   讨论群 https://discord.gg/hfxRh74mK7

'''
dryrun



ROI: Closed trades
∙ 520.179 USDT (67.99%) (26.27 Σ%)
∙ 520.179 USD
ROI: All trades
∙ 454.301 USDT (57.85%) (22.94 Σ%)
∙ 454.301 USD
Total Trade Count: 1129
Bot started: 2025-08-01 09:58:11
First Trade opened: 3 months ago (2025-08-01 10:12:56)
Latest Trade opened: 34 minutes ago (2025-11-03 01:06:45)
Win / Loss: 1060 / 0
Winrate: 100.00%
Expectancy (Ratio): 0.49 (100.00)
Avg. Duration: 3 days, 23:14:35
Best Performing: ATA/USDT:USDT: 7.513 USDT (69.14%)
Trading volume: 31252.268 USDT
Profit factor: inf
Max Drawdown: 0.00% (0 USDT)
    from 2025-08-02 10:13:49 (0.554 USDT)
    to 2025-08-02 10:13:49 (0.554 USDT)


Monday (count)    USDT         USD        Profit %
----------------  -----------  ---------  ----------
2025-11-03 (16)   8.578 USDT   8.58 USD   0.34%
2025-10-27 (129)  73.535 USDT  73.54 USD  3.05%
2025-10-20 (70)   35.088 USDT  35.09 USD  1.48%
2025-10-13 (228)  97.9 USDT    97.90 USD  4.29%
2025-10-06 (119)  75.842 USDT  75.84 USD  3.44%
2025-09-29 (71)   30.198 USDT  30.20 USD  1.39%
2025-09-22 (117)  64.642 USDT  64.64 USD  3.06%
2025-09-15 (41)   16.154 USDT  16.15 USD  0.77%
2025-09-08 (17)   11.559 USDT  11.56 USD  0.56%
2025-09-01 (62)   23.449 USDT  23.45 USD  1.14%
2025-08-25 (48)   19.565 USDT  19.57 USD  0.96%
2025-08-18 (67)   25.629 USDT  25.63 USD  1.27%
2025-08-11 (33)   15.018 USDT  15.02 USD  0.75%
2025-08-04 (33)   15.559 USDT  15.56 USD  0.78%
2025-07-28 (9)    7.466 USDT   7.47 USD   0.38%
2025-07-21 (0)    0 USDT       0.00 USD   0.00%
2025-07-14 (0)    0 USDT       0.00 USD   0.00%
2025-07-07 (0)    0 USDT       0.00 USD   0.00%
2025-06-30 (0)    0 USDT       0.00 USD   0.00%
2025-06-23 (0)    0 USDT       0.00 USD   0.00%






'''


class Github_NTZ12345678_freqtrade_chinese_discord__SL__20251103_015658(IStrategy):
    can_short = True
    timeframe = '1d'  # 
    startup_candle_count = 30  # 需要至少30根K线
    process_only_new_candles = True  # 只处理新K线

    entry_step_pct = -5
    entry_stake_amount = 6
    num_entry = 300
    # 交易参数
    stoploss = -10000
    use_custom_stoploss = False  # 启用动态止损
    REAL_USE_MUL = 10
    liqutation_ratio = 0.0

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        current_pair = metadata['pair']
        whitelist = self.dp.current_whitelist()
        new_cols = {}
        for pair in whitelist:
            pair_df = self.dp.get_pair_dataframe(pair=pair, timeframe=self.timeframe)
            new_cols[f'{pair}_1d_per'] = (pair_df['close'] / pair_df['close'].shift(1)) - 1
        # 合并所有新列
        new_cols_df = pd.DataFrame(new_cols, index=dataframe.index)
        dataframe = pd.concat([dataframe, new_cols_df], axis=1)
        # 只保留实际存在的列
        valid_cols = [col for col in [f'{pair}_1d_per' for pair in whitelist] if col in dataframe.columns]
        current_col = f'{current_pair}_1d_per'
        if not valid_cols or current_col not in valid_cols:
            dataframe['momentum_rank'] = None
        else:
            dataframe['momentum_rank'] = dataframe[valid_cols].rank(axis=1, ascending=False)[current_col]
        dataframe['ma60'] = ta.SMA(dataframe, timeperiod=60)  # 计算60日均线
        return dataframe


    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        """
        生成买入信号：
        - 做多排名前 2 的币种
        - 做空排名后 2 的币种
        """
        #dataframe.loc[dataframe['momentum_rank'] <= 2, 'enter_long'] = 1  # 排名前 2 的做多
                # 空头条件
        enter_short_conditions = [
            dataframe['momentum_rank'] <= 10,
            dataframe['close'] < dataframe['ma60'],
        ]
        if enter_short_conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, enter_short_conditions), ["enter_short", "enter_tag"]
            ] = (1, "short")
        #return dataframe
    
        #dataframe.loc[dataframe['momentum_rank'] >= dataframe['momentum_rank'].max() - 1, 'enter_short'] = 1  # 排名后 2 的做空
        return dataframe

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        dataframe['exit_long'] = 0  
        dataframe['exit_short'] = 0  
        return dataframe  

    def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
                        current_profit: float, **kwargs):

            exit_time = trade.open_date_utc + timedelta(days=1)
            exit_time1 = trade.open_date_utc + timedelta(days=100)  
            exit_time2 = trade.open_date_utc + timedelta(days=200)  
            if current_time >= exit_time and current_profit > 0.5:
                return "profit_50%_time_exit"
            if current_time >= exit_time1 and current_profit > 0:
                return "time_exit_100day"
            #if current_time >= exit_time2 :
            #    return "time_exit_200day"

            open_trades = Trade.get_trades_proxy(is_open=True)
            all_profits = {}
            total_profit_amount = 0.0

            for open_trade in open_trades:
                profit_ratio = 0.0

                if open_trade.pair == pair:
                    profit_ratio = current_profit
                else:
                    other_pair = open_trade.pair
                    (dataframe, _) = self.dp.get_analyzed_dataframe(pair=other_pair, timeframe=self.timeframe)

                    if not dataframe.empty:
                        last_candle = dataframe.iloc[-1]
                        rate_for_other_pair = last_candle['close']

                        if open_trade.is_short:
                            # Short position profit ratio
                            profit_ratio = (1 - (rate_for_other_pair / open_trade.open_rate))
                        else:
                            # Long position profit ratio
                            profit_ratio = ((rate_for_other_pair / open_trade.open_rate) - 1)

                        profit_ratio *= open_trade.leverage

                profit_pct = profit_ratio * 100
                profit_amount = open_trade.stake_amount * profit_ratio
                total_profit_amount += profit_amount

                all_profits[open_trade.pair] = f"{profit_pct:.2f}% ({profit_amount:.2f} {open_trade.stake_currency})"

            ratio = total_profit_amount / self.wallets.get_total('USDT')
            if ratio < -1:
                logger.warning(f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.")
                logger.warning(f"Total profit ratio is too low!!!!!!liqutation_ratio!!!!!!!!: {ratio:.2%}.")
                logger.warning(f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.")
                return None
            self.liqutation_ratio = ratio

            if pair == open_trades[0].pair:
                #logger.info(f"--- Backtest time: {current_time} ---")
                #logger.info(f"Current profit for all open trades: {all_profits}")
                #logger.info(f"Total profit amount for all open trades: {total_profit_amount:.2f} USDT")
                logger.info(f"Total profit ratio: {ratio:.2%}")
                #logger.info(f"trade number:{len(all_profits)}")
                #logger.info("---------------------------------")

            return None
    
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
                            proposed_stake: float, min_stake: float | None, max_stake: float,
                            leverage: float, entry_tag: str | None, side: str,
                            **kwargs) -> float:
        #logger.info(f"Calculating custom stake amount for {pair} at {current_time}: self.entry_stake_amount={self.entry_stake_amount}")
        return self.entry_stake_amount / self.REAL_USE_MUL

    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
                            time_in_force: str, side: str, **kwargs) -> bool:
        if self.liqutation_ratio < -0.5:
            logger.warning(f"Liquidity ratio is too low ({self.liqutation_ratio}), skipping trade entry for {pair}.")
            return False
        logger.info(f"Confirming trade entry for {pair}: order_type={order_type}, amount={amount}, rate={rate}, side={side}")
        return True

    position_adjustment_enable = True

    def adjust_trade_position(self, trade: Trade, current_time: datetime,current_rate: float, current_profit: float,min_stake: float | None, max_stake: float,
                              current_entry_rate: float, current_exit_rate: float,current_entry_profit: float, current_exit_profit: float,**kwargs
                              ) -> float | None | tuple[float | None, str | None]:
        current_profit_stake = current_profit * trade.stake_amount
        current_profit_stake_ratio = current_profit_stake /  self.wallets.get_total('USDT')
        #logger.info(f"Adjusting position for {trade.pair} at {current_time}: current_profit={current_profit},trade.stake_amount={trade.stake_amount} current_profit_stake={current_profit_stake} current_profit_stake_ratio = {current_profit_stake_ratio}")
        last_time = trade.date_last_filled_utc + timedelta(days=7)
        if self.wallets:
            free_eth = 0.95*self.wallets.get_free('USDT')
            used_eth = self.wallets.get_used('USDT')
            total_eth = self.wallets.get_total('USDT')
            ratio = self.wallets.get_free('USDT') / self.wallets.get_total('USDT')
            #logger.info(f"Free USDT: {free_eth}, Used USDT: {used_eth}, Total USDT: {total_eth}")
            if trade.nr_of_successful_entries == 1:
                self.entry_stake_amount = max(6, free_eth / self.num_entry)
                #self.entry_stake_amount = 6
                #logger.info(f"Adjusting entry stake amount to {self.entry_stake_amount} USDT based on available balance.,Free USDT: {free_eth}")
        if current_profit < self.entry_step_pct and trade.nr_of_successful_entries != self.num_entry and current_time > last_time  and  current_profit_stake_ratio > -0.05 :
            #logger.info(f"Adjusting position for {trade.pair} at {current_time}: current_profit={current_profit}, nr_of_successful_entries={trade.nr_of_successful_entries}")
            if trade.nr_of_successful_entries < self.num_entry:
                logger.info(f"Adjusting position for {trade.pair} at {current_time}: current_profit={current_profit}, nr_of_successful_entries={trade.nr_of_successful_entries}, entry_stake_amount={self.entry_stake_amount}")
            return self.entry_stake_amount  / self.REAL_USE_MUL
        
        return None

    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
                           rate: float, time_in_force: str, exit_reason: str,
                           exit_tag: str = None, **kwargs) -> bool:
        #logger.info(f"exit-----------------------------------------------------------------------------exit")
        if self.wallets:
            free_usdt = 0.95*self.wallets.get_free('USDT')
            used_usdt = self.wallets.get_used('USDT')
            total_usdt = self.wallets.get_total('USDT')
        logger.info(f"Confirming trade exit for {pair}: order_type={order_type}, amount={amount}, rate={rate}, exit_reason={exit_reason}, free_usdt: {free_usdt}, used_usdt: {used_usdt}, total_usdt: {total_usdt}")

        return True


    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                 proposed_leverage: float, max_leverage: float, entry_tag: str, side: str,
                 **kwargs) -> float:
        return 20


              


    











