币圈老鸟都在用的 “做空神器”:永续合约,究竟是收割镰刀还是避险盾牌

  • 木西
  • 发布于 1天前
  • 阅读 63

前言本文围绕永续合约展开全面梳理,既涵盖核心理论知识,也包含完整的技术开发实践内容。理论部分系统梳理了永续合约的基础概念、核心运行机制、实际应用场景及风险提示;技术实践部分则基于HardhatV3开发框架,借助OpenZeppelin、Chainlink工具库及Solidity编程语言,从最小

前言

本文围绕永续合约展开全面梳理,既涵盖核心理论知识,也包含完整的技术开发实践内容。理论部分系统梳理了永续合约的基础概念、核心运行机制、实际应用场景及风险提示;技术实践部分则基于Hardhat V3开发框架,借助OpenZeppelin、Chainlink工具库及Solidity编程语言,从最小单位实现永续合约的开发、测试到部署的全流程。

1. 是什么:永续合约的定义

永续合约(Perpetual Contract)是一种特殊的加密货币期货合约,最大的特点是没有到期日或结算日

  • 传统期货 vs. 永续合约:

    • 传统期货: 像交割合约(Delivery Futures),有明确的到期日(如 BTC 0325,表示 3 月 25 日到期)。到期时,无论盈亏,合约都会强制平仓并以现货价格结算。
    • 永续合约: 你可以一直持有,直到你主动平仓。它更像是一个带有杠杆的现货市场
  • 核心目的: 让交易者能够长期持有某个方向的头寸(做多或做空),而不必担心合约到期被迫换仓。

2. 如何运行:核心机制揭秘

永续合约之所以能 “永续” 存在且不偏离现货价格,依赖于两个核心机制:资金费率(Funding Rate)标记价格(Mark Price)

2.1 资金费率(Funding Rate)—— 灵魂机制

这是永续合约最独特的地方。由于没有到期日,为了防止合约价格严重偏离现货价格(BTC/USD),交易所引入了资金费率。

  • 作用: 强制将合约价格拉回现货价格水平。

  • 谁付给谁?

    • 当合约价格 > 现货价格(溢价): 多头(买方)付给空头(卖方)。这会惩罚过度看涨的人,鼓励做空,从而压低价格。
    • 当合约价格 < 现货价格(折价): 空头(卖方)付给多头(买方)。这会惩罚过度看跌的人,鼓励做多,从而推高价格。
  • 支付频率: 通常每 8 小时一次(UTC 时间 00:00, 08:00, 16:00)。

    • 注意:如果你在这一瞬间没有持仓,就不需要支付或收取资金费。

2.2 标记价格(Mark Price)与强平

为了防止市场剧烈波动时的 “插针” 爆仓,永续合约不直接使用最新成交价作为强平依据,而是使用标记价格

  • 计算方式: 标记价格 ≈ 现货指数价格 + 资金费率基差。
  • 保护机制: 即使合约市场出现瞬间的恶意砸盘(价格瞬间归零),只要现货价格没动,你的仓位就不会被强制平仓。

2.3 杠杆(Leverage)

永续合约允许用户借贷资金进行交易。

  • 例如:10 倍杠杆意味着你用 100 USDT 可以买卖价值 1000 USDT 的合约。
  • 高风险高收益: 放大了盈利,也放大了亏损(包括资金费率的成本)。

3. 应用场景:为什么要用永续合约?

永续合约主要服务于投机、套利和对冲这三类需求。

场景一:杠杆投机(Speculation)

这是最常见的场景。交易者看好或看衰某种加密货币的短期走势。

  • 做多(Long): 认为 BTC 会涨,开 10 倍杠杆做多。如果涨 1%,你赚 10%(扣除手续费和资金费)。
  • 做空(Short): 认为 ETH 会跌,开永续合约做空。这在现货市场很难做到(通常需要借币卖出),但在永续合约中一键即可实现。

场景二:套期保值(Hedging)

矿工或大额持仓者用于规避现货下跌风险。

  • 例子: 某矿工手里有 100 个 BTC 现货。他担心明天政策利空导致币价暴跌。
  • 操作: 他在永续合约市场开一个等值的 100 BTC 空单。
  • 结果: 无论明天 BTC 是涨是跌,现货的亏损 / 盈利会被合约的盈利 / 亏损抵消,从而锁定当前的财富价值。

场景三:资金费率套利(Arbitrage)

利用资金费率的周期性进行低风险获利。

  • 正向套利(吃费率): 当资金费率很高(例如 + 0.1%,每 8 小时),说明多头在向空头付费。

    • 操作: 开空单,同时在现货买入等额币种。
    • 逻辑: 只要价格波动不大,你就能稳稳赚取多头支付的资金费。
  • 期现套利: 利用合约价格与现货价格的价差进行搬砖(虽然资金费率机制已经大大压缩了这种价差的空间)。

4. 总结与风险提示

一句话总结:永续合约是加密货币市场的 “涡轮增压器”,它通过资金费率这一巧妙的机制,让带杠杆的合约交易像现货一样永续存在,既满足了交易者以小博大的投机需求,也提供了做空对冲的金融工具。

⚠️ 风险提示:虽然机制精妙,但永续合约是高风险产品。

  1. 爆仓风险: 价格反向波动超过保证金比例,仓位会被强制平仓。
  2. 资金费率累积: 长期持有时,资金费可能会吃掉你的大部分利润(甚至本金)。
  3. 归零风险: 极端行情下(如 LUNA 事件),即使是老手也可能瞬间归零。

    5.智能合约开发、测试、部署

    智能合约

    代币合约

    
    // SPDX-License-Identifier: MIT
    // Compatible with OpenZeppelin Contracts ^5.5.0
    pragma solidity ^0.8.24;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract BoykaYuriToken is ERC20, ERC20Burnable, Ownable, ERC20Permit { constructor(address recipient, address initialOwner) ERC20("MyToken", "MTK") Ownable(initialOwner) ERC20Permit("MyToken") { _mint(recipient, 1000000 * 10 ** decimals()); } function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } }

### 喂价合约

**特殊说明**:需要说明的是,若合约部署在主链或测试链,可直接在Chainlink上查看合约地址及实例;而为便于本地测试,建议另行部署Mock喂价合约。

// SPDX-License-Identifier: MIT pragma solidity ^0.8.20;

import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

/**

  • @title MockV3Aggregator
  • @dev 用于本地测试环境模拟 Chainlink 预言机 */ contract MockV3Aggregator is AggregatorV3Interface { int256 private _answer; uint8 public immutable decimals;

    constructor(int256 initialAnswer, uint8 _decimals) { _answer = initialAnswer; decimals = _decimals; }

    function latestRoundData() external view override returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { return (1, _answer, block.timestamp, block.timestamp, 1); }

    // 辅助函数:更新模拟价格 function updateAnswer(int256 newAnswer) external { _answer = newAnswer; }

    // // 实现接口所需的所有其他视图函数(虽然在测试中可能用不到) // function description() external view override returns (string memory) { return "Mock ETH/USD"; } // function version() external view override returns (uint256) { return 3; } // function getRoundData(uint80) external view override returns (uint80, int256, uint256, uint256, uint80) { // revert("Not implemented"); // } // 修改后:通过读取 decimals 让编译器保持 view 属性 function description() external view override returns (string memory) { decimals; // 虚拟读取 return "Mock ETH/USD"; }

function version() external view override returns (uint256) { decimals; // 虚拟读取 return 3; }

function getRoundData(uint80) external view override returns (uint80, int256, uint256, uint256, uint80) { decimals; // 虚拟读取 revert("Not implemented"); } }

### 永续合约

// SPDX-License-Identifier: MIT pragma solidity ^0.8.27;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";

/**

  • @title PerpTrade
  • @dev 优化的永续合约 Demo,包含精度修复和 Gas 优化 */ contract PerpTrade is ReentrancyGuard, AccessControl { using SafeERC20 for IERC20;

    bytes32 public constant LIQUIDATOR_ROLE = keccak256("LIQUIDATOR_ROLE");

    uint256 public constant BPS_DENOMINATOR = 10000; // 基点分母 uint256 public constant PRECISION = 1e18; uint256 public constant MAX_LEVERAGE = 20 * PRECISION; uint256 public constant MAINTENANCE_MARGIN_BPS = 500; // 5% 维持保证金率

    IERC20 public immutable collateralToken; AggregatorV3Interface public immutable priceFeed; uint8 public immutable priceDecimals;

    struct Position { uint128 size; // 名义价值 (1e18) uint128 collateral; // 保证金数量 (1e18) uint64 entryPrice; // 喂价原始精度 uint64 lastUpdate; // 时间戳 bool isLong; bool isActive; }

    mapping(address => Position[]) public positions;

    event Opened(address indexed user, uint256 pid, bool isLong, uint256 size, uint256 collateral, uint256 price); event Closed(address indexed user, uint256 pid, uint256 exitPrice, int256 pnl); event Liquidated(address indexed user, uint256 pid, address liquidator, uint256 price, uint256 reward);

    constructor(address _collateral, address _priceFeed) { collateralToken = IERC20(_collateral); priceFeed = AggregatorV3Interface(_priceFeed); priceDecimals = priceFeed.decimals(); _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(LIQUIDATOR_ROLE, msg.sender); }

    /**

    • @notice 获取最新价格 (标准化为 1e18) / function getNormalizedPrice() public view returns (uint256) { (, int256 price,,,) = priceFeed.latestRoundData(); if (price <= 0) revert("Invalid Price"); return uint256(price) (10**(18 - priceDecimals)); }

    /**

    • @notice 开仓
    • @param isLong 是否看涨
    • @param collateralAmount 保证金数量 (token decimals)
    • @param leverage 杠杆倍数 (1e18 为 1x) */ function open(bool isLong, uint256 collateralAmount, uint256 leverage) external nonReentrant { require(leverage >= PRECISION && leverage <= MAX_LEVERAGE, "Invalid Leverage"); require(collateralAmount > 0, "Zero Collateral");

      // 转移保证金到合约 collateralToken.safeTransferFrom(msg.sender, address(this), collateralAmount);

      uint256 price = getNormalizedPrice(); uint256 size = (collateralAmount * leverage) / PRECISION;

      positions[msg.sender].push(Position({ size: uint128(size), collateral: uint128(collateralAmount), entryPrice: uint64(price / (10**(18 - priceDecimals))), // 存储原始精度节省空间 lastUpdate: uint64(block.timestamp), isLong: isLong, isActive: true }));

      emit Opened(msg.sender, positions[msg.sender].length - 1, isLong, size, collateralAmount, price); }

    /**

    • @notice 平仓 */ function close(uint256 pid) external nonReentrant { Position storage p = positions[msg.sender][pid]; require(p.isActive, "Not Active"); _close(msg.sender, pid, p); }

    /**

    • @notice 内部平仓逻辑 */ function _close(address user, uint256 pid, Position storage p) internal { uint256 currentPrice = getNormalizedPrice(); (bool profit, uint256 pnlValue) = _calculatePnL(p, currentPrice);

      uint256 payOut; if (profit) { payOut = p.collateral + pnlValue; } else { payOut = pnlValue >= p.collateral ? 0 : p.collateral - pnlValue; }

      p.isActive = false; if (payOut > 0) collateralToken.safeTransfer(user, payOut);

      emit Closed(user, pid, currentPrice, profit ? int256(pnlValue) : -int256(pnlValue)); }

    /**

    • @notice 清算逻辑 */ function liquidate(address user, uint256 pid) external nonReentrant onlyRole(LIQUIDATOR_ROLE) { Position storage p = positions[user][pid]; require(p.isActive, "Not Active");

      uint256 currentPrice = getNormalizedPrice(); (bool profit, uint256 pnlValue) = _calculatePnL(p, currentPrice);

      // 清算条件:非盈利且亏损导致剩余保证金低于维持保证金要求 bool isLiquidatable = !profit && (p.collateral <= pnlValue || (p.collateral - pnlValue) BPS_DENOMINATOR < uint256(p.size) MAINTENANCE_MARGIN_BPS / 100);

      require(isLiquidatable, "Position Safe");

      uint256 reward = uint256(p.collateral) / 20; // 5% 清算奖励 p.isActive = false;

      collateralToken.safeTransfer(msg.sender, reward); // 剩余保证金通常留给协议作为风险基金

      emit Liquidated(user, pid, msg.sender, currentPrice, reward); }

    /**

    • @dev 计算 PnL,返回 (是否盈利, 盈亏金额) / function _calculatePnL(Position memory p, uint256 currentPrice) internal view returns (bool profit, uint256 pnlValue) { uint256 entryPriceStandard = uint256(p.entryPrice) (10**(18 - priceDecimals));

      if (currentPrice == entryPriceStandard) return (true, 0);

      uint256 priceDiff; if (p.isLong) { profit = currentPrice > entryPriceStandard; priceDiff = profit ? currentPrice - entryPriceStandard : entryPriceStandard - currentPrice; } else { profit = currentPrice < entryPriceStandard; priceDiff = profit ? entryPriceStandard - currentPrice : currentPrice - entryPriceStandard; }

      pnlValue = (uint256(p.size) * priceDiff) / entryPriceStandard; }

    function positionCount(address user) external view returns (uint256) { return positions[user].length; } }

    ## **测试脚本**
    **测试说明**:
    * **验证初始化参数与标准化价格**
    * **用户开仓 (Long 10x) 应该成功**
    * **价格上涨后平仓应该获利**
    * **爆仓逻辑验证 (Liquidate)**

    import assert from "node:assert/strict"; import { describe, it, beforeEach } from "node:test"; import hre from "hardhat"; import { parseEther, parseUnits, formatUnits } from "viem";

describe("PerpTrade 永续合约逻辑测试", async function () { // 获取 Hardhat Viem 实例 const { viem } = await hre.network.connect();

let owner: any, user: any;
let publicClient: any;
let PerpTrade: any;
let MockPriceFeed: any;
let CollateralToken: any;

const INITIAL_PRICE = "3500.65"; // $3500.65
const PRICE_DECIMALS = 8;

beforeEach(async function () {
    publicClient = await viem.getPublicClient();
    [owner, user] = await viem.getWalletClients();

    // 1. 部署代币 (假设精度为 18)
    CollateralToken = await viem.deployContract("BoykaYuriToken", [
        owner.account.address,
        owner.account.address
    ]);

    // 2. 部署模拟预言机 (价格, 精度)
    const initialPriceWei = parseUnits(INITIAL_PRICE, PRICE_DECIMALS);
    MockPriceFeed = await viem.deployContract("MockV3Aggregator", [
        initialPriceWei, 
        PRICE_DECIMALS
    ]);

    // 3. 部署永续合约
    PerpTrade = await viem.deployContract("PerpTrade", [
        CollateralToken.address,
        MockPriceFeed.address
    ]);

    // 4. 给用户准备点钱并授权
    const fundAmount = parseEther("10000");
    await CollateralToken.write.transfer([user.account.address, fundAmount]);

    // 用户需要授权给 PerpTrade 才能开仓
    await CollateralToken.write.approve([PerpTrade.address, fundAmount], {
        account: user.account
    });
    // --- 新增:给 PerpTrade 合约注入“利润储备金” ---
    // 确保合约有足够的余额支付给获利的交易者
    const treasuryAmount = parseEther("50000");
    await CollateralToken.write.transfer([PerpTrade.address, treasuryAmount]);
});

it("验证初始化参数与标准化价格", async function () {
    const price = await PerpTrade.read.getNormalizedPrice();
    // 应该被标准化为 1e18
    assert.equal(price, parseEther(INITIAL_PRICE));

    const tokenAddress = await PerpTrade.read.collateralToken();
    assert.equal(tokenAddress.toLowerCase(), CollateralToken.address.toLowerCase());
});

it("用户开仓 (Long 10x) 应该成功", async function () {
    const collateral = parseEther("100"); // 100 保证金
    const leverage = parseEther("10");    // 10x 杠杆

    // 执行开仓
    await PerpTrade.write.open([true, collateral, leverage], {
        account: user.account
    });

    const count = await PerpTrade.read.positionCount([user.account.address]);
    assert.equal(count, 1n);

    const position = await PerpTrade.read.positions([user.account.address, 0n]);
    // [size, collateral, entryPrice, lastUpdate, isLong, isActive]
    assert.equal(position[0], parseEther("1000")); // size = 100 * 10
    assert.equal(position[1], collateral);
    assert.equal(position[4], true); // isLong
    assert.equal(position[5], true); // isActive
});

it("价格上涨后平仓应该获利", async function () {
    const collateral = parseEther("100");
    const leverage = parseEther("10"); // 1000 USD size

    await PerpTrade.write.open([true, collateral, leverage], { account: user.account });

    // 模拟价格上涨 10%: 3500.65 -> 3850.715
    const newPrice = parseUnits("3850.715", PRICE_DECIMALS);
    await MockPriceFeed.write.updateAnswer([newPrice]);

    // 获取用户平仓前的余额
    const balBefore = await CollateralToken.read.balanceOf([user.account.address]);

    // 平仓
    await PerpTrade.write.close([0n], { account: user.account });

    const balAfter = await CollateralToken.read.balanceOf([user.account.address]);

    // 盈利计算: size(1000) * delta(10%) = 100
    // 总返还 = 100(保证金) + 100(盈利) = 200
    assert.ok(balAfter > balBefore, "余额应该增加");
    const profit = balAfter - balBefore;
    // 允许微小的精度舍入误差
    assert.ok(profit >= parseEther("199.9") && profit &lt;= parseEther("200.1"));
});

it("爆仓逻辑验证 (Liquidate)", async function () {
    const collateral = parseEther("100");
    const leverage = parseEther("20"); // 20x 杠杆极易爆仓

    await PerpTrade.write.open([true, collateral, leverage], { account: user.account });

    // 价格下跌 5%, 亏损即达到 100% 保证金 (20x * 5% = 100%)
    const crashPrice = parseUnits("3325.6175", PRICE_DECIMALS);
    await MockPriceFeed.write.updateAnswer([crashPrice]);

    // 管理员(Owner)执行清算
    await PerpTrade.write.liquidate([user.account.address, 0n], {
        account: owner.account
    });

    const position = await PerpTrade.read.positions([user.account.address, 0n]);
    assert.equal(position[5], false, "仓位应该已关闭");

    // 检查管理员是否拿到了清算奖励 (5%)
    const adminReward = await CollateralToken.read.balanceOf([owner.account.address]);
    assert.ok(adminReward > 0n);
});

});

## **部署脚本**

// scripts/deploy.js import { network, artifacts } from "hardhat"; import {parseEther,parseUnits} from "viem" async function main() { // 连接网络 const { viem } = await network.connect({ network: network.name });//指定网络进行链接

// 获取客户端 const [deployer] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient();

const deployerAddress = deployer.account.address; console.log("部署者的地址:", deployerAddress); // 加载合约 const BoykaYuriTokenArtifact = await artifacts.readArtifact("BoykaYuriToken");

// 部署(构造函数参数:recipient, initialOwner) const BoykaYuriTokenHash = await deployer.deployContract({ abi: BoykaYuriTokenArtifact.abi,//获取abi bytecode: BoykaYuriTokenArtifact.bytecode,//硬编码 args: [deployerAddress,deployerAddress],//process.env.RECIPIENT, process.env.OWNER });

// 等待确认并打印地址 const BoykaYuriTokenArtifactReceipt = await publicClient.waitForTransactionReceipt({ hash: BoykaYuriTokenHash }); console.log("BoykaYuriToken合约地址:", BoykaYuriTokenArtifactReceipt.contractAddress); const MockV3AggregatorArtifact = await artifacts.readArtifact("MockV3Aggregator"); // 部署(构造函数参数:recipient, initialOwner) const MOCK_PRICE = 3500.65; const PRICE_DECIMALS = 8; const initialPriceWei = parseUnits(MOCK_PRICE.toString(), PRICE_DECIMALS); const MockV3AggregatorHash = await deployer.deployContract({ abi: MockV3AggregatorArtifact.abi, bytecode: MockV3AggregatorArtifact.bytecode, // 必须匹配构造函数顺序: [initialAnswer, _decimals] args: [initialPriceWei, PRICE_DECIMALS], }); // 等待确认并打印地址 const MockV3AggregatorArtifactReceipt = await publicClient.waitForTransactionReceipt({ hash: MockV3AggregatorHash }); console.log("MockV3Aggregator合约地址:", MockV3AggregatorArtifactReceipt.contractAddress); const PerpTradeArtifact = await artifacts.readArtifact("PerpTrade"); // 部署(构造函数参数:recipient, initialOwner) const PerpTradeHash = await deployer.deployContract({ abi: PerpTradeArtifact.abi,//获取abi bytecode: PerpTradeArtifact.bytecode,//硬编码 args: [BoykaYuriTokenArtifactReceipt.contractAddress,MockV3AggregatorArtifactReceipt.contractAddress],//process.env.RECIPIENT, process.env.OWNER }); // 等待确认并打印地址 const PerpTradeArtifactReceipt = await publicClient.waitForTransactionReceipt({ hash: PerpTradeHash }); console.log("PerpTrade合约地址:", PerpTradeArtifactReceipt.contractAddress);

}

main().catch(console.error);


# 结语
至此,关于永续合约的相关概念知识梳理,以及对应的代码实现工作已全部完整呈现。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
0x5D5C...2dD7
江湖只有他的大名,没有他的介绍。