# source: https://raw.githubusercontent.com/witooh/else/68abebdaa95ad34882d5a5036ccdf427b3d7bf59/user_data/strategies/BTCAccumulator/BTCAccumulator.py
"""
Github_witooh_else__BTCAccumulator__20260306_110129 — Salary DCA, Hold Forever

Strategy:
  Entry:  Weekly Supertrend bullish → buy BTC with initial deposit
  DCA:    Every 30 days from last buy — full monthly deposit, regardless of market regime
  Hold:   Never sell.

Budget system:
  Simulates monthly salary deposits for accurate backtesting.
  cumulative_budget = initial_deposit + (months_elapsed × monthly_deposit)
  DCA only allowed when total_invested < cumulative_budget.

User workflow:
  - Deposit USDT on the 24th of each month
  - Bot auto-manages buying/DCA
  - Starting capital: ~$400, monthly add: ~$840 (~30,000 THB)
"""

from datetime import datetime

import pandas as pd
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, merge_informative_pair

import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from indicators import calculate_supertrend


class Github_witooh_else__BTCAccumulator__20260306_110129(IStrategy):
    INTERFACE_VERSION = 3

    timeframe = "1d"
    startup_candle_count = 100

    can_short = False

    stoploss = -0.99  # effectively disabled — hold through bear markets
    trailing_stop = False
    use_custom_stoploss = False

    minimal_roi = {"0": 100000}  # never hit

    use_exit_signal = False  # never sell via signal
    process_only_new_candles = True

    position_adjustment_enable = True
    max_entry_position_adjustment = -1  # unlimited DCA

    st_period = 10
    st_multiplier = 3.0
    dca_interval_days = 30
    reminder_day = 25  # day of month to send DCA reminder (salary arrives on 24th)

    # Fallback defaults — overridden by config
    initial_deposit = 400.0
    monthly_deposit = 840.0

    def bot_start(self, **kwargs) -> None:
        self.initial_deposit = self.config.get("initial_deposit", self.initial_deposit)
        self.monthly_deposit = self.config.get("monthly_deposit", self.monthly_deposit)
        self._last_reminder_month: int = 0
        self._last_summary_week: int = 0

    def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
        year_month = current_time.year * 100 + current_time.month
        iso_year, iso_week, _ = current_time.isocalendar()
        year_week = iso_year * 100 + iso_week

        # DCA Reminder: salary day + 1
        if current_time.day == self.reminder_day and year_month != self._last_reminder_month:
            self._last_reminder_month = year_month
            self._send_dca_reminder(current_time)

        # Weekly Summary: every Sunday
        if current_time.weekday() == 6 and year_week != self._last_summary_week:
            self._last_summary_week = year_week
            self._send_weekly_summary(current_time)

    def _get_open_trade(self):
        trades = Trade.get_trades_proxy(is_open=True)
        return trades[0] if trades else None

    def _get_current_rate(self, pair: str) -> float | None:
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        if dataframe is not None and not dataframe.empty:
            return dataframe.iloc[-1]["close"]
        return None

    def _send_dca_reminder(self, current_time: datetime) -> None:
        trade = self._get_open_trade()
        if trade:
            budget = self._cumulative_budget(trade, current_time)
            remaining = budget - trade.stake_amount
            current_rate = self._get_current_rate(trade.pair)
            profit_line = ""
            if current_rate and trade.amount > 0:
                avg_cost = trade.stake_amount / trade.amount
                profit_pct = ((current_rate / avg_cost) - 1) * 100
                profit_line = f"\nกำไร: {profit_pct:+.1f}%"
            msg = (
                f"*DCA Reminder*\n"
                f"เงินเดือนเข้าแล้ว เตรียมโอน ${self.monthly_deposit:.0f} เข้า Exchange\n"
                f"ต้นทุนสะสม: ${trade.stake_amount:.0f}\n"
                f"งบคงเหลือ: ${remaining:.0f}"
                f"{profit_line}"
            )
        else:
            msg = (
                f"*DCA Reminder*\n"
                f"เงินเดือนเข้าแล้ว เตรียมโอน ${self.monthly_deposit:.0f} เข้า Exchange\n"
                f"ยังไม่มี trade เปิด — รอสัญญาณ entry"
            )
        self.dp.send_msg(msg)

    def _send_weekly_summary(self, current_time: datetime) -> None:
        trade = self._get_open_trade()
        if not trade:
            return
        current_rate = self._get_current_rate(trade.pair)
        if not current_rate:
            return
        budget = self._cumulative_budget(trade, current_time)
        remaining = budget - trade.stake_amount
        days_open = (current_time - trade.open_date_utc).days
        months_open = days_open // 30
        current_value = trade.amount * current_rate
        avg_cost = trade.stake_amount / trade.amount
        profit_pct = ((current_rate / avg_cost) - 1) * 100
        msg = (
            f"*Weekly Summary*\n"
            f"ระยะเวลาสะสม: {months_open} เดือน ({days_open} วัน)\n"
            f"ต้นทุนทั้งหมด: ${trade.stake_amount:.0f}\n"
            f"มูลค่าปัจจุบัน: ${current_value:.0f} ({profit_pct:+.1f}%)\n"
            f"BTC สะสม: {trade.amount:.6f}\n"
            f"งบคงเหลือ: ${remaining:.0f}"
        )
        self.dp.send_msg(msg)

    def informative_pairs(self):
        return [(pair, "1w") for pair in self.dp.current_whitelist()]

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        pair_1w = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe="1w")
        if pair_1w is not None and not pair_1w.empty:
            _, wst_dir = calculate_supertrend(pair_1w, self.st_period, self.st_multiplier)
            pair_1w["weekly_st_dir"] = wst_dir
            pair_1w = pair_1w[["date", "weekly_st_dir"]]
            dataframe = merge_informative_pair(
                dataframe, pair_1w, self.timeframe, "1w", ffill=True
            )
        else:
            dataframe["weekly_st_dir_1w"] = 1

        return dataframe

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        bullish = (dataframe["weekly_st_dir_1w"] == 1) & (dataframe["volume"] > 0)
        dataframe.loc[bullish, "enter_long"] = 1
        dataframe.loc[bullish, "enter_tag"] = "accumulate"
        return dataframe

    def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        return dataframe

    def _cumulative_budget(self, trade, current_time) -> float:
        """Total USDT that should be available by now (initial + monthly deposits)."""
        days_since_open = (current_time - trade.open_date_utc).days
        months_elapsed = days_since_open // self.dca_interval_days
        return self.initial_deposit + (months_elapsed * self.monthly_deposit)

    def adjust_trade_position(
        self,
        trade,
        current_time,
        current_rate: float,
        current_profit: float,
        min_stake,
        max_stake: float,
        current_entry_rate: float,
        current_exit_rate: float,
        current_entry_profit: float,
        current_exit_profit: float,
        **kwargs,
    ):
        """
        DCA every 30 days from last buy. Full monthly deposit each time.
        Budget-gated: won't spend more than cumulative deposits allow.
        """
        # Time gate: 30 days since last filled buy order
        filled_buys = [
            o for o in trade.orders
            if o.ft_order_side == "buy" and o.status == "filled"
        ]
        if filled_buys:
            last_buy_date = max(o.order_filled_date for o in filled_buys)
            if (current_time - last_buy_date).days < self.dca_interval_days:
                return None

        # Budget gate: don't spend more than cumulative deposits
        budget = self._cumulative_budget(trade, current_time)
        remaining = budget - trade.stake_amount
        if remaining <= 0:
            return None

        # Full monthly deposit, capped by remaining budget and max_stake
        stake = min(self.monthly_deposit, remaining, max_stake)
        return stake if stake >= min_stake else None
